获取源代码 git clone https://github.com/coderebot/ListViewAutoAdapter.git 或者使用svn co https://github.com/coderebot/ListViewAutoAdapter
网上有很多介绍ListView的用法,大多涉及了Adapter。Android提供的Adapter主要有ArrayAdapter, SimpleAdapter, SimpleCursorAdapter。当然,也介绍了如何自己从BaseAdapter继承的方法。
但是,这些文章介绍的方法距离实际使用还是很有距离的,基本处于练手的级别。对于很多开发者来说,实现一个功能复杂、效率高的ListView还是有一点难度。
我根据自己的编程经验,结合网上的介绍,利用java的反射原理,提供一种使用更加简便、效率更高、控制更加灵活的方法。
我的灵感来源自SimpleAdapter。使用过SimpleAdapter的朋友都知道,SimpleAdapter是一种使用简单而功能强大的Adapter,可以实现大多数我们已知的ListView类型。但是SimpleAdapter还是存在很多问题:
我通过实现一个AutoBinderAdapter来解决以上遇到的问题。
为了便于说明,请先看截图:
代码下载可以到:http://download.csdn.net/detail/doon/6819483
Activity的布局很简单,就是一个listview: (activity_main.xml)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <ListView android:id="@+id/listView1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentRight="true" android:layout_alignParentTop="true" > </ListView> </RelativeLayout>listitem的布局也非常简单:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > <ImageView android:id="@+id/img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="3dp" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > <TextView android:id="@+id/title" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_gravity="left" android:layout_weight="1" android:textSize="16sp" /> <ImageView android:id="@+id/img_constellation" android:layout_width="32dp" android:layout_height="32dp" android:layout_gravity="right" android:layout_margin="1dp" /> </LinearLayout> <TextView android:id="@+id/info" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="10sp" /> </LinearLayout> </LinearLayout>
package org.liview.listviewadpter; import org.liview.listviewadpter.R; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.widget.ListView; import java.util.List; import java.util.ArrayList; import android.view.View; import android.widget.ImageView; public class MainActivity extends Activity { private ListView mListView; //这里定义了数据结构 public static class Beauty { public String name; public int headIcon; public String info; public int constellation; //星座 public static final int Aquarius = 0; public static final int Pisces = 1; public static final int Aries = 2; public static final int Taurus = 3; public static final int Gemini = 4; public static final int Cancer = 5; public static final int Leo = 6; public static final int Virgo = 7; public static final int Libra = 8; public static final int Scorpio = 9; public static final int Sagittarius = 10; public static final int Capricorn = 11; public Beauty(String name, int head, String info, int cons) { this.name = name; this.headIcon = head; this.info = info; this.constellation = cons; } } private List<Beauty> mBeauties; private List<Beauty> getData() { if(mBeauties != null) return mBeauties; mBeauties = new ArrayList<Beauty>(); mBeauties.add(new Beauty("貂蝉", R.drawable.mv1, "东汉帝国小姐冠军", Beauty.Aquarius)); mBeauties.add(new Beauty("王昭君", R.drawable.mv2, "和亲大使", Beauty.Capricorn)); mBeauties.add(new Beauty("西施", R.drawable.mv3, "越国美女007", Beauty.Leo)); mBeauties.add(new Beauty("杨贵妃", R.drawable.mv4, "最不担心体重的美丽女人", Beauty.Gemini)); mBeauties.add(new Beauty("苍井空", R.drawable.mv5, "屌丝女神", Beauty.Aries)); mBeauties.add(new Beauty("赫本", R.drawable.mv6, "最有公主气质的女人", Beauty.Sagittarius)); return mBeauties; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView)findViewById(R.id.listView1); <span style="white-space:pre"> </span>//这里是关键,实现adpater的地方 try{ mListView.setAdapter( new AutoBindAdapter<Beauty>( this, R.layout.listitem, getData(), new AutoBindAdapter.AutoBinder() .add(R.id.img, Beauty.class.getField("headIcon")) .add(R.id.title, Beauty.class.getField("name")) .add(R.id.info, Beauty.class.getField("info")) .add(R.id.img_constellation, Beauty.class.getField("constellation"), new AutoBindAdapter.Binder() { public void setView(View view, Object obj) { ImageView img = (ImageView)(view); int resId = 0; int constellation = (Integer)obj; switch(constellation){ case Beauty.Aquarius: resId = R.drawable.aquarius; break; case Beauty.Pisces: resId = R.drawable.pisces; break; case Beauty.Aries: resId = R.drawable.aries; break; case Beauty.Taurus: resId = R.drawable.taurus; break; case Beauty.Gemini: resId = R.drawable.gemini; break; case Beauty.Cancer: resId = R.drawable.cancer; break; case Beauty.Leo: resId = R.drawable.leo; break; case Beauty.Virgo: resId = R.drawable.virgo; break; case Beauty.Libra: resId = R.drawable.libra; break; case Beauty.Scorpio: resId = R.drawable.scorpio; break; case Beauty.Sagittarius: resId = R.drawable.sagittarius; break; case Beauty.Capricorn: resId = R.drawable.capricorn; break; default: return; } img.setImageResource(resId); } } ) ) ); }catch(Exception e) { e.printStackTrace(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
请大家注意,这里我通过定义class Beauty来表示数据,而不是像SimpleAdapter那样,必须定义一个包含'name","headIcon","info"和"constellation" 的HashMap对象。因为在通常情况下,开发者为了效率和方便访问数据,都会将数据声明为一个class类型。
对于ListView来说,Beauty就是一个原始类型。
下面,我们通过AutoBinderAdapter来让ListView直接使用,请看下面的分解代码:
mListView.setAdapter( new AutoBindAdapter<Beauty>( this, R.layout.listitem, getData(), new AutoBindAdapter.AutoBinder().... )AutoBindAdapter类的构造函数接受4个参数:context, 资源id,List数据和一个Binder。 其他三个同ArrayBinder类,第四个Binder对象是负责将数据和ListItem的View结合在一起的。我们先看一下:
new AutoBindAdapter.AutoBinder() .add(R.id.img, Beauty.class.getField("headIcon")) ....AutoBindAdapter.AutoBinder类有一个add方法,其返回值是它自己,因此我们可以连续调用,就像火车头带车厢一样,可以带N多个add调用。
add方法有两个重载,上面的例子是一个简单的方法:接受一个id(ListItem中的子id),和一个Field对象,通过Beauty.class.getField的反射方法直接获取。
它的工作原理是:1. 通过id,找到对应子view;2. 通过Field的get方法,从Beauty对象中取得对应的属性值;3. 把获取的属性值设置给对应的View。
在默认情况下,我们根据View的类型和Field的类型,自动匹配数据和View,但是,AutoBindAdapter也允许开发者提供自定义的设置类型:
.add(R.id.img_constellation, Beauty.class.getField("constellation"), <span style="white-space:pre"> </span>new AutoBindAdapter.Binder() { <span style="white-space:pre"> </span>public void setView(View view, Object obj) { ImageView img = (ImageView)(view); int resId = 0; int constellation = (Integer)obj; switch(constellation){ case Beauty.Aquarius: resId = R.drawable.aquarius; break; case Beauty.Pisces: resId = R.drawable.pisces; break; case Beauty.Aries: resId = R.drawable.aries; break; case Beauty.Taurus: resId = R.drawable.taurus; break; case Beauty.Gemini: resId = R.drawable.gemini; break; case Beauty.Cancer: resId = R.drawable.cancer; break; case Beauty.Leo: resId = R.drawable.leo; break; case Beauty.Virgo: resId = R.drawable.virgo; break; case Beauty.Libra: resId = R.drawable.libra; break; case Beauty.Scorpio: resId = R.drawable.scorpio; break; case Beauty.Sagittarius: resId = R.drawable.sagittarius; break; case Beauty.Capricorn: resId = R.drawable.capricorn; break; default: return; } img.setImageResource(resId); } } )我们创建了一个匿名类,继承自AutoBindAdapter.Binder,实现了方法setView。setView有两个参数:view表示被设置的view对象;obj是数据。
在这个例子中,我们获取的是星座的信息,它是0~11的数字,表示12个星座;view是一个ImageView,要显示对应的星座的图片。该匿名类就是实现这个转换,并设置给imageview的。
AutoBindAdapter的使用已经够简单了吧,重点是AutoBindAdapter的实现。
AutoBindAdapter继承自BaseAdapter:
public class AutoBindAdapter<E> extends BaseAdapter { private List<E> mList; private final Context mContext; private final LayoutInflater mInflater;
public static abstract class Binder { private static Binder defaultBinder = new Binder() { public void setView(View view, Object obj){ ..... //由于篇幅限制,省略,有兴趣者可以看代码 }; public abstract void setView(View view, Object obj); public static Binder getDefault() { return defaultBinder; } }
实现一个自动映射的binder类 (看里面的注释吧,不单独写了)
public static class AutoBinder extends Binder{ public static class BindInfo { public int viewId; public AccessibleObject access; public Binder bind; //这里又使用了一个Binder。AutoBinder是给Adapter用的,这个bind是给listitem的子view使用的,因为它们的接口都一样,所以就借用了Adapter的Binder接口了。 public BindInfo(int id, AccessibleObject access, Binder bind) { viewId = id; this.access = access; this.bind = bind; } //这里是获取子数据的地方 public Object getValue(Object obj) { try{ if(access instanceof Field) //按照Field调用 { return ((Field)access).get(obj); } else { return ((Method)access).invoke(obj, (Object[])null); //按照Method调用,我假定是一个get方法 } } catch(Exception e) { e.printStackTrace(); } return null; } } private List<BindInfo> mBindList; //这是包含每个具体数据和view的映射关系的列表 public AutoBinder() { mBindList = new ArrayList<BindInfo>(); } //要求用户提供binder的add方法 public AutoBinder add(int viewId, AccessibleObject access, Binder bind) { mBindList.add(new BindInfo(viewId, access, bind)); return this; //返回自身,可以实现连续调用 } //直接使用默认binder的add方法 public AutoBinder add(int viewId, AccessibleObject access) { return add(viewId, access, Binder.getDefault()); } //这个方法被Adapter的getView调用,用于绑定数据和子view public void setView(View view, Object obj) { int size = mBindList.size(); //获得总共的子数据项和子view项 if(view == null || obj == null || size <= 0) return ; View[] holders = (View[]) view.getTag(); //这是ViewHolder的优化方法,显然不需要定义一个专门的类来表示ViewHolder,只需要一个固定长度的View数组即可 if(holders == null) //第一次调用,没有缓存 { holders = new View[size]; //创建ViewHolder for(int i = 0; i < size; i ++) //依次处理每个子项 { BindInfo bind = mBindList.get(i); //获得对应位置的binder信息 holders[i] = view.findViewById(bind.viewId); holders[i].setTag(bind); //这又是一个加速方法,不用每次调用mBindList.get方法 bind.bind.setView(holders[i], bind.getValue(obj)); //调用真正setView } view.setTag(holders); //注意我上传的源代码这里忘记添加了,是个bug } else { //利用缓存 for(int i = 0; i < size; i++) { BindInfo bind = (BindInfo)holders[i].getTag(); //从缓存中取得binder bind.bind.setView(holders[i], bind.getValue(obj)); //直接进行绑定 } } } }
注:上传的资源中的:view.setTag(holders) 一句忘了写了,会造成bug,请大家使用时自行添加注意!
@Override public View getView(int postion, View convert, ViewGroup parent) { View view; if(convert == null) view = mInflater.inflate(mViewId, parent, false); else view = convert; bindView(view, mList.get(postion)); return view; } private void bindView(View view, Object obj) { if(mBinder == null) { Binder.getDefault().setView(view, obj); } else { mBinder.setView(view, obj); } }mBinder就是构造时传递过来的。 很简单,不说了。
AutoBinderAdapter用起来是不是很简单呢?
我已经将一些常用的优化技术集中在AutoBinderAdapter中了,我相信还会有一些优化技术用在其中。
而且,AutoBinderAdapter完全不依赖于用户的具体数据类型,这样,不同的listview完全可以使用同一套代码,可维护性大大的提高。
PS: Binder这个名字实在不好,和Android的Binder冲突了,但是我一时半会想不到更好的名字,如果哪位有什么好的建议,请告诉我哦!