RecyclerView虽然因其灵活性、高效性等特点而备受好评,但也不是一定得用它把ListView给替代掉。在某些场景中,ListView还是相对更适合的。比如数据量不大,不频繁更新,并且需要简单地设置一下divider或header、footer的时候,相对于RecyclerView的繁琐,ListView在实现上则表现得更方便和简洁。

过去的封装

在使用ListView的过程中,为了复用ListView中的convertView以及优化getView()时的findViewById操作,我们通常会引入一个ViewHolder类,来持有itemView的子view。但是不便的是,我们需要为每一种显示不同数据的ListView都重写其BaseAdapter的getView(),于是就有了对其的一种封装:

创建一个通常的ViewHolder,里面用SparseArray<View>来缓存,如下:

public class ViewHolder {
private final View itemView;
private SparseArray<View> mHolderViews; public ViewHolder(View view) {
itemView = view;
view.setTag(this);
mHolderViews = new SparseArray<>();
} public void hold(int... resIds) {
for (int id : resIds) {
mHolderViews.put(id, itemView.findViewById(id));
}
} public <V> V get(int id) {
return (V) mHolderViews.get(id);
}
}

再通过重写BaseAdapter的getView方法,作如下封装:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
holder = createHolder(position, parent);
convertView = holder.itemView;
} else {
holder = (ViewHolder) convertView.getTag();
}
bindData(position, holder, getItem(position));
return convertView;
} public abstract ViewHolder createHolder(int position, ViewGroup parent); public abstract void bindData(int position, ViewHolder holder, T data);

这样使用的时候只需要继承并实现两个抽象方法即可。

这样写法,在后来我觉得还是有些别扭的地方。一是使用的时候还是需要去继承我们封装过的Adapter;二是在bindData()方法中,需要对每一个我们要设置的控件调用holder.get(id)方法,才能开始赋值或进行其他的设置,当我们的item的控件较多时,这些调用会使bindData()显得臃肿而不够纯粹,于是我又对其进行了另一种封装。

另一种优雅封装

这里我主要是通过接口解决重写Adapter的问题,然后再借鉴RecyclerView.ViewHolder,封装结果如下:

假设我们重写BaseAdapter的类为BaseListAdapter,那么首先定义它的一个静态内部类:

    public static abstract class ViewHolder {
public final View itemView; public ViewHolder(View itemView) {
this.itemView = itemView;
itemView.setTag(this);
}
}

然后我们定义一个接口,用于创建ViewHolder以及绑定数据,如下:

package com.githang.android.snippet.demo.adapter.adapter;

import android.view.ViewGroup;

public interface ViewCreator<T, H extends BaseListAdapter.ViewHolder> {

    H createHolder(int position, ViewGroup parent);

    /**
* 设置列表里的视图内容
*
* @param position 在列表中的位置
* @param holder 该位置对应的视图
*/
void bindData(int position, H holder, T data);
}

这里定义了两个泛型 ,一个是T,表示我们Adapter里的数据对象,另一个是H,为我们的ViewHolder的子类,表示我们最终创建出来的ViewHolder。

接下来,修改我们的BaseListAdapter,如下:

public class BaseListAdapter<T, H extends BaseListAdapter.ViewHolder> extends BaseAdapter {
private final List<T> mData;
private final ViewCreator<T, H> mViewCreator; public BaseListAdapter(ViewCreator<T, H > creator) {
this(new ArrayList<T>(), creator);
} public BaseListAdapter(List<T> data, ViewCreator<T, H> creator) {
mData = data == null ? new ArrayList<T>() : data;
mViewCreator = creator;
} @Override
public int getCount() {
return mData.size();
} @Override
public T getItem(int position) {
return mData.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
final H holder;
if (convertView == null) {
holder = mViewCreator.createHolder(position, parent);
convertView = holder.itemView;
} else {
holder = (H) convertView.getTag();
}
mViewCreator.bindData(position, holder, getItem(position));
return convertView;
} public void update(List<T> data) {
mData.clear();
addData(data);
} public void addData(List<T> data) {
if (data != null) {
mData.addAll(data);
}
notifyDataSetChanged();
} public static abstract class ViewHolder {
public final View itemView; public ViewHolder(View itemView) {
this.itemView = itemView;
itemView.setTag(this);
}
}
}

在这个BaseListAdapter里,同样定义了两个泛型,与我们的ViewCreator相同。我们创建ViewHolder及绑定数据的操作,通过调用该接口来执行(见getView方法),而该接口实例则通过构造方法传入。

这样,我们在使用的时候就可以先像使用RecyclerView一样,定义一个实现我们ViewHolder的子类,然后使我们的Fragment或Activity实现这个ViewCreator接口就可以了。

最后,我们还可以继承ViewHolder,封装一个通用的ViewHolder,代码如下:

    public static class DefaultViewHolder extends ViewHolder {
private SparseArray<View> mHolderViews; public DefaultViewHolder(View view) {
super(view);
mHolderViews = new SparseArray<>();
} public void hold(int... resIds) {
for (int id : resIds) {
mHolderViews.put(id, itemView.findViewById(id));
}
} public <V> V get(int id) {
return (V) mHolderViews.get(id);
}
}

接下来是使用示例:

public class SampleFragment extends Fragment implements ViewCreator<User,SampleFragment.UserViewHolder> {

    private BaseListAdapter<User, UserViewHolder> mAdapter = new BaseListAdapter<User, UserViewHolder>(this);

    @Override
public UserViewHolder createHolder(int position, ViewGroup parent) {
return new UserViewHolder(LayoutInflater.from(getActivity()).inflate(R.layout.item_user, parent, false));
} @Override
public void bindData(int position, UserViewHolder holder, User user) {
holder.name.setText(user.name);
holder.email.setText(user.email);
} static class UserViewHolder extends BaseListAdapter.ViewHolder {
public final TextView name;
public final TextView email; public UserViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
email = (TextView) itemView.findViewById(R.id.email);
}
}
}

全部的封装代码加上注释等也不过一百来行,但是可以看到,在这样的封装下,我们的代码已经变得很清晰。

本文的代码见:http://download.csdn.net/detail/maosidiaoxian/9700700

更详细的封装及相关代码见我的开源项目:https://github.com/msdx/AndroidSnippet/tree/master/library/src/main/java/com/githang/android/snippet/adapter

Android开发技巧——BaseAdapter的另一种优雅封装的更多相关文章

  1. Android开发技巧——大图裁剪

    本篇内容是接上篇<Android开发技巧--定制仿微信图片裁剪控件> 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪. 裁剪控件的简单使 ...

  2. Android开发技巧——实现可复用的ActionSheet菜单

    在上一篇<Android开发技巧--使用Dialog实现仿QQ的ActionSheet菜单>中,讲了这种菜单的实现过程,接下来将把它改成一个可复用的控件库. 本文原创,转载请注明出处: h ...

  3. Android开发技巧——高亮的用户操作指南

    Android开发技巧--高亮的用户操作指南 2015-12-15补记: 发现使用PopupWindow进行遮罩层的显示,在华为P7上会有问题.具体表现为:画出来的高亮部分会偏下.原因为:通过view ...

  4. Android开发技巧——自定义控件之增加状态

    Android开发技巧--自定义控件之增加状态 题外话 这篇本该是上周四或上周五写的,无奈太久没写博客,前几段把我的兴头都用完了,就一拖再拖,直到今天.不想把这篇拖到下个月,所以还是先硬着头皮写了. ...

  5. Android开发技巧——自定义控件之使用style

    Android开发技巧--自定义控件之使用style 回顾 在上一篇<Android开发技巧--自定义控件之自定义属性>中,我讲到了如何定义属性以及在自定义控件中获取这些属性的值,也提到了 ...

  6. Android开发技巧——自定义控件之自定义属性

    Android开发技巧--自定义控件之自定义属性 掌握自定义控件是很重要的,因为通过自定义控件,能够:解决UI问题,优化布局性能,简化布局代码. 上一篇讲了如何通过xml把几个控件组织起来,并继承某个 ...

  7. 50个android开发技巧

    50个android开发技巧 http://blog.csdn.net/column/details/androidhacks.html

  8. Android开发技巧——使用PopupWindow实现弹出菜单

    在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...

  9. Android开发技巧——自定义控件之组合控件

    Android开发技巧--自定义控件之组合控件 我准备在接下来一段时间,写一系列有关Android自定义控件的博客,包括如何进行各种自定义,并分享一下我所知道的其中的技巧,注意点等. 还是那句老话,尽 ...

随机推荐

  1. POJ-3295 Tautology---栈+表达式求值

    题目链接: https://vjudge.net/problem/POJ-3295 题目大意: 输入由p.q.r.s.t.K.A.N.C.E共10个字母组成的逻辑表达式WFF      其中      ...

  2. LabelFrame

    LabelFrame组件是Frame组件的变体. 默认情况下,LabelFrame会在其子组件的周围绘制一个边框以及一个标题. 何时使用LabelFrame组件?当你想要奖一些相关的组件分为一组的时候 ...

  3. 简述angular自定义过滤器在页面和控制器中的使用

    首先设置自定义过滤器. 定义模块名:angular ? 1 2 3 4 5 6 .module('myApp') .filter('filterName',function(){ return fun ...

  4. 如何用Math.max.apply()获取数组最大/小值

    最近似乎对JavaScript有点兴趣了~~~打算好好钻研这个东西.可是,一开始就遇到问题了!!! Math.min.apply(obj,args);//这个obj对象将代替Function类里thi ...

  5. background属性的学习整理转述

    前言:这是笔者学习之后自己的理解与整理.如果有错误或者疑问的地方,请大家指正,我会持续更新! background我们一般用到的的属性有: background-attachment:背景(图片)是否 ...

  6. 用PHP如何实现这种乘法口诀表?

    用PHP如何实现这种乘法口诀表? 1x1=1 ,1x2=2 ,1x3=3 ,.....,1x9=9 2x2=4 ,2x3=6 ,......,2x9=18 ........ ...... 8x8=64 ...

  7. [LeetCode] Design Excel Sum Formula 设计Excel表格求和公式

    Your task is to design the basic function of Excel and implement the function of sum formula. Specif ...

  8. SpringMVC 自定义类型转换器

    先准备一个JavaBean(Employee) 一个Handler(SpringMVCTest) 一个converters(EmployeeConverter) 要实现的输入一个字符串转换成一个emp ...

  9. shell编程-项目部署(二)

    上节我们讲了项目部署的准备工作,现在具体讲下代码部署 首先梳理下思路,大致是这样: 获取代码 打包代码 传输代码 关闭应用 解压文件 放置文件(备份老文件,放置新的文件) 开启应用 最后检查下 OK, ...

  10. 机器学习技法:14 Radial Basis Function Network

    Roadmap RBF Network Hypothesis RBF Network Learning k-Means Algorithm k-Means and RBF Network in Act ...