代码优化>>>Android ListView适配器三级优化详解
转载本专栏每一篇博客请注明转载出处地址,尊重原创。此博客转载链接地址:点击打开链接 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;
}
这里我们主要 把重点放在 Adapter 和 ViewHolder 的抽取封装,首先简单分析其逻辑组成:
1.首先看到我自定义的 HomeAdapter 继承的是 BaseAdapter 。继承的四个方法中,前三个:getCount 、getItem 、getItemId 看的出来方法及其简单,
但是getView方法中步骤略复杂,首先梳理清楚方法里的逻辑,才好进一步的封装:
(1)加载布局文件,布局转换(xml —> view)
(2)初始化控件(finViewById)
(3)给ViewHolder设置标记(setTag),便于下次复用
(4)给控件设置数据
那么就开始对市面上60%的项目中的封装方法,进行普通封装。
封装一》》》普通封装:方式,保留getView(),getCount 、getItem 、getItemId 先封装。
方法:抽取类名: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父类的,他把以前孩子自己要写的getCount 、getItem 、getItemId方法帮孩子完成,孩子简写三大方法。同时,在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适配器三级优化详解的更多相关文章
- ListView复用和优化详解
我们每一个Android开发人员对ListView的使用肯定是很熟悉的,然而多少人能真正的懂ListView的缓存机制呢,说白了就是ListView为了提高效率,而内部实现的一种优化,牺牲一点内存.而 ...
- Android listview addHeaderView 和 addFooterView 详解
addHeaderView()方法:主要是向listView的头部添加布局addFooterView()方法:主要是向listView的底部添加布局 需要注意的是添加布局的时候应该添加从父容器开始添加 ...
- 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高
第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...
- Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)
Android XML shape 标签使用详解 一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...
- Android Design Support Library使用详解
Android Design Support Library使用详解 Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的And ...
- Android 之窗口小部件详解--App Widget
Android 之窗口小部件详解--App Widget 版本号 说明 作者 日期 1.0 添加App Widge介绍和示例 Sky Wang 2013/06/27 1 App ...
- Android 之窗口小部件详解(三) 部分转载
原文地址:http://blog.csdn.net/iefreer/article/details/4626274. (一) 应用程序窗口小部件App Widgets 应用程序窗口小部件(Widget ...
- Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能
Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 我的本意是第二篇写Mob的shareSD ...
- Android项目刮刮奖详解扩展篇——开源刮刮奖View的制作
Android项目刮刮奖详解(四) 前言 我们已经成功实现了刮刮奖的功能了,本期是扩展篇,我们把这个View直接定义成开源控件,发布到JitPack上,以后有需要也可以直接使用,关于自定义控件的知识, ...
随机推荐
- 字符串的一些常用方法 string
##字符串## 字符串: 由0个或多个字符组成,被成对的英文单引号或双引号包含起来的. 字符编码: 每一个字符在计算机存储的编号. 计算机会保存有一套或几套用于标注编号与字符对应关系的字典.(字符集) ...
- [SDOI2009]虔诚的墓主人
题目描述 小W是一片新造公墓的管理人.公墓可以看成一块N×M的矩形,矩形的每个格点,要么种着一棵常青树,要么是一块还没有归属的墓地. 当地的居民都是非常虔诚的基督徒,他们愿意提前为自己找一块合适墓地. ...
- hdu 1542 线段树扫描(面积)
Atlantis Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Su ...
- [bzoj4868][Shoi2017]期末考试
来自FallDream 的博客,未经允许,请勿转载,谢谢. 有n位同学,每位同学都参加了全部的m门课程的期末考试,都在焦急的等待成绩的公布.第i位同学希望在第ti天或之前得知所.有.课程的成绩.如果在 ...
- [APIO2013]
A.机器人 题目大意:给定一个n*m的地图,有一些障碍物和k个机器人,你每次可以选择一个机器人往任意一个方向推,遇到转向器会转向,两个编号相邻的机器人可以合并,求最少推多少次可以全部合并. $n,m\ ...
- Visual Studio 2015 无法命中断点
新安装操作系统后发现,vs2015无法命中断点. 在项目中设置生成调试信息:FULL 即可.
- Python Django rest framework
本节内容 Django rest framework 安装 Django rest framwwork 环境配置 简单举例说明 Django中使用 rest framework 1.1 安装 Djan ...
- 上传本地项目到Github
进入要上传的本地文件夹,右键打开Git Bash Here,然后进行以下步骤: 1.在命令行中,输入"git init",使Test文件夹加入git管理: 2.输入"gi ...
- .net 导入excel数据
using System; using System.Data; using System.Data.OleDb; using System.Data.SqlClient; using System. ...
- kindeditor配合requirejs使用时,ready失效
KindEditor官方的文档在使用KindEditor时是这样的: KindEditor.ready(function(K)) { K.create('#editor_id'); } 使用了自己提供 ...