如下图,加入现在有一个这样的需求图,你会怎么做?作为一个初学者,之前我都是直接用SimpleAdapter结合一个Item的布局来实现的,感觉这样实现起来很方便(基本上一行代码就可以实现),而且也没有觉得有什么不好的。直到最近在慕课网上看到鸿洋大神讲的“机器人小慕”和“万能适配器”两节课,才对BaseAdapter有所了解。看了鸿洋大神的课程之后,我又上网搜了几个博客,也看了一些源码和文档,于是打算写一个帖子来记录一下自己的学习历程。

  在今天的帖子中,我们从一个最基本的实现BaseAdapter的适配器开始,先介绍ListView性能优化(convertView结合ViewHolder类),再将其封装起来,最后达到可以像SimpleAdapter一样可以一行代码搞定一个ListView的数据绑定。

  总结一下,本帖子要实现的功能:

  • 为一个继承自BaseAdapter的原始的Adapter添加ViewHolder,达到缓存的功能
  • 将优化后的ListView进行封装,实现一行代码为ListView绑定数据

一、最原始的适配器类的实现

  最原始的思想就是常见一个继承自BaseAdapter的适配器类,在getView()方法中找到子View的布局,获取到子View中的控件,再为其绑定数据。简略代码如下:

 @Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = inflater.inflate(R.layout.sideworks_main_userlist_item, parent, false);
ImageView userPhoto = (ImageView) convertView.findViewById(R.id.find_listitem_photo);
TextView userName = (TextView) convertView.findViewById(R.id.find_listitem_name);
User user = userList.get(position);
userPhoto.setImageResource(user.getPhotoRes());
userName.setText(user.getUserName());
return convertView;
}

  这样写代码理论上是没有问题的,但是,看过源码的人都知道,convertView这个参数指的是 The old view to reuse, if possible. 也就是说,convertView是一个以前用过的子View,如果它存在的话,就可以复用它,即只要ListView中存在一个布局和这个子View一样的子View,那么那个子View就可以复用这个convertView。这样就有了一个缓存的机制,也就是靠这个机制,我们可以达到ListView性能优化的目的。

二、convertView结合ViewHolder类实现ListView性能优化

  先看代码。

     @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
convertView = inflater.inflate(R.layout.sideworks_main_userlist_item, parent, false);
holder = new ViewHolder();
holder.userPhoto = (ImageView) convertView.findViewById(R.id.find_listitem_photo);
holder.userName = (TextView) convertView.findViewById(R.id.find_listitem_name);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
User user = userList.get(position);
holder.userPhoto.setImageResource(user.getPhotoRes());
holder.userName.setText(user.getUserName());
return convertView;
} /**
* 存储以前用过的View中的控件的类
*/
class ViewHolder {
ImageView userPhoto;
TextView userName;
}

  从代码中我们可以看到,和最开始的代码相比较,现在的代码多了一个内部类ViewHolder,这个类就是用来存储子View中的所有控件的。在Adapter的getView()方法中,我们首先判断convertView是否存在,如果不存在则说明这是第一次使用这个子View,此时我们就需要新建convertView和ViewHolder,找到ViewHolder中的所有控件,方便后面绑定数据,最后将ViewHolder存储到convertView缓存中;如果convertView存在,则说明以前已经用过一次这个子View,此时我们只需要从convertView的缓存中将ViewHolder去出来即可。得到ViewHolder实例之后,我们再根据List中的数据为子View中的所有控件绑定数据。

  从理论上说,到此,ListView的优化已经结束,对于只有一个ListView的项目,这样已经是极限了。但是,如果一个项目中有100个ListView呢?如果有100个ListView,那么按照我们现在对ListView的优化程度,我们只能把Adapter复制粘贴100份,分别绑定100份数据,然后再绑到ListView上。这样是不是很愚蠢?可见,我们的“革命”尚未成功,仍需努力。下面,我们就来把这个Adapter封装起来。

三、封装适配器Adapter

  我们的最终目的是要把ListView绑定数据的代码尽可能多的封装起来,尽可能达到一行代码为ListView绑定数据。要达到这个目标,我们需要克服以下几个困难:

  • 对于不同的ListView,其ViewHolder不可能相同,因此我们需要一个可以适配所有布局的ViewHolder
  • 对于不同的ListView,与之绑定的数据的类型不能确定,因此我们需要一个可以绑定所有数据类型的适配器

  基于这两个问题,我们先给出我们解决问题的思路:先写出ViewHolder类,再写适配器类。

  首先来看ViewHolder类。如上面所说的,我们需要一个可以适配所有布局的ViewHolder,因此,我们不能用TextView、ImageView等具体的控件类型,而是要用它们的父类View;其次,每个ListView只有一个ViewHolder,我们不可能对每个Item都用一个ViewHolder,因为这样不仅浪费了性能,而且也不合理,因此,我们使用单例模式来获得ViewHolder;另外,我们无法预测ViewHolder中都有几个控件,因此我们需要一个动态的数据结构来存储这些控件,我们选择的是SparseArray,它类似于HashMap,但其性能远远高于HashMap;最后,我们需要明确ViewHolder类中都干了什么:我们的ViewHolder类中主要进行对现在代码中getView()方法和ViewHolder类的封装。

  最重要的一点,我们需要在ViewHolder类中开放若干个方法,一旦有新的控件类型,我们就需要在ViewHolder中为这种控件声明一个绑定数据的方法,如TextView对应setTextToTextView()方法,ImageView对应setResourceToImageView()、setBitmapToImageView()方法等。最后,我们可以用链式变成的思想,把这些方法的返回值设置成ViewHolder类型,这样,我们就可以通过 holder.setTextToTextView(?,?).setResourceToImageView(?,?)... 来实现数据的绑定,也就是一行代码搞定数据绑定。

  说完了VeiwHolder类,让我们再来说说Adapter适配器类。因为是“万能适配器”,因此我为它起名为PowerfulAdapter。这个类中封装了BaseAdapter中的几个可以复用的方法,并且暴露出一个接口,让用户可以写具体的、每个Adapter都不同的代码。我们的思路是,将BaseAdapter中的getCount()、getItem()、getItemId()方法彻底封装起来,将getView()方法中获取ViewHolder的部分封装起来,把具体绑定数据的部分用一个抽象方法暴露给外界,让用户可以自定义绑定数据。

  另外一个问题是关于数据源的类型问题。和ViewHolder类中的存放控件的集合一样,我们并不能事先预测到用户会设置什么数据,所以我们用一个通用泛型<T>来代替,这样我们就可以绑定随意类型的数据了。

  这样一来,如果我们想要为一个ListView绑定数据,可以有两种方式:第一种,新建一个类继承PowerfulAdapter,并实现其中的抽象方法,为LsitView绑定数据;第二种,我们可以直接在Activity的onCreate()方法中实现绑定数据,直接用 listView.setAdapter(new PowerfulAdapter(){ ...... }); 来实现,“......”处写绑定数据的代码。

四、总结与代码

  首先先来总结一下:在这个DEMO中,我们主要解决了一下几个问题:

  • 为ListView绑定数据
  • 使用convertView结合ViewHolder实现了数据缓存
  • 对ViewHolder和Adapter进行了完美的封装,实现了一行代码为ListView绑定数据

  以下是代码。在代码之前,国际惯例,让我们先看一看DEMO的文件结构,如下图所示:

  以下是代码

主界面布局 activity_main.xml 文件中的代码:

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"> <ListView
android:id="@+id/find_main_lv_userlist"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@android:color/transparent"
android:divider="@android:color/darker_gray"
android:dividerHeight="1.0dip" /> </RelativeLayout>

主界面ListView的Item的布局 sideworks_main_userlist_item.xml 文件中的代码:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="6.0dip"> <ImageView
android:id="@+id/find_listitem_photo"
android:layout_width="45.0dip"
android:layout_height="45.0dip"
android:src="@mipmap/ic_launcher" /> <TextView
android:id="@+id/find_listitem_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10.0dip"
android:textColor="@android:color/black"
android:textSize="18.0sp"
android:textStyle="bold" /> </LinearLayout>

数据源实体类 User.java 文件中的代码:

 package com.itgungnir.entity;

 public class User {
private int photoRes;
private String userName; public User(){ } public User(int photoRes, String userName) {
this.photoRes = photoRes;
this.userName = userName;
} public int getPhotoRes() {
return photoRes;
} public void setPhotoRes(int photoRes) {
this.photoRes = photoRes;
} public String getUserName() {
return userName;
} public void setUserName(String userName) {
this.userName = userName;
}
}

数据源生成类 DataGenerater.java 中的代码:

 package com.itgungnir.tools;

 import com.itgungnir.activity.R;
import com.itgungnir.entity.User; import java.util.ArrayList;
import java.util.List; /**
* 生成User列表数据的工具类
*/
public class DataGenerater {
/**
* 获取UserList列表数据
*/
public static List<User> getUserList() {
List<User> userList = new ArrayList<User>();
for (int i = 0; i < 15; i++) {
userList.add(new User(R.mipmap.ic_launcher, "User Name " + (i + 1)));
}
return userList;
}
}

ListView的Item中控件的存放与处理类 ViewHolder.java 文件中的代码:

 package com.itgungnir.tools;

 import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView; public class ViewHolder {
private SparseArray<View> views; // 存储ViewHolder中所有的控件(SparseArray类似于HashMap但性能强于HashMap)
private View convertView; // 以前用过的布局 /**
* 构造函数,因为ViewHolder之后一个,因此声明为private私有类型,通过下面的getInstance()方法获取
*/
private ViewHolder(Context context, int layoutId, ViewGroup parent) {
this.views = new SparseArray<View>(); // 初始化控件集合
this.convertView = LayoutInflater.from(context).inflate(layoutId, parent, false); // 第一次用布局
this.convertView.setTag(this); // 初始化布局之后将一个ViewHolder放入布局中作为缓存
} /**
* 单例模式获取ViewHolder实例
*/
public static ViewHolder getInstance(View convertView, Context context, int layoutId, ViewGroup parent) {
if (convertView == null) { // 如果convertView不存在,则需要new一个ViewHolder并返回
return new ViewHolder(context, layoutId, parent);
} else { // 如果convertView存在,则只需要从convertView中将ViewHolder去出来返回即可
return (ViewHolder) convertView.getTag();
}
} /**
* 通过ID找到控件并返回
*/
private <T extends View> T getView(int viewId) {
View view = views.get(viewId); // 先尝试着从控件集合中取出控件
if (view == null) { // 如果view==null则说明控件没有加载到控件集合中,这时就需要手动查找控件并放到集合中
view = this.convertView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
} /**
* 设置一个为TextView设置文本的方法
*/
public ViewHolder setTextToTextView(int viewId,String text){
TextView textView = getView(viewId);
textView.setText(text);
return this;
} /**
* 设置一个为ImageView设置图片资源的方法
*/
public ViewHolder setResourceToImageView(int viewId,int resourceId){
ImageView imageView = getView(viewId);
imageView.setImageResource(resourceId);
return this;
} /**
* 开放一个返回convertView的方法,在适配器中会用到
*/
public View getConvertView() {
return this.convertView;
}
}

万能适配器类 PowerfulAdapter.java 文件中的代码:

 package com.itgungnir.tools;

 import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter; import java.util.List; public abstract class PowerfulAdapter<T> extends BaseAdapter {
private List<T> dataList;
private Context context;
private int layoutId; public PowerfulAdapter(Context context, List<T> dataList, int layoutId) {
this.dataList = dataList;
this.context = context;
this.layoutId = layoutId;
} @Override
public int getCount() {
return dataList.size();
} @Override
public T getItem(int position) {
return dataList.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = ViewHolder.getInstance(convertView, context, layoutId, parent);
convert(holder, dataList.get(position));
return holder.getConvertView();
} public abstract void convert(ViewHolder holder, T t);
}

PowerfulAdapter类的子类MyAdapter.java文件中的代码,用于第一种绑定数据的方式:

 package com.itgungnir.tools;

 import android.content.Context;

 import com.itgungnir.activity.R;
import com.itgungnir.entity.User; import java.util.List; public class MyAdapter extends PowerfulAdapter<User> { public MyAdapter(Context context, List<User> dataList, int layoutId) {
super(context, dataList, layoutId);
} @Override
public void convert(ViewHolder holder, User user) {
holder.setTextToTextView(R.id.find_listitem_name, user.getUserName())
.setResourceToImageView(R.id.find_listitem_photo, user.getPhotoRes());
}
}

主界面 MainActivity.java 中的代码:

 package com.itgungnir.activity;

 import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView; import com.itgungnir.entity.User;
import com.itgungnir.tools.DataGenerater;
import com.itgungnir.tools.MyAdapter;
import com.itgungnir.tools.PowerfulAdapter;
import com.itgungnir.tools.ViewHolder; public class MainActivity extends Activity {
private ListView userList; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
} private void initView() {
userList = (ListView) findViewById(R.id.find_main_lv_userlist);
/**
* 绑定数据:第一种方法,新建一个继承自PowerfulAdapter的Adapter类,在其中实现convert()方法
*/
userList.setAdapter(new MyAdapter(MainActivity.this, DataGenerater.getUserList(), R.layout.sideworks_main_userlist_item));
/**
* 绑定数据:第二种方法,直接使用PowerfulAdapter,并现场实现其convert()方法
*/
// userList.setAdapter(new PowerfulAdapter<User>(MainActivity.this, DataGenerater.getUserList(), R.layout.sideworks_main_userlist_item) {
// @Override
// public void convert(ViewHolder holder, User user) {
// holder.setTextToTextView(R.id.find_listitem_name, user.getUserName())
// .setResourceToImageView(R.id.find_listitem_photo, user.getPhotoRes());
// }
// });
}
}

最后奉上LZ的Git,欢迎各位大神批评指正:https://github.com/ITGungnir/TestPowerfulAdapter

Android之ListView性能优化——一行代码绑定数据——万能适配器的更多相关文章

  1. ym——Android之ListView性能优化

    转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持! Android之ListView性能优化 假设有看过我写过的15k面试题的朋友们一定知 ...

  2. Android之ListView性能优化——使用ConvertView和ViewHolder

    使用ConvertView和ViewHolder的优化是针对ListView的Adapter(BaseAdapter)的.这种优化的优点如下: 1)重用了ConveertView,在很大程度上减少了内 ...

  3. Android之ListView性能优化

    ListView滚动速度优化主要可以应用以下几点方法来实现: 1.使用Adapter提供的convertView convertView是Adapter提供的视图缓存机制,当第一次显示数据的时候,ad ...

  4. Android ListView性能优化实例讲解

    前言: 对于ListView,大家绝对都不会陌生,只要是做过Android开发的人,哪有不用ListView的呢? 只要是用过ListView的人,哪有不关心对它性能优化的呢? 关于如何对ListVi ...

  5. Android进阶笔记14:ListView篇之ListView性能优化

    1. 首先思考一个问题ListView如何才能提高效率 ? 当convertView为空时候,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象.当convertView不 ...

  6. Android进阶笔记11:ListView篇之ListView性能优化

    1. 首先思考一个问题ListView如何才能提高效率 ? 当convertView为空时候,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象.当convertView不 ...

  7. 【腾讯Bugly干货分享】跨平台 ListView 性能优化

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/FbiSLPxFdGqJ00WgpJ94yw 导语 精 ...

  8. 转——Android应用开发性能优化完全分析

    [工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.] 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉 ...

  9. Android 应用开发性能优化完全分析

    1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...

随机推荐

  1. 【python】sqlite使用

    官方文档:https://docs.python.org/2/library/sqlite3.html sqlite教程:http://www.runoob.com/sqlite/sqlite-del ...

  2. yum安装高版本mysql(5.5)

    1.导入第三方源webtatic rpm -Uvh http://repo.webtatic.com/yum/centos/5/latest.rpm 2.如果已安装低版本的mysql就删除 yum r ...

  3. 《R语言实战》读书笔记-- 第六章 基本图形

    首先写第二部分的前言. 第二部分用来介绍获取数据基本信息的图形技术和统计方法. 本章主要内容 条形图.箱型图.点图 饼图和扇形图 直方图和核密度图 分析数据第一步就是要观察它,用可视化的方式是最好的. ...

  4. 设置IIS会话过期时间

    打开默认网站----双击ASP--展开会话属性---更改超时时间-

  5. So easy Webservice 8.spring整合CXF 发布WS

    1.添加jar包(cxf的jar包中包含了spring的jar包),添加spring配置文件 2.web.xml中配置CXFServlet,过滤WS服务的地址 <!-- 配置CXFServlet ...

  6. CPU/ABI显示No system images installed for this target的解决方案

    CPU/ABI显示No system images installed for this target的解决方案 手动下载image http://www.androiddevtools.cn/ SD ...

  7. HDU2079选课时间(母函数)

    母函数的简单应用http://acm.hdu.edu.cn/showproblem.php?pid=2079 介绍见另一篇随笔HDU1028Ignatius and the Princess III( ...

  8. hdu 1281

    二分图,简单的模板题,不过题目比较难懂: 其中important chess就是删掉它不能够完美匹配,所以就枚举每一个可能删的棋子: 代码: #include <cstdio> #incl ...

  9. ExtJS5_自定义菜单1

    顶部和底部区域已经作好,在顶部区域有一个菜单的按钮,这一节我们设计一个菜单的数据结构,使其可以展示出不同样式的菜单.由于准备搭建的是一个系统模块自定义的系统,因此菜单也是自定义的,在操作员系统登录的时 ...

  10. hdu2304Electrical Outlets

    Problem Description Roy has just moved into a new apartment. Well, actually the apartment itself is ...