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); } }
开始测试程序,结果是
仔细查看代码流程并没有错,那好 dedug 一下程序,结果发现
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; 打个断点
发现 obj = Integer (我想到了 Java 自动装箱与拆箱(Autoboxing and unboxing) )
Integer 类重写了 Object 父类的 toString() 方法,这在我们平时写 JavaBean 时也提供一个 toString() 便于测试,但它 的toString() 如何返回 String 的
原来这句 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 方法时,的确写得。。。