Android 自定义 Adapter

今天在学习 Android Adapter 中遇到一个奇怪的问题,

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

   主布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#06a"
    android:padding="10dip" >

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="myadapter" />

    <ListView
        android:id="@+id/lv_ad"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </ListView>

    <Button
        android:id="@+id/btn_buy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="提交" />

</LinearLayout>

   列表项Code

  

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/goods_pic"
        android:layout_width="40dip"
        android:layout_height="40dip"
        android:layout_marginLeft="20dip" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/people_name"
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dip"
            android:textSize="20sp" />

        <TextView
            android:id="@+id/goods_price"
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dip"
            android:textSize="18sp" />
    </LinearLayout>

    <CheckBox
        android:id="@+id/cb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dip" />

    <Button
        android:id="@+id/btn_deal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查看" />

</LinearLayout>

   要自定义 Adapter 就必须继承 Android基础类之BaseAdapter  

  

   在实现 MyAdpter 之前 参照以前所学的 SimpleAdapter  用法

   

   最为关键的一句 

   SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.list_item, from, to);

   SimpleAdapter 源码

public class SimpleAdapter extends BaseAdapter implements Filterable {
    private int[] mTo;
    private String[] mFrom;
    private ViewBinder mViewBinder;

    private List<? extends Map<String, ?>> mData;

    private int mResource;
    private int mDropDownResource;
    private LayoutInflater mInflater;

    private SimpleFilter mFilter;
    private ArrayList<Map<String, ?>> mUnfilteredData;

    /**
     * Constructor
     * 
     * @param context The context where the View associated with this SimpleAdapter is running
     * @param data A List of Maps. Each entry in the List corresponds to one row in the list. The
     *        Maps contain the data for each row, and should include all the entries specified in
     *        "from"
     * @param resource Resource identifier of a view layout that defines the views for this list
     *        item. The layout file should include at least those named views defined in "to"
     * @param from A list of column names that will be added to the Map associated with each
     *        item.
     * @param to The views that should display column in the "from" parameter. These should all be
     *        TextViews. The first N views in this list are given the values of the first N columns
     *        in the from parameter.
     */
    public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
            int resource, String[] from, int[] to) {
        mData = data;
        mResource = mDropDownResource = resource;
        mFrom = from;
        mTo = to;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    
    /**
     * @see android.widget.Adapter#getCount()
     */
    public int getCount() {
        return mData.size();
    }

    /**
     * @see android.widget.Adapter#getItem(int)
     */
    public Object getItem(int position) {
        return mData.get(position);
    }

    /**
     * @see android.widget.Adapter#getItemId(int)
     */
    public long getItemId(int position) {
        return position;
    }

    /**
     * @see android.widget.Adapter#getView(int, View, ViewGroup)
     */
    public View getView(int position, View convertView, ViewGroup parent) {
        return createViewFromResource(position, convertView, parent, mResource);
    }

   接下来:

    MyAdapter.java

 public class MyAdapter extends BaseAdapter {

	private int[] mTo;
	private String[] mFrom;

	private List<? extends Map<String, ?>> mData;

	private int mResource;
	private LayoutInflater mInflater;


	public MyAdapter(Context context, List<? extends Map<String, ?>> data,
			int resource, String[] from, int[] to) {
		mData = data;
		mResource  = resource;
		mFrom = from;
		mTo = to;//LayoutInflater inflater = LayoutInflater.from(context);  // 其实现原理就是下面这句
		mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	}
	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return mData.size();
	}

	@Override
	public Object getItem(int position) {
		return mData.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		//
		View view = mInflater.inflate(mResource, null);
		//填充组件
		for (int i = 0; i < mFrom.length; i++) {
			View v = view.findViewById(mTo[i]);
			Object content = mData.get(position).get(mFrom[i]);
			if (v instanceof ImageView) {
				ImageView iv = (ImageView) v;
				iv.setBackgroundResource((Integer)content);
			}
			else if (v instanceof TextView) {
				TextView tv = (TextView)v;
				tv.setText( (String)content); //tv.setText(content.toString());
			}
		}
		}
		return view;
	}




}

   LayoutInflater inflater = LayoutInflater.from(context); 源码 

/**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

 TestAdapter

  

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.heart.listviewdemo.R;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

public class TestAdapter extends Activity {
	private ListView listVIew;

	private static List<Map<String, Object>> data;
	static String[] from;
	static int[] to;
		
	static {
		data = new ArrayList<Map<String, Object>>();
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("pic", R.drawable.p1);
		map.put("name", "移动");
		map.put("price", 10086);
		data.add(map);
		map = new HashMap<String, Object>();
		map.put("pic", R.drawable.p2);
		map.put("name", "联通");
		map.put("price", "10010");
		data.add(map);
		map = new HashMap<String, Object>();
		map.put("pic", R.drawable.p3);
		map.put("name", "电信");
		map.put("price", "0000");
		data.add(map);
		map = new HashMap<String, Object>();
		map.put("pic", R.drawable.p7);
		map.put("name", "Me");
		map.put("price", "1353");
		data.add(map);

		
		from = new String[] { "pic", "name", "price" };
		to = new int[] { R.id.goods_pic, R.id.people_name,
				R.id.goods_price };
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.adapter_ly);
		//
		MyAdapter adapter = new MyAdapter(getApplicationContext(), data, R.layout.myadapter, from, to);
		listVIew = (ListView) findViewById(R.id.lv_ad);
		listVIew.setAdapter(adapter);
	}

}

    

   开始测试程序,结果是

  
Android 自定义 Adapter
 

  仔细查看代码流程并没有错,那好 dedug 一下程序,结果发现

  
Android 自定义 Adapter
 

  content 值为Integer

  所以发生了:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.CharSequence

  content 值是由下面这句获得的

Object content = mData.get(position).get(mFrom[i]);

   

   由此推断:在数据方面出错了

  

map.put("price", 10086);
map.put("price", "10010");

   原来一不小心放了 整型 由于定义了 private static List<Map<String, Object>> data;

   value 放入什么都没关系的

   解决方案

   1.修改 map.put("price","10086");

   2.在之前的 SimpleAdapter 的用法有提过,同样的数据 在 SimpleAdapter  测试中不出错

   这是why?

   

public class SimpleAdapterDemo extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		//
		ListView listView = new ListView(this);
		//

		List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("pic", R.drawable.p1);
		map.put("name", "移动");
		map.put("phone", 10086);
		data.add(map );
		map = new HashMap<String, Object>();
		map.put("pic", R.drawable.p2);
		map.put("name", "联通");
		map.put("phone", 10010);
		data.add(map );
		map = new HashMap<String, Object>();
		map.put("pic", R.drawable.p3);
		map.put("name", "电信");
		map.put("phone", 0000);
		data.add(map );
		map = new HashMap<String, Object>();
		map.put("pic", R.drawable.p7);
		map.put("name", "Me");
		map.put("phone", "13532605287");
		data.add(map );

		String[] from = new String [] {
				"pic",	
				"name",	
				"phone"	
		};
		int[] to = new int [] {
				R.id.people_pic,	
				R.id.people_name,	
				R.id.people_num		
		};
		SimpleAdapter adapter = new SimpleAdapter(this, data, R.layout.list_item, from, to);
		//
		listView.setAdapter(adapter);
		//
		setContentView(listView);
		//
		listView.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				Log.d("TAG", String.valueOf(position));
				Toast.makeText(SimpleAdapterDemo.this, "onItemClick " + position, Toast.LENGTH_LONG).show();
			}
		});
		
		//listView
		listView.setOnItemSelectedListener(new OnItemSelectedListener() {

			@Override
			public void onItemSelected(AdapterView<?> parent, View view,
					int position, long id) {
				Log.d("TAG", String.valueOf(position));
				Toast.makeText(SimpleAdapterDemo.this, "OnItemSelectedListener " + position, Toast.LENGTH_LONG).show();
			}

			@Override
			public void onNothingSelected(AdapterView<?> parent) {
				
			}
		});
	}
}

   程序能运行起来,为什么能运行起来?

  List<Map<String, Object>> data = new ArrayList<Map<String,Object>>();

   

  如果把它改成这样  List<Map<String, String>> data = new ArrayList<Map<String,String>>(); 问题不就好办了

  但是我们不可能都是String 类型的数据吧 如:map.put("pic", R.drawable.p1);

  SimpleAdapter 如何做到的

  

public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
            int resource, String[] from, int[] to) {
        mData = data;
        mResource = mDropDownResource = resource;
        mFrom = from;
        mTo = to;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    List<? extends Map<String, ?>> data  有 Java 基础的人都知道泛型

  

   public View getView(int position, View convertView, ViewGroup parent)  //这个方法很重要,关键是它如何得到一个 View 对象的

/**
     * @see android.widget.Adapter#getView(int, View, ViewGroup)
     */
    public View getView(int position, View convertView, ViewGroup parent) {
        return createViewFromResource(position, convertView, parent, mResource);
    }

    private View createViewFromResource(int position, View convertView,
            ViewGroup parent, int resource) {
        View v;
        if (convertView == null) {
            v = mInflater.inflate(resource, parent, false);
        } else {
            v = convertView;
        }

        bindView(position, v);

        return v;
    }

   

   终于:

  

private void bindView(int position, View view) {
        final Map dataSet = mData.get(position);
        if (dataSet == null) {
            return;
        }

        final ViewBinder binder = mViewBinder;
        final String[] from = mFrom;
        final int[] to = mTo;
        final int count = to.length;

        for (int i = 0; i < count; i++) {
            final View v = view.findViewById(to[i]);
            if (v != null) {
                final Object data = dataSet.get(from[i]);
                String text = data == null ? "" : data.toString();
                if (text == null) {
                    text = "";
                }

                boolean bound = false;
                if (binder != null) {
                    bound = binder.setViewValue(v, data, text);
                }

                if (!bound) {
                    if (v instanceof Checkable) {
                        if (data instanceof Boolean) {
                            ((Checkable) v).setChecked((Boolean) data);
                        } else if (v instanceof TextView) {
                            // Note: keep the instanceof TextView check at the bottom of these
                            // ifs since a lot of views are TextViews (e.g. CheckBoxes).
                            setViewText((TextView) v, text);
                        } else {
                            throw new IllegalStateException(v.getClass().getName() +
                                    " should be bound to a Boolean, not a " +
                                    (data == null ? "<unknown type>" : data.getClass()));
                        }
                    } else if (v instanceof TextView) {
                        // Note: keep the instanceof TextView check at the bottom of these
                        // ifs since a lot of views are TextViews (e.g. CheckBoxes).
                        setViewText((TextView) v, text);
                    } else if (v instanceof ImageView) {
                        if (data instanceof Integer) {
                            setViewImage((ImageView) v, (Integer) data);                            
                        } else {
                            setViewImage((ImageView) v, text);
                        }
                    } else {
                        throw new IllegalStateException(v.getClass().getName() + " is not a " +
                                " view that can be bounds by this SimpleAdapter");
                    }
                }
            }
        }
    }

   

  关键是:data.toString();

String text = data == null ? "" : data.toString();
                if (text == null) {
                    text = "";
                }



// Note: keep the instanceof TextView check at the bottom of these
                            // ifs since a lot of views are TextViews (e.g. CheckBoxes).
                            setViewText((TextView) v, text);

   

   data  不是 Object 类型么? toString(); 不是打印哈希值么?

 经过长时间的研究发现  Object 的toString() 方法 并不返回 Object 的哈希值

public String toString()
{
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

 那只好调试程序了,调程序时,竟然发现 jdk 源码无法进入去,只好折腾一番,大家可以参考 这篇文章

 接下来终于可以调试,jdk 源码了,为了更好的理解运行流程

public class TestToString {
	public static void main(String[] args) {
		@SuppressWarnings("unused")
		Object obj = 1;
		//String s = (String) obj; //报错
		String s =  obj.toString(); //通过
		new TestToString().test();
	}
	
	public void test(){
		Object obj = 1;
		System.out.println(obj instanceof Integer);
		System.out.println(obj == Integer.valueOf(1));
		System.out.println(obj.getClass());
		obj = obj.toString();
		System.out.println(obj.getClass());
		System.out.println(obj instanceof String);
		System.out.println(obj);
		obj = new Integer(1);
	}
}

 在 Object obj = 1; 打个断点

 
Android 自定义 Adapter
 

 发现 obj = Integer  (我想到了 Java 自动装箱与拆箱(Autoboxing and unboxing) )

 
Android 自定义 Adapter
  

  Integer 类重写了 Object 父类的 toString() 方法,这在我们平时写 JavaBean 时也提供一个 toString() 便于测试,但它 的toString() 如何返回 String 的


Android 自定义 Adapter
   
Android 自定义 Adapter
 

      原来这句 return new String(buf, true); 返回了对象,上图能看见 buf 值需要重新编译 jdk 部分源码

    

     原因:这是由于Oracle公司打包jdk时,为缩减体积,去除了二进制文件里的一些东西,所以看不到;目前的解决方案是,把jdk的源码导入到eclipse中,重新编译,然后打包,把jdk路径下的rj.jar替换掉。

重新编译 jdk 比较繁琐,大家可以参考 JDK源码重新编译——支持eclipse调试JDK源码--转载  或

 解决Debug JDK source 无法查看局部变量的问题方案

  修改 Object obj = ”1“;  再调试,只不过 obj=String 了 调用的是 String类的toString(); 返回本身

  

/**
     * This object (which is already a string!) is itself returned.
     *
     * @return  the string itself.
     */
    public String toString() {
        return this;
    }

   总结:

   TextView 的 setText(CharSequence text) 方法 参数是CharSequence 可读可写序列

   然而我们通常这样做 tv.setText((CharSequence) content);  //也许因为 IDE 提示功能,我们习惯性地强转      了。tv.setText( content.toString());  有 java 的自动装箱拆箱,动态编译 支持。使用 toString(); 神马都能返回 String 字符串。虽然在程序上 TextView 显示的绝对是 String 类型的字符串,而不是什么 Integer ,Double 等包装类,数据流转的困难(大家都懂的 Java 是强类型的语言),大家在放数据的 一般情况下页面是放的都是 String 类型,这没什么好担心的,不过 使用 toString() 方法不是很完美么,再说 SimpleAdapter 都是这样做的, 外国人写那个 SimpleAdapter  方法时,的确写得。。。

相关推荐