Android listview 利用反射的自动绑定Adapter

来源:互联网 发布:大数据研判能力 编辑:程序博客网 时间:2024/05/22 05:27

获取源代码 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还是存在很多问题:

  1. 它使用Map对象保存一条数据,空间和时间效率都不高;
  2. SimpleAdapter使用的数据和原始数据是不能直接共享,必须生成一个新的List<Map>对象
  3. View和数据的映射是固定的,不能根据我们的需要做调整,比如,如果数据是一个星期类型,就不能直接提供给View来显示。

我通过实现一个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>

下面看Activity的代码

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;}@Overrideprotected 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();}}@Overridepublic 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;}}


首先,我定义了一个内部类Beauty,表示美女数据,包括name, headIcon, info, constellation四个信息。这些信息都将要显示在ListView里面。

请大家注意,这里我通过定义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;


然后,我们定义一个binder接口:

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;}}

核心是setView方法,需要开发者实现。当然,我为一般情况提供了一个默认的Binder。


实现一个自动映射的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调用,用于绑定数据和子viewpublic 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]; //创建ViewHolderfor(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(); //从缓存中取得binderbind.bind.setView(holders[i], bind.getValue(obj)); //直接进行绑定}}}}

注:上传的资源中的:view.setTag(holders) 一句忘了写了,会造成bug,请大家使用时自行添加注意!


下面是Adapter的重点部分:

@Overridepublic View getView(int postion, View convert, ViewGroup parent) {View view;if(convert == null)view = mInflater.inflate(mViewId, parent, false);elseview = 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冲突了,但是我一时半会想不到更好的名字,如果哪位有什么好的建议,请告诉我哦!


1 0
原创粉丝点击