ListView的三大适配器ArrayAdapter,SimpleAdapter,MyAdapter

ArrayAdapter

是一个只在列表项ListItem上显示TextView的适配器。

其实在列表项上可以有其他组件,在后面会介绍)我们来看一下其构造函数。

  public ArrayAdapter(Context context, int textViewResourceId) {
        init(context, textViewResourceId, 0, new ArrayList());
    }    
    public ArrayAdapter(Context context, int resource, int textViewResourceId) {
        init(context, resource, textViewResourceId, new ArrayList());
    }    
    public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
        init(context, textViewResourceId, 0, Arrays.asList(objects));
    }    
    public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) {
        init(context, resource, textViewResourceId, Arrays.asList(objects));
    }
    public ArrayAdapter(Context context, int textViewResourceId, List objects) {
        init(context, textViewResourceId, 0, objects);
    }    
    public ArrayAdapter(Context context, int resource, int textViewResourceId, List objects) {
        init(context, resource, textViewResourceId, objects);
    }
可以看到最终调用的都是init(...)函数。

   private void init(Context context, int resource, int textViewResourceId, List objects) {
        mContext = context;
        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        mResource = mDropDownResource = resource;
        mObjects = objects;
        mFieldId = textViewResourceId;
    }
1.使用android的布局文件android.R.layout.simple_list_item_1。

ArrayAdapter arrayAdapter=new ArrayAdapter(this,android.R.layout.simple_list_item_1);

在android.R.layout.simple_list_item_1中只有一个TextView,连容器LinearLayout都没有,且也不能有。为什么呢?看看源码。

首先在init(...)函数中,将android.R.layout.simple_list_item_1赋值给mResource。

我们看一下在ListView获取每个列表项Item的getView函数中,是如何得到这个列表项View的。

   public View getView(int position, View convertView, ViewGroup parent) {
        return createViewFromResource(position, convertView, parent, mResource);
    }
来分析一下,下面这段代码的执行,这里mFieldId是等于0的,因为构造函数中的初始化,那么就会执行text = (TextView) view;view的来源是view = mInflater.inflate(resource, parent, false);可以看出,通过inflate布局文件得到view,而这个view又赋值给了TextView。那么在布局文件中肯定只能有一个TextView了。那可不可以有其他控件呢?当然是可以的,那就要是调用其他构造函数了。
private View createViewFromResource(int position, View convertView, ViewGroup parent,
            int resource) {
        View view;
        TextView text;

        if (convertView == null) {
            view = mInflater.inflate(resource, parent, false);
        } else {
            view = convertView;
        }

        try {
            if (mFieldId == 0) {
                //  If no custom field is assigned, assume the whole resource is a TextView
                text = (TextView) view;
            } else {
                //  Otherwise, find the TextView field within the layout
                text = (TextView) view.findViewById(mFieldId);
            }
        } catch (ClassCastException e) {
            Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
            throw new IllegalStateException(
                    "ArrayAdapter requires the resource ID to be a TextView", e);
        }

        T item = getItem(position);
        if (item instanceof CharSequence) {
            text.setText((CharSequence)item);
        } else {
            text.setText(item.toString());
        }

        return view;
    }

我们来看一下这个构造函数。
public ArrayAdapter(Context context, int resource, int textViewResourceId) {
        init(context, resource, textViewResourceId, new ArrayList());
    }

第二个参数resource的含义是布局文件的id,和我们上面的android.R.layout.simple_list_item_1是类似的,第三个参数textViewResourceId是该布局文件中的TextView的id。

首先我们定义一个layout布局文件layout_simple_list,在该布局文件中可以有其他控件。



    
    
既有LinearLayout,还有一个Button控件。那么第三个参数就是文件中的TextView的id:simple_list_item_text。我们来分析调用以下构造函数后的执行过程:
arrayAdapter=new ArrayAdapter(this,R.layout.layout_simple_list,R.id.simple_list_item_text);
在init(...)的执行中,mResource=R.layout.layout_simple_list,mFieldId=R.id.simple_list_item_text。那么在createViewFromResource(...)函数中执行的是
view = mInflater.inflate(resource, parent, false);
text = (TextView) view.findViewById(mFieldId);
这里就可以直观的看出ArrayAdapter内部在得到每一个列表项View时,是通过先实例化layout布局,再从布局中获取TextView控件,所以说布局中有无其他控件是没关系的,但是ArrayAdapter只负责对每个列表项中的TextView进行数据填充。

总的来说,ArrayAdapter是最简单的ListView的适配器,内部只有一个TextView,而且参数我们一般使用android内部的id变量android.R.layout.simple_list_item_1。

SimpleAdapter

是android提供的向ListView添加复杂项的适配器,但其仍然是有局限性的,SimpleAdapter只支持3种组件:

1.实现Checkable接口的组件类。

2.TextView类及其子类。

3.ImageView类及其子类。

我们首先看一下SimpleAdapter的使用方法,再分析其内部的实现。

既然是添加复杂的列表项,那么肯定需要先提供一个xml布局文件,这个文件做为每一个列表项的布局模板。

layout_list_item.xml文件中代码如下:



    
    
    
    



然后因为我们的列表项中组件为TextView和ImageView,我们需要根据其组件id对其进行赋值(类型是字符串或者图片id)。
所以我们需要一个存放组件id的数组,来索引到指定的组件。
int[] to=new int[]{R.id.text,R.id.image};
通过to数组的第i项可以得到组件的id,同样我们需要一个与之对应的存放具体值的数组。
from=new String[]{"Text Index","Image Index"};
to数组与from数组是一一对应的,from数组中存放的是一个key,根据这个key值,我们到Map中去取相应的具体的值。
list=new ArrayList>();
for(int i=0;i<5;i++){
	map=new HashMap();
	map.put("Text Index", "This is TextView"+i);
	map.put("Image Index", R.drawable.ic_launcher);
	list.add(map);
}
list中存放的是每一个列表项的Map,Map中有存放了列表项的每个组件的具体的值。我们就是根据from中存放的索引来得到Map中存放的具体值,然后在将其赋给通过to数组中的值得到的组件。
来看一下SimpleAdapter内部是如何实现得到每个列表项View及给列表项中的组件赋值的。

public SimpleAdapter(Context context, List> data,
            int resource, String[] from, int[] to) {
        mData = data;
        mResource = mDropDownResource = resource;
        mFrom = from;
        mTo = to;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
构造函数中将我们存放键值对的Map的List赋给mData,mResource为我们的列表项组件布局模板,
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;
    }
getView在每次添加列表项是调用,首先根据布局文件来实例化View。
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 ? "" : data.getClass()));
                        }
                    } else if (v instanceof TextView) {
                        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");
                    }
                }
            }
        }
    }
final Map dataSet = mData.get(position);先得到List中存放的键值对Map.
final Object data = dataSet.get(from[i]);再得到具体的值
final View v = view.findViewById(to[i]);利用to数组中存放的id获到组件,
接着分三种情况来讨论组件的类型,Checkable,TextView,ImageView分别进行赋值。其实这样也给我们提供了自定义适配器的思路。
可以看出SimpleAdapter可以支持的组件是有限的,更别说我们自定义的一些组件了。



了实现给ListView添加更复杂的列表项,这是就需要我们来自定义适配器了。

自定义的适配器是继承自BaseAdapter的。

其实我们之前分析的ArrayAdapter和SimpleAdapter都是继承自BaseAdapter的。

即使上面的两种适配器中也可以添加Button组件,但是也只能进行数据填充,Button无法进行点击事件响应。下面我们通过自定义适配器MyAdapter来实现列表项中的Button动态删除列表项自身。

首先看一下列表项的布局模板文件



    
    
    
    
    
    
在这个布局中简单的添加了三个组件:TextView,ImageView,Button。

我们仍然需要一个list来存放组件的具体数据。

public List>mData;
mData=new ArrayList>();
Mapmap1=new HashMap();
map1.put("text", "测试数据1");
map1.put("image", R.drawable.pic1);
map1.put("button", "按钮1");
mData.add(map1);
Mapmap2=new HashMap();
map2.put("text", "测试数据2");
map2.put("image", R.drawable.pic2);
map2.put("button", "按钮2");
mData.add(map2);
Mapmap3=new HashMap();
map3.put("text", "测试数据3");
map3.put("image", R.drawable.pic3);
map3.put("button", "按钮3");
mData.add(map3);
在使用上简单的创建适配器对象,然后对ListView设置适配器。
MyAdapter myAdapter=new MyAdapter(this);
ListView listView5.setAdapter(myAdapter);
然后我们来分析一下MyAdapter的实现。
class ViewHolder{
	TextView text;
	ImageView image;
	Button button;
}
我们需要一个ViewHolder来管理列表项中的组件集合。
class MyAdapter extends BaseAdapter{
		
	private Context mContext;
	private LayoutInflater inflater;
	
	MyAdapter(Context context){
		mContext=context;
		inflater=LayoutInflater.from(context);
	}
	@Override
	public int getCount() {
		if(mData!=null){
			return mData.size();
		}
		else
			return 0;
	}
	@Override
	public Object getItem(int position) {
		return null;
	}
	@Override
	public long getItemId(int position) {
		return 0;
	}
	@Override
	public View getView(final int position, View convertView, ViewGroup parent) {
		if(convertView==null){
			View v=inflater.inflate(R.layout.layout_myadapter, parent, false);
			ViewHolder holder=new ViewHolder();
			holder.text=(TextView)v.findViewById(R.id.text);
			holder.image=(ImageView)v.findViewById(R.id.image);
			holder.button=(Button)v.findViewById(R.id.button);
			holder.text.setText((CharSequence) mData.get(position).get("text"));
			holder.image.setImageResource((Integer) mData.get(position).get("image"));
			holder.button.setText((CharSequence) mData.get(position).get("button"));
			holder.button.setOnClickListener(new OnClickListener(){
				@Override
				public void onClick(View v) {
					Toast.makeText(mContext, (CharSequence) mData.get(position).get("button"), Toast.LENGTH_SHORT).show();
				    mData.remove(position);
				    notifyDataSetChanged();
				}
			});
			v.setTag(holder);
			return v;
		}
		else{
			ViewHolder holder=(ViewHolder)convertView.getTag();
			holder.text.setText((CharSequence) mData.get(position).get("text"));
			holder.image.setImageResource((Integer) mData.get(position).get("image"));
			holder.button.setText((CharSequence) mData.get(position).get("button"));
		}
		return convertView;
	}
		
}
列表项的个数是通过执行getCount函数得到的,也就是mData的大小。我们可以通过删除该List中指定的项来删除指定的列表项。在ListView得到每个列表项的getView函数中,使用了convertView。为什么不是直接生成View呢?假如我们的列表中有1000项,首先肯定不是同时全部生成的,一般是当列表滑动到需要显示时才会生成,那么当该列表项处于显示状态时,convertView==null,当我们滑动列表导致该列表项不可见时,系统并不一定会立即销毁它,而是保存到convertView,所以我们一般先检查convertView是否为空,若不为空,我们则直接使用该convertView,若为空,我们再创建新的view。这里我们将ViewHolder保存到view的Tag,再通过该viewholder来管理组件。也可以看到我们对ViewHolder的Button添加了事件监听,可以动态删除列表项。


你可能感兴趣的:(Android)