您的位置:首页 > 移动开发 > Android开发

代码优化>>>Android ListView适配器三级优化详解

2016-10-02 00:38 435 查看
转载本专栏每一篇博客请注明转载出处地址,尊重原创。此博客转载链接地址:点击打开链接 http://blog.csdn.net/qq_32059827/article/details/52718489
对ListView的优化,也就是对其封装:抽取方法共性,封装 BaseAdapter 和 ViewHolder

大多App都会使用到的基本控件 ——- Listiew,特别像新闻浏览类的比如说“今日关注”,或者“应用宝”这种汇集手机软件集合的。而且大家都知道 需要给每个单独的 ListView 搭配相应的适配器 Adapter 。如果你的项目中使用ListView 的频率很少甚至没有,那我不建议你对 ListView 进行抽取封装,但是!如果它的使用渗透到App中大多页面时,你必须考虑 对Adapter的公共方法进行抽取封装到单独的类中,来避免整个项目代码的冗杂,使代码结构规范化

首先,我们在写ListView的时候一般会这么写:

一. 未封装版 ListView

@Override
public View onCreateSuccessView() {
ListView view = new ListView(UIUtils.getContext());
initData();
view.setAdapter(new MyListViewAdapter());
return view;
}

private void initData() {
for (int i = 0; i < 50; i++) {
mList.add("测试数据" + i);
}
}

class MyListViewAdapterextends BaseAdapter {

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

@Override
public String 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) {
ViewHolder holder = null;
if (convertView == null) {
convertView = View.inflate(UIUtils.getContext(),
R.layout.list_item_home, null);
holder = new ViewHolder();
holder.mListTextView= (TextView) convertView
.findViewById(R.id.tv_content);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

holder.mListTextView.setText(getItem(position));

return convertView;
}
}

static class ViewHolder {
public TextView mListTextView;
}


这里我们主要 把重点放在 AdapterViewHolder 的抽取封装,首先简单分析其逻辑组成

1.首先看到我自定义的 HomeAdapter 继承的是 BaseAdapter 。继承的四个方法中,前三个:getCountgetItemgetItemId 看的出来方法及其简单,

但是getView方法中步骤略复杂,首先梳理清楚方法里的逻辑,才好进一步的封装:

(1)加载布局文件,布局转换(xml —> view)

(2)初始化控件(finViewById)

(3)给ViewHolder设置标记(setTag),便于下次复用

(4)给控件设置数据

那么就开始对市面上60%的项目中的封装方法,进行普通封装。
封装一》》》普通封装:方式,保留getView(),getCountgetItemgetItemId 先封装。

方法:抽取类名:MyBaseAdapter。基类是需要一个数据源的,因此通过构造方法得到这个数据源;注意:数据源是什么类型,通过泛型指定。

public class MyBaseAdapter<T> extends BaseAdapter {//在类旁边声明一下泛型

private ArrayList<T> mList;

/**
* 我需要一个集合,那么就由孩子传递过来。但是孩子这么多,集合可以有好多类型,那么集合到底写什么类型?泛型更好用,孩子什么类型,我就什么类型
*/
public MyBaseAdapter(ArrayList<T> list){
this.mList = list;
}

@Override
public int getCount() {
// TODO Auto-generated method stub
return mList.size();
}

@Override
public T getItem(int position) {
// TODO Auto-generated method stub
return mList.get(position);
}

@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
return null;
}

}


因为抽取了一些方法,我们原来的Adapter类继承自MyBaseAdapter的代码就简单了一些,如下:

private class MyListViewAdapter extends MyBaseAdapter<String>{//在这里告知父亲,我继承你我要给你的集合传递什么类型

//通过构造函数,把孩子的集合传给父亲
public MyListViewAdapter(ArrayList<String> list) {
super(list);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 自定义listview界面
ViewHolder holder = null;
if(convertView == null){
holder = new ViewHolder();
//1、加载item布局
convertView = UIUtils.inflate(R.layout.homefragment_listview_item);
//2、获取item布局实例
holder.mListTextView = (TextView) convertView.findViewById(R.id.tv_listview_item_text);
//3、viewholder设置到tag
convertView.setTag(holder);
}else{
//若不为空,在缓存对象里取出
holder = (ViewHolder) convertView.getTag();
}

//4、设置listview布局上的控件数据
holder.mListTextView.setText(getItem(position));
return convertView;
}

}


同时,原来实例化适配器代码也要稍作修改,传递数据源到基类里面:

//获取适配器对象
MyListViewAdapter listAdapter = new MyListViewAdapter(mListDatas);


现在运行程序,跟以前的效果是一样的。如下:



到目前为止,叫做是普通抽取,也就是60%应用可能是这么做的,对于每个子类的getView()都是自己实现自己的。

但是要想再高大上一些,要想根那些牛逼点的app一样,就要抽取getView(),对于这一点,比较复杂。接下来就一点点的演变整个过程。

在演变之前,要对ListView的加载流程几个问题要清楚,只有理解了ListView的加载流程,才能更好的而理解抽取getView()的思想。

HomeHolder抽取前思考问

为什么想到去抽取?

convertView的作用 ?

ViewHolder是什么 ?

ViewHolder的作用 ?

ViewHolder里面需要持有什么对象?

做ViewHolder需要什么条件?

一个ListView会创建几个convertView以及几个ViewHolder的思考?

getView其实主要是做啥?

HomeHolder抽取前思考答

为什么想到去抽取?

总是要创建ViewHolder

有重复代码

convertView的作用 ?

减少view对象的创建

ViewHolder是什么 ?

就是一个普通的类,成员变量有根布局里面的孩子对象.

ViewHolder的作用 ?

减少孩子对象的创建,减少findViewById的调用

ViewHolder里面需要持有什么对象?

持有根布局里面的孩子对象

做ViewHolder需要什么条件?

一个类持有根布局里面的孩子对象即可

其实只要能有根布局就可以了,有根布局就有了对应的孩子.孩子无非就是调用根布局的findViewById初始化即可

一个ListView会创建几个convertView以及几个ViewHolder的思考

如果一个屏幕正好显示6个itemView那么会创建6+1个convertView和6+1个ViewHolder

getView其实主要是做啥?

决定根布局

得到数据

填充数据

相信您已经理解了LitView的加载流程,就看一下第一次演变:

对ViewHolder抽取,定义为HomeHolder。

getView()有两层:视图层V、数据源模型层M。其实属于典型的MVC。抽取的过程,也按照MVC模式

整体的步骤,都详细的注释在代码中了,一目了然:

public class HomeHolder {
//根据getView的设置过程来写这里的代码

//getView()过程:
//0、初始化ViewHolder
//1、初始化布局
//2、viewholder设置到根布局的tag。这里根布局给了mHolderView【viewHolder可以绑定tag的条件:该viewHolder要有孩子的布局,或者所有根布局的控件实例】
//3、初始化孩子对象,即item布局上的控件实例
//4、给孩子(item的布局控件实例)设置数据

/*******************初始化视图************************/
public View mHolderView;

//条件:做Holder需要有孩子对象。该viewHolder要有孩子所有根布局的控件实例,该布局只有一个控件,就是展示文本
TextView mListTextView;

private String mData;

//0、初始化ViewHolder
public HomeHolder(){/**可见,构造方法一调用,初始化了viewholder、初始化了布局、布局控件实例化、此viewHolder绑定到了当前布局的tag中**/
mHolderView = initView();
//2、viewholder设置到根布局的tag。这里根布局给了mHolderView
mHolderView.setTag(this);//this指当前的viewHolder
}

//1、初始化布局
private View initView() {
//return UIUtils.inflate(R.layout.homefragment_listview_item);
View view = UIUtils.inflate(R.layout.homefragment_listview_item);

//3、初始化孩子对象,即item布局上的控件实例
mListTextView = (TextView) view.findViewById(R.id.tv_listview_item_text);

return view;
}

/***********************初始化数据***********************/
//4、给孩子(item的布局控件实例)设置数据
public void setDataAndRefreshHolderView(String data) {
//保存数据
this.mData = data;
//刷新显示,设置数据
refreshHolderView(data);
}

private void refreshHolderView(String data) {
// 刷新数据显示
mListTextView.setText(data);
}
}


经过抽取ViewHolder后,再看一看getView()代码怎么写:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
/**********************初始化视图 决定根布局***********************/
HomeHolder holder = null;
if(convertView == null){
holder = new HomeHolder();
}else{
holder = (HomeHolder) convertView.getTag();
}

/**********************数据刷新显示***********************/

holder.setDataAndRefreshHolderView(getItem(position));
return holder.mHolderView;
}

}


可见,getView()明显的少了好多代码,一下子变得轻松了许多。运行程序,结果一模一样。

这个时候,明显轻松了很多,但是上边的抽取仅仅是针对HomeHolder的,如果页面很多的话,显然要写很多的Holder类。为了节省Holder的代码,在网上抽取。

》》》》HomeHolder抽取成BaseHolder。该类的抽取,是基于上边HomeHolder做修改的。把觉得基类取不到的具体东西定义为抽象方法。例如:加载具体的布局、设置刷新具体的数据。在基类里面都无法得知,交给子类实现。并且,由于成了基类,所以具体的设置的数据类型也不清楚,所以设置传递数据的时候,使用泛型

public abstract class BaseHolder<T> {
//根据getView的设置过程来写这里的代码

//getView()过程:
//0、初始化ViewHolder
//1、初始化布局
//2、viewholder设置到根布局的tag。这里根布局给了mHolderView【viewHolder可以绑定tag的条件:该viewHolder要有孩子的布局,或者所有根布局的控件实例】
//3、初始化孩子对象,即item布局上的控件实例
//4、给孩子(item的布局控件实例)设置数据

/*******************初始化视图************************/
public View mHolderView;

//条件:做Holder需要有孩子对象。该viewHolder要有孩子所有根布局的控件实例,该布局只有一个控件,就是展示文本
//TextView mListTextView;基类不知道孩子对象

private T mData;

//0、初始化ViewHolder
public BaseHolder(){/**可见,构造方法一调用,初始化了viewholder、初始化了布局、布局控件实例化、此viewHolder绑定到了当前布局的tag中**/
mHolderView = initHolderView();
//2、viewholder设置到根布局的tag。因为能获取item的根布局也可以设置tag
mHolderView.setTag(this);//this指当前的viewHolder
}

/**
* 初始化holderView/根视图
* @call BaseHolder初始化的时候调用
* @return
*/
//1、初始化布局
public abstract View initHolderView();

/***********************初始化数据***********************/
/**
* 设置数据和刷新视图
* @call 需要设置数据和刷新数据的时候调用
* @param data
*/
//4、给孩子(item的布局控件实例)设置数据
public void setDataAndRefreshHolderView(T data) {//只有HomeHolder子类是String类型,其他的孩子不一定也是String,有可能还是bean等。因此使用泛型
//保存数据
this.mData = data;
//刷新显示,设置数据
refreshHolderView(data);
}

/**
* 刷新Holder视图
* @call setDataAndRefreshHolderView(T data) 调用的时候就被调用了吧
* 具体的布局我不知道,布局实例更不可能知道。定义为抽象
* @param data
*/
public abstract void refreshHolderView(T data);
}
此时,HomeHolder继承自BaseHolder。看一下HomeHolder的代码简单了多少吧!

public class HomeHolder extends BaseHolder<String> {

private TextView mListTextView;

@Override
public View initHolderView() {
View view = UIUtils.inflate(R.layout.homefragment_listview_item);
mListTextView = (TextView) view
.findViewById(R.id.tv_listview_item_text);
return view;
}

@Override
public void refreshHolderView(String data) {
// 刷新数据显示
mListTextView.setText(data);
}
}
只需要实现两个方法,就搞定了。运行程序,还是跟原来一样的效果。

那么最后,再回到getView()所在的MyListViewAdapter适配器类。当然记得,它也是有父类的MyBaseAdapter。

父类里面还有一个getView()没去写代码,最后就去搞一搞父亲的这个方法,让两个基类MyBaseAdapter和BaseHolder打招呼整合一下。把子类的getView()放到基类里面去

在基类里面做如下修改(与BaseHolder建立了连接)

public abstract class MyBaseAdapter<T> extends BaseAdapter {

private ArrayList<T> mList;

public MyBaseAdapter(ArrayList<T> list){
this.mList = list;
}

@Override
public int getCount() {
// TODO Auto-generated method stub
return mList.size();
}

@Override
public T getItem(int position) {
// TODO Auto-generated method stub
return mList.get(position);
}

@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
/**********************初始化视图 决定根布局***********************/
//基类根基类交谈,不能使用具体的实现类
BaseHolder<T> holder = null;
if(convertView == null){
//该holder应该是具体哪个Holder不清楚。如:HomeHolder、AppHolder等。因此定义抽象方法获取
holder = getSpecialHolder();
}else{
holder = (BaseHolder) convertView.getTag();//注意:从缓存里面取tag
}

/**********************数据刷新显示***********************/

holder.setDataAndRefreshHolderView(getItem(position));
return holder.mHolderView;
}

/**
* 返回具体的BaseHolder的子类
* @call getView()方法中,如果没有converView的时候被创建
* @return
*/
public abstract BaseHolder<T> getSpecialHolder();

}


Adapter的基类抽象方法,且在获取Holder对象时,调用子类具体哪个Holder,只需要在子类中实现这个方法,并且返回具体的哪个Holder就好了。代码如下:

private class MyListViewAdapter extends MyBaseAdapter<String>{//在这里告知父亲,我继承你我要给你的集合传递什么类型

//通过构造函数,把孩子的集合传给父亲
public MyListViewAdapter(ArrayList<String> list) {
super(list);
}

@Override
public BaseHolder<String> getSpecialHolder() {
// TODO Auto-generated method stub
return new HomeHolder();
}
}
这个时候,您发现getView方法的代码以及适配器里面的代码少了太多太多了。而且运行结果还是一样的。

可能仅仅一个Adapter看不出有多强大,如果说有100个类需要适配器的话,那么只需要一个基类,其他的只要继承自基类,每个适配器类里面的方法就上边那几行,很显然,简化了大量的冗余的代码。

最后再总结一下此时的调用加载流程。

当ListView想要关联适配器的时候,创建自己的adapter适配器类对象,同时把集合数据源数据传递过去。

例如此例中的MyListViewAdapter listAdapter = new MyListViewAdapter(mListDatas);这样把mListDatas传给了adapter的基类,是通过带参构造函数的形式传给adapter父类的,他把以前孩子自己要写的getCountgetItemgetItemId方法帮孩子完成,孩子简写三大方法。同时,在ListVIew的item加载布局和数据的时候,getView方法被调用,此时的getView()方法在adapter基类里面呢,对于item的布局的显示和布局实例的数据刷新,都封装在了对应的Holder类里面;先执行holder
= getSpecialHolder();方法来看该adapter配套的Holder是谁。马上调用该adapter的实现方法

@Override

public BaseHolder<String> getSpecialHolder() {

// TODO Auto-generated method stub

return new HomeHolder();

}

看到返回的是HomeHolder,在new HomeHolder();的同时BaseHolder的构造也会被加载(子类初始化先初识化父类构造),此时父类的构造函数

public BaseHolder(){/**可见,构造方法一调用,初始化了viewholder、初始化了布局、布局控件实例化、此viewHolder绑定到了当前布局的tag中**/

mHolderView = initHolderView();

//2、viewholder设置到根布局的tag。因为能获取item的根布局也可以设置tag

mHolderView.setTag(this);//this指当前的viewHolder

}

加载了布局、设置了tag。注意此时public abstract View initHolderView();是调用对应子类实现类HomeHolder的具体方法(调用父类抽象实际调用子类实现方法)。

此时HomeHolder中的

@Override

public View initHolderView() {

View view = UIUtils.inflate(R.layout.homefragment_listview_item);

mListTextView = (TextView) view

.findViewById(R.id.tv_listview_item_text);

return view;

}

被调用,终于看到在哪里初始化布局了!

这一系列方法走完之后,再回到adapter基类的getView()方法,继续往下执行到holder.setDataAndRefreshHolderView(getItem(position));。同上,先调用holder的

//4、给孩子(item的布局控件实例)设置数据

public void setDataAndRefreshHolderView(T data) {//只有HomeHolder子类是String类型,其他的孩子不一定也是String,有可能还是bean等。因此使用泛型

//保存数据

this.mData = data;

//刷新显示,设置数据

refreshHolderView(data);

}

refreshHolderView(data);抽象,调用实现类HomeHolder的实现类方法,完成了数据的设置和刷新。

到此,此次ListView的优化,以及详细的解析终于结束了。

9点半开始写博客,现在0:36,搞了3个多小时。由衷佩服自己,看完您记得关注本博客,或者点赞留下包括意见哦。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: