Android开发技巧——BaseAdapter的另一种优雅封装

时间:2023-01-13 15:18:19

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.****.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.****.net/column/details/androidhacks.html

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

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

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

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

随机推荐

  1. android 权限大全

    教程 博客 淘帖     论坛›eoe·Android开发资源区›Android开发实例教程 191507 12 / 2 页下一页 android 权限大全 『癲瘋霸気』 于 2013-4-3 10: ...

  2. Devexpress VCL Build v2014 vol 15&period;2&period;3 发布

    2016年第一个版本,继续修补. New Major Features in 15.2 What's New in VCL Products 15.2 Breaking Changes To lear ...

  3. UITableViewCell 单元格样式

    UITableViewCell 单元格样式作用 typedef NS_ENUM(NSInteger, UITableViewCellStyle) { UITableViewCellStyleDefau ...

  4. 如何优雅的写C&plus;&plus;代码(一)

    // get the greatest power of two that is a divisor of n: return n&-n; // swap two integers a and ...

  5. Android(java)学习笔记123:Clock app编写报错01

    1.首先我们二话不说直接先看报错内容如下: 07-12 08:25:03.572: E/dalvikvm(3602): native fork pid:0 done. 07-12 08:25:03.5 ...

  6. C&plus;&plus;学习笔记39:进程概念

    进程的基本概念 进程是描述程序执行过程和资源共享的基本单位 主要目的:控制和协调程序的执行 进程相关函数 用户与组ID函数 创建进程:system(),fork(),exec() 终止进程:kill( ...

  7. IBM Minus One&lpar;water&rpar;

    IBM Minus One Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tot ...

  8. HTTP请求的502、504、499错误

    1.名词解释 502 Bad Gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应(伪响应). 504 Gateway Time-out:作为网关或者代理工作的服务 ...

  9. scrapy入门与进阶

    Scrapy是用纯Python实现一个为了爬取网站数据.提取结构性数据而编写的应用框架,用途非常广泛. 框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非 ...

  10. springboot项目打包部署在指定的tomcat容器中

    1.首先需要修改项目的打包方式,将package改为war <packaging>war</packaging> 2.移除spring boot web中的嵌入式tomcat ...