Android开发技巧——BaseAdapter的另一种优雅封装
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的另一种优雅封装的更多相关文章
- Android开发技巧——大图裁剪
本篇内容是接上篇<Android开发技巧--定制仿微信图片裁剪控件> 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪. 裁剪控件的简单使 ...
- Android开发技巧——实现可复用的ActionSheet菜单
在上一篇<Android开发技巧--使用Dialog实现仿QQ的ActionSheet菜单>中,讲了这种菜单的实现过程,接下来将把它改成一个可复用的控件库. 本文原创,转载请注明出处: h ...
- Android开发技巧——高亮的用户操作指南
Android开发技巧--高亮的用户操作指南 2015-12-15补记: 发现使用PopupWindow进行遮罩层的显示,在华为P7上会有问题.具体表现为:画出来的高亮部分会偏下.原因为:通过view ...
- Android开发技巧——自定义控件之增加状态
Android开发技巧--自定义控件之增加状态 题外话 这篇本该是上周四或上周五写的,无奈太久没写博客,前几段把我的兴头都用完了,就一拖再拖,直到今天.不想把这篇拖到下个月,所以还是先硬着头皮写了. ...
- Android开发技巧——自定义控件之使用style
Android开发技巧--自定义控件之使用style 回顾 在上一篇<Android开发技巧--自定义控件之自定义属性>中,我讲到了如何定义属性以及在自定义控件中获取这些属性的值,也提到了 ...
- Android开发技巧——自定义控件之自定义属性
Android开发技巧--自定义控件之自定义属性 掌握自定义控件是很重要的,因为通过自定义控件,能够:解决UI问题,优化布局性能,简化布局代码. 上一篇讲了如何通过xml把几个控件组织起来,并继承某个 ...
- 50个android开发技巧
50个android开发技巧 http://blog.csdn.net/column/details/androidhacks.html
- Android开发技巧——使用PopupWindow实现弹出菜单
在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...
- Android开发技巧——自定义控件之组合控件
Android开发技巧--自定义控件之组合控件 我准备在接下来一段时间,写一系列有关Android自定义控件的博客,包括如何进行各种自定义,并分享一下我所知道的其中的技巧,注意点等. 还是那句老话,尽 ...
随机推荐
- angular中使用echart遇到的获取容器高度异常的问题记录
问题 在使用echart去创建图表时,发现图表只占了容器的一个角落,如图,并没有充满容器. 第一反应是容器元素的样式有问题,于是我把容器的宽高都改为px指定的(之前是百分比设定的,查询资料发现说ech ...
- 从让 HTTPS 更安全出发,聊聊 HTTPS
随着公众对网络安全的日益关注,各种网络安全防护手段层出不穷.HTTPS Everywhere作为提升HTTPS安全性的有效手段,日前安全性与实用性再次得到了加强. HTTPS虽然可以有效提升用户浏览网 ...
- C#之设计模式之六大原则(转载)
设计模式之六大原则(转载) 关于设计模式的六大设计原则的资料网上很多,但是很多地方解释地都太过于笼统化,我也找了很多资料来看,发现CSDN上有几篇关于设计模式的六大原则讲述的比较通俗易懂,因此转载过来 ...
- C#生成MD5码
/// <summary> /// 获取文件的MD5码 /// </summary> /// <param name="fileName">传入 ...
- python九九乘法表
j = 1 while j <= 9: i = 1 while i <= j: print("%d*%d=%d\t" % (i, j, i*j), end=" ...
- 音频压缩编码 opus 附完整C++代码示例
绝大数人都知道mp3格式编码,以及aac,amr等压缩格式编码. 而在语音通信界有一个强悍的音频格式编码opus. 经过实测,压缩比最高可以达到1:10. 100KB 压缩后 10KB 虽然是有损压缩 ...
- [LeetCode] My Calendar I 我的日历之一
Implement a MyCalendar class to store your events. A new event can be added if adding the event will ...
- [LeetCode] Design Log Storage System 设计日志存储系统
You are given several logs that each log contains a unique id and timestamp. Timestamp is a string t ...
- 持久化 XSS:ServiceWorkers 利用
来源:http://www.mottoin.com/95058.html 来源:https://www.owasp.org/images/3/35/2017-04-20-JSONPXSS.pdf Se ...
- [HNOI 2016]大数
Description 题库链接 给你一个长度为 \(n\) ,可含前导零的大数,以及一个质数 \(p\) . \(m\) 次询问,每次询问你一个大数的子区间 \([l,r]\) ,求出子区间中有多少 ...