获取源代码 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来解决以上遇到的问题。


为了便于说明,请先看截图:

Android listview 利用反射的自动绑定Adapter_第1张图片


代码下载可以到: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. Android之socket编程实例,熟悉socket使用方法
  2. Android——类型转换 时间处理
  3. Android数据持久化——五种方式
  4. Android的ListView数据更新后,如何使最新的条目可以自动滚动到可
  5. Android Re-installation failed解决方法
  6. Android GestureDetector方法详解
  7. Android xxx is not translated in yyy, zzz 的解决方法
  8. android 开发中遇到的问题及解决方法
  9. Android设备如何保证数据同步写入磁盘

随机推荐

  1. 学习android的博客
  2. Android(安卓)获得当前进程PackageName
  3. Android(安卓)VelocityTracker使用总结
  4. android 获取sd卡根目录
  5. 防止Android点击按钮过快造成多次事件实
  6. 直播源码用Glide框架缓存压缩图片做一个
  7. android Handler内存泄露 kotlin内存泄露
  8. Android点击EditText以外区域隐藏键盘Fra
  9. Android(安卓)全屏显示
  10. android 页面跳转(intent)