Android BaseAdapter 的抽象与封装

ListView 是个常用的组件,常用于展示列表数据。使用的时候通常会搭配一个 BaseAdapter 来进行数据的绑定,写多了之后发现,这些 BaseAdapter 都有一些重复或类似的代码,为了让 BaseAdapter 更纯粹,只专注于业务逻辑,所以对 BaseAdapter 进行进一步的抽象。

对于 BaseAdapter,通常需要关注的是 getView() 方法,在这个方法里面进行数据跟 UI 的绑定,其他的方法如 getCount()getItem()getItemId() 等都是固定的写法,这些方法可以封装起来。getView() 一般使用一个 ViewHolder 来重复利用 UI 组件进行性能优化,不然的话 ListView 随着数据的增大性能会急速下降,会卡成幻灯片一样,所以 getView() 里面的步骤也是类似的,区别的地方在于绑定数据和 UI 响应的部分不一样,所以可以只把绑定数据和 UI 响应部分抽象出来。

整体框架

先把 getCount()getItem()getItemId() 这几个固定的方法封装起来,搭建一个整体的框架,getView() 先留空,下一步再处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 通用的 Adapter
* Created by cengt on 8/4/16 10:03.
*/
public abstract class CommonAdapter<T> extends BaseAdapter {
protected Context mContext;
protected List<T> mList;

public CommonAdapter(Context context, List<T> list) {
mContext = context;
mList = list;
}

@Override
public int getCount() {
return mList.size();
}

@Override
public T getItem(int position) {
return mList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}

封装 GetView()

getView() 通常的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
// UI 布局文件绑定
convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item_apps, parent, false);

// 创建一个 ViewHolder 对象,并初始化 UI 组件
holder = new ViewHolder();
holder.appName = (TextView) convertView.findViewById(R.id.app_name);
holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);

// 将 ViewHolder 对象缓存起来,用于下次重复使用
convertView.setTag(holder);
} else {
// 使用缓存的 ViewHolder 对象,ViewHolder 对象保存了 UI 组件的引用,避免每次都创建新的 UI 组件,可以大幅提高性能
holder = (ViewHolder) convertView.getTag();
}

// UI 组件的更新
ResolveInfo info = (ResolveInfo) getItem(position);
holder.appName.setText(info.loadLabel(mPackageManager));
holder.appIcon.setImageDrawable(info.loadIcon(mPackageManager));

return convertView;
}

需要把整个步骤封装起来,抽象出 UI 组件初始化和更新部分,修改之后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;

if (convertView == null) {
// 抽象 `getLayout()` 方法来指定 UI 布局文件
convertView = LayoutInflater.from(mContext).inflate(getLayout(), parent, false);

// 抽象初始化 UI 组件方法
holder = createViewHolder();
holder.initView(convertView);

convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

// 抽象 UI 组件更新方法
holder.updateView(position, getItem(position));

return convertView;
}

getView() 方法的封装就完成了

封装 ViewHolder

ViewHolder 需要提供两个方法供 getView() 调用: initView()updateView(),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 通用的 ViewHolder
* Created by cengt on 8/4/16 10:05.
*/
public abstract class CommonViewHolder<T> {
/**
* 初始化 UI 组件
* @param view
*/
public abstract void initView(View view);

/**
* 更新 UI 组件
* @param position Item 的位置
* @param item
*/
public abstract void updateView(int position, T item);
}

举个栗子

使用的时候,只需要重写四个方法: createViewHolder()getLayout()initView()updateView() 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class AppsAdapter extends CommonAdapter<ResolveInfo> {
private PackageManager mPackageManager;

public AppsAdapter(final Context context, final PackageManager pm, final List<ResolveInfo> resolveInfos) {
super(context, resolveInfos);
mPackageManager = pm;
}

@Override
protected CommonViewHolder<ResolveInfo> createViewHolder() {
return new ViewHolder();
}

@Override
protected int getLayout() {
return R.layout.list_item_apps;
}

private class ViewHolder extends CommonViewHolder<ResolveInfo> {
private TextView appName;
private ImageView appIcon;

@Override
public void initView(View view) {
appName = (TextView) view.findViewById(R.id.app_name);
appIcon = (ImageView) view.findViewById(R.id.app_icon);
}

@Override
public void updateView(int position, ResolveInfo item) {
appName.setText(item.loadLabel(mPackageManager));
appIcon.setImageDrawable(item.loadIcon(mPackageManager));
}
}
}

有图有真相,栗子效果:

完整的代码

CommonAdapter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package cn.snaillauncher.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import java.util.List;

/**
* 通用的 Adapter
* Created by cengt on 8/4/16 10:03.
*/
public abstract class CommonAdapter<T> extends BaseAdapter {
protected Context mContext;
protected List<T> mList;

public CommonAdapter(Context context, List<T> list) {
mContext = context;
mList = list;
}

@Override
public int getCount() {
return mList.size();
}

@Override
public T getItem(int position) {
return mList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
CommonViewHolder holder;

if (convertView == null) {
// 抽象 `getLayout()` 方法来指定 UI 布局文件
convertView = LayoutInflater.from(mContext).inflate(getLayout(), parent, false);

// 抽象初始化 UI 组件方法
holder = createViewHolder();
holder.initView(convertView);

convertView.setTag(holder);
} else {
holder = (CommonViewHolder) convertView.getTag();
}

// 抽象 UI 组件更新方法
holder.updateView(position, getItem(position));

return convertView;
}

/**
* 创建 ViewHolder 对象
* @return ViewHolder 对象
*/
protected abstract CommonViewHolder createViewHolder();

/**
* 绑定 ListView Item 的布局文件
* @return 布局文件的资源 ID
*/
protected abstract int getLayout();
}

CommonViewHolder.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.snaillauncher.adapter;

import android.view.View;

/**
* 通用的 ViewHolder
* Created by cengt on 8/4/16 10:05.
*/
public abstract class CommonViewHolder<T> {
/**
* 初始化 UI 组件
* @param view
*/
public abstract void initView(View view);

/**
* 更新 UI 组件
* @param position Item 的位置
* @param item
*/
public abstract void updateView(int position, T item);
}