Android 自定义 Adapter

本文讲述了在Android开发中自定义Adapter时遇到的Integer转换为String引发的ClassCastException问题。通过分析源码和调试,揭示了Integer类重写toString()方法的原因,并提出了解决方案和优化建议,帮助开发者理解数据类型转换和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  方法时,的确写得。。。

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值