转载本专栏每一篇博客请注明转载出处地址,尊重原创。此博客转载链接地址:点击打开链接  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抽取前思考问

  1. 为什么想到去抽取?
  2. convertView的作用 ?
  3. ViewHolder是什么 ?
  4. ViewHolder的作用 ?
  5. ViewHolder里面需要持有什么对象?
  6. 做ViewHolder需要什么条件?
  7. 一个ListView会创建几个convertView以及几个ViewHolder的思考?
  8. getView其实主要是做啥?

HomeHolder抽取前思考答

  1. 为什么想到去抽取?

    1. 总是要创建ViewHolder
    2. 有重复代码
  2. convertView的作用 ?

    1. 减少view对象的创建
  3. ViewHolder是什么 ?
    1. 就是一个普通的类,成员变量有根布局里面的孩子对象.
  4. ViewHolder的作用 ?
    1. 减少孩子对象的创建,减少findViewById的调用
  5. ViewHolder里面需要持有什么对象?
    1. 持有根布局里面的孩子对象
  6. 做ViewHolder需要什么条件?
    1. 一个类持有根布局里面的孩子对象即可
    2. 其实只要能有根布局就可以了,有根布局就有了对应的孩子.孩子无非就是调用根布局的findViewById初始化即可
  7. 一个ListView会创建几个convertView以及几个ViewHolder的思考
    1. 如果一个屏幕正好显示6个itemView那么会创建6+1个convertView和6+1个ViewHolder
  8. getView其实主要是做啥?
    1. 决定根布局
    2. 得到数据
    3. 填充数据

相信您已经理解了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个多小时。由衷佩服自己,看完您记得关注本博客,或者点赞留下包括意见哦。

代码优化>>>Android ListView适配器三级优化详解的更多相关文章

  1. ListView复用和优化详解

    我们每一个Android开发人员对ListView的使用肯定是很熟悉的,然而多少人能真正的懂ListView的缓存机制呢,说白了就是ListView为了提高效率,而内部实现的一种优化,牺牲一点内存.而 ...

  2. Android listview addHeaderView 和 addFooterView 详解

    addHeaderView()方法:主要是向listView的头部添加布局addFooterView()方法:主要是向listView的底部添加布局 需要注意的是添加布局的时候应该添加从父容器开始添加 ...

  3. 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高

    第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...

  4. Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

    Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...

  5. Android Design Support Library使用详解

    Android Design Support Library使用详解 Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的And ...

  6. Android 之窗口小部件详解--App Widget

    Android 之窗口小部件详解--App Widget  版本号 说明 作者 日期  1.0  添加App Widge介绍和示例  Sky Wang 2013/06/27        1 App ...

  7. Android 之窗口小部件详解(三)  部分转载

    原文地址:http://blog.csdn.net/iefreer/article/details/4626274. (一) 应用程序窗口小部件App Widgets 应用程序窗口小部件(Widget ...

  8. Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能

    Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 我的本意是第二篇写Mob的shareSD ...

  9. Android项目刮刮奖详解扩展篇——开源刮刮奖View的制作

    Android项目刮刮奖详解(四) 前言 我们已经成功实现了刮刮奖的功能了,本期是扩展篇,我们把这个View直接定义成开源控件,发布到JitPack上,以后有需要也可以直接使用,关于自定义控件的知识, ...

随机推荐

  1. [LeetCode] Longest Harmonious Subsequence 最长和谐子序列

    We define a harmonious array is an array where the difference between its maximum value and its mini ...

  2. 【Swift】图文混排,ios开发中在textfield或textView中插入图片

    在ios开发中,我们一般都是在textfield或者textView中输入文字.当我们需要插入图片的时候其实也是很简单的 我们需要利用的textfield,textView的属性化文本,将图片以附件的 ...

  3. 认识JQuery,JQuery的优势、语法、多库冲突、JS原生对象和JQuery对象之间相互转换和DOM操作,常用的方法

    (一)认识JQuery  JQuery是一个JavaScript库,它通过封装原生的JavaScript函数得到一套定义好的方法    JQuery的主旨:以更少的代码,实现更多的功能 (二)JQue ...

  4. kmp——cogs 1570 乌力波

    1570. [POJ3461]乌力波 ★☆   输入文件:oulipo.in   输出文件:oulipo.out   简单对比 时间限制:1 s   内存限制:256 MB [题目描述] 法国作家乔治 ...

  5. [Codeforces 235B]Let's Play Osu!

    Description 题库链接 有一个长度为 \(n\) 的 "XO" 序列,其中第 \(i\) 个位置 "O" 出现的概率为 \(p_i\) .一个序列的价 ...

  6. [BJOI2006]狼抓兔子

    题目描述 现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的,而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形: ...

  7. [HNOI2004]L语言

    题目描述 标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的.现在你要处理的就是一段没有标点的文章. 一段文章T是由若干小写字母构成.一个单词W也是由若干小写字母构成.一个字典D是若干个单词的 ...

  8. POI SZP

    贪心: 初始所有点为白色,对于点i,若a[i]为白色则将其染成与i不同的颜色. 证明:若点i确定为白色,a[i]染白色也只能提供一个黑点,故a[i]染黑色不会差:若所有指向i的点均为黑色,则i只能是白 ...

  9. [bzoj1488][HNOI2009]图的同构——Polya定理

    题目大意 求两两互不同构的含n个点的简单图有多少种. 简单图是关联一对顶点的无向边不多于一条的不含自环的图. a图与b图被认为是同构的是指a图的顶点经过一定的重新标号以后,a图的顶点集和边集能完全与b ...

  10. ●BZOJ 3527 [Zjoi2014]力

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=3527 题解: FFT求卷积. $$\begin{aligned}E_i&=\frac ...