您的位置:首页 > 其它

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

2015-04-16 18:18 246 查看
ArrayAdapter

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

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

public ArrayAdapter(Context context, int textViewResourceId) {
init(context, textViewResourceId, 0, new ArrayList<T>());
}
public ArrayAdapter(Context context, int resource, int textViewResourceId) {
init(context, resource, textViewResourceId, new ArrayList<T>());
}
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<T> objects) {
init(context, textViewResourceId, 0, objects);
}
public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) {
init(context, resource, textViewResourceId, objects);
}
可以看到最终调用的都是init(...)函数。

private void init(Context context, int resource, int textViewResourceId, List<T> 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<String>(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了。那可不可以有其他控件呢?当然是可以的,那就要是调用其他构造函数了。
</pre><pre name="code" class="java">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<T>());
}


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

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

<?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="horizontal">
<TextView
android:id="@+id/simple_list_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"/>
</LinearLayout>
既有LinearLayout,还有一个Button控件。那么第三个参数就是文件中的TextView的id:simple_list_item_text。我们来分析调用以下构造函数后的执行过程:
arrayAdapter=new ArrayAdapter<String>(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文件中代码如下:

<?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="horizontal" >

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

<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</LinearLayout>


然后因为我们的列表项中组件为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<Map<String,Object>>();
for(int i=0;i<5;i++){
map=new HashMap<String,Object>();
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<? 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);
}
构造函数中将我们存放键值对的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 ? "<unknown type>" : 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动态删除列表项自身。

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

<?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="horizontal" >

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

<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除"/>

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

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

public List<Map<String,Object>>mData;
mData=new ArrayList<Map<String,Object>>();
Map<String,Object>map1=new HashMap<String,Object>();
map1.put("text", "测试数据1");
map1.put("image", R.drawable.pic1);
map1.put("button", "按钮1");
mData.add(map1);
Map<String,Object>map2=new HashMap<String,Object>();
map2.put("text", "测试数据2");
map2.put("image", R.drawable.pic2);
map2.put("button", "按钮2");
mData.add(map2);
Map<String,Object>map3=new HashMap<String,Object>();
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添加了事件监听,可以动态删除列表项。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐