在Android开发中,我们经常会用到ListView、GridView,每次编码的时候都需要为他们写对应的Adapter,写多了就感觉很烦躁了,因为基本的编程思想都是一样的,但是每次都要重复去写,所以我们能不能把它们抽象成一个通用的模板,这样就不用每次都重复写相同的代码了,直接重复使用,这样不是更好,下面我们就来介绍介绍一个开源项目base-adapter-helper。
传统Adapter的编码思路,主要看Adapter中的getView方法。
public View getView(int pos, View convertView, ViewGroup parent){ ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item, null); holder = new ViewHolder(); holder.text = (TextView) convertView.findViewById(R.id.text)); holder.icon = (ImageView) convertView.findViewButId(R.id.icon)); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.text.setText(DATA[pos]); holder.icon.setImageBitmap((pos & 1) == 1 ? mIcon1 : mIcon2); return convertView; } static class ViewHolder { TextView text; ImageView icon; }
上面使用了一个ViewHolder用来缓存对应Item中的view,并且重用移出的Item,它对应的就是convertView。这样注意为了节省资源,提高效率。这种写法大家都应该很熟悉了。
下面来看看base-adapter-helper是怎样对其进行抽象封装的。首先来看看它的类继承图。
github链接:base-adapter-helper
可以看到BaseQuickAdapter继承自BaseAdapter,同样我们重点关注它的getView函数。
@Override public View getView(int position, View convertView, ViewGroup parent) { if (getItemViewType(position) == 0) { final H helper = getAdapterHelper(position, convertView, parent); T item = getItem(position); helper.setAssociatedObject(item); convert(helper, item); return helper.getView(); } return createIndeterminateProgressView(convertView, parent); } private View createIndeterminateProgressView(View convertView, ViewGroup parent) { if (convertView == null) { FrameLayout container = new FrameLayout(context); container.setForegroundGravity(Gravity.CENTER); ProgressBar progress = new ProgressBar(context); container.addView(progress); convertView = container; } return convertView; }
下面我们分析分析getView的代码:
- 第3行代码就是获取该postion的Item类型,上面定义了两种类型的Item,一种是我们需要显示的View的Item,一种是底部的加载的View的Item。当Item类型为0是就为需要显示的Item,当Item类型为1是就为底部加载的Item,上面的第11行代码的createIndeterminateProgressView就是创建底部用来的加载的Item,可以看到它是一个ProgressBar。另外可以通过showIndeterminateProgress(boolean)来显示或者隐藏这个item。
- 第4行代码的getAdapterHelper获取一个BaseAdapterHelper对象,我们可以看到,在BaseQuickAdapter类中,getAdapterHelper是一个抽象函数,所以我们来看看BaseQuickAdapter的子类QuickAdapter,在这个类中它实现了getAdapterHelper方法,执行的实质是BaseAdapterHelper的静态get方法。另外需要提到的是,QuickAdapter类的构造函数有一个参数为layoutResId,它就是传入我们要显示Item的布局文件。
static BaseAdapterHelper get(Context context, View convertView, ViewGroup parent, int layoutId, int position) { if (convertView == null) { return new BaseAdapterHelper(context, parent, layoutId, position); } // Retrieve the existing helper and update its position BaseAdapterHelper existingHelper = (BaseAdapterHelper) convertView.getTag(); existingHelper.position = position; return existingHelper; }
从上面可以看到它也对convertView进行了重用,当convertView为null,那么我们需要创建一个BaseAdapterHelper,传入的就是要显示的位置position以及layoutId布局文件,这个position为前面getView中的那个position参数,layoutId为创建QuickAdapter传入的参数,就是Item的布局文件。
protected BaseAdapterHelper(Context context, ViewGroup parent, int layoutId, int position) { this.context = context; this.position = position; this.views = new SparseArray<View>(); convertView = LayoutInflater.from(context) // .inflate(layoutId, parent, false); convertView.setTag(this); }
在BaseAdapterHelper的构造函数里面,定义了一个views,它其实就是传统Adapter里面的那个ViewHolder用来存放Item里面的各个view。convertView为我们要显示的Item的View,接着通过setTag函数将BaseAdapterHelper对象本身关联到convertView上面,所以我们知道每个Item对象都关联了一个BaseAdapterHelper对象。
当convertView不为null的时候,我们就直接可以复用这个convertView,首先从convertView中取出它关联的BaseAdapterHelper对象,这样就可以复用这个convertView对应的BaseAdapterHelper对象。
- 接着分析最上面的代码,第5行代码getItem用来得到需要显示的数据,这里需要说明的是,我们应该把需要传入的数据放在一个List中,我们可以看看QuickAdapter的构造函数
public QuickAdapter(Context context, int layoutResId, List<T> data) { super(context, layoutResId, data); }
可以看到我们传入Item布局的layoutResId和要显示的数据data,data是List类型的。
- 第6行代码是将我们向显示的数据项与BaseAdapterHelper对象关联起来。
- 第7行代码convert函数同样是一个抽象函数,它需要我们进行实现,来具体设置对应的Item里面的内容。
- 第8行代码是当BaseAdapterHelper对象设置为Item里面的内容之后,然后就可以得到这个Item的View对象进行返回。
下面我们来具体画个图来说明。假如我们要显示的是一个ListView。
当刚开始显示的时候,因为对应的convertView为null,所以会创建BaseAdapterHelper,核心代码如下:
protected BaseAdapterHelper(Context context, ViewGroup parent, int layoutId, int position) { this.context = context; this.position = position; this.views = new SparseArray<View>(); convertView = LayoutInflater.from(context) // .inflate(layoutId, parent, false); convertView.setTag(this); }
因为在BaseAdapterHelper对象中包含有对应的Item对应的convertView和对应的position以及convertView里面的子view集合,因此我们可以直接通过BaseAdapterHelper对象来完成各项操作。
当List向上滑动的时候,第一个Item移出List,底部就需要再显示一个Item,这个时候getView里面的convertView就是第一个移出的View,我们可以直接对它重用来显示下一个Item,核心代码为:
BaseAdapterHelper existingHelper = (BaseAdapterHelper) convertView.getTag(); existingHelper.position = position;
他直接得到BaseAdapterHelper对象,然后重新设置它对应的位置postion,因为BaseAdapterHelper对象中引用到了重用的convertView,这样就可以直接使用这个view的Item了。
下面来举个简单的例子:
public class MainActivity extends AppCompatActivity { private ListView listView; private List<String> data = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); data.add("text1"); data.add("text2"); data.add("text3"); data.add("text4"); data.add("text5"); data.add("text6"); listView = (ListView) findViewById(R.id.listview); QuickAdapter adapter = new QuickAdapter<String>(this, R.layout.item, data) { @Override protected void convert(BaseAdapterHelper helper, String item) { helper.setText(R.id.textView, item); } }; listView.setAdapter(adapter); } }
也就是说我们只需要定义一个QuickAdapter重写convert方法就可以了,在convert方法里面我们直接使用BaseAdapterHelper对象完成对数据内容item的设置就可以了。