请描述baseadapter优化详解策略,为什么要这样

13963人阅读
Android(141)
& & & & Android中很多地方使用的是适配器(Adapter)机制,那我们就要好好把这个Adapter利用起来,并且用出自己的特色,来符合我们自行设计的需要喽~~~
& & & & 下面先上一个例子,是使用ViewHolder进行显示效率优化过的工程:
& & & &&package com.test.l
import java.util.ArrayL
import java.util.HashM
import java.util.L
import java.util.M
import android.app.AlertD
import android.app.ListA
import android.content.C
import android.content.DialogI
import android.os.B
import android.util.L
import android.view.LayoutI
import android.view.V
import android.view.ViewG
import android.widget.BaseA
import android.widget.B
import android.widget.ImageV
import android.widget.ListV
import android.widget.TextV
/* listView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到listView的长
* 度(这也是为什么在开始的第一张图特别的标出列表长度),然后根据这个长度,调用getView()逐
* 一绘制每一行。如果你的getCount()返回值是0的话,列表将不显示同样return 1,就只显示一行。
* 系统显示列表时,首先实例化一个适配器(这里将实例化自定义的适配器)。当手动完成适配时,必
* 须手动映射数据,这需要重写getView()方法。系统在绘制列表的每一行的时候将调用此方法。
* getView()有三个参数,position表示将显示的是第几行,covertView是从布局文件中inflate来的
* 布局。我们用LayoutInflater的方法将定义好的main.xml文件提取成View实例用来显示。然后
* 将xml文件中的各个组件实例化(简单的findViewById()方法)。这样便可以将数据对应到各个组件
* 上了。但是按钮为了响应点击事件,需要为它添加点击监听器,这样就能捕获点击事件。至此一个自定
* 义的listView就完成了,现在让我们回过头从新审视这个过程。系统要绘制ListView了,他首先获得
* 要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面
* 首先获得一个View(实际上是一个ViewGroup),然后再实例并设置各个组件,显示之。好了,绘制完
* 这一行了。那 再绘制下一行,直到绘完为止。在实际的运行过程中会发现listView的每一行没有焦点
* 了,这是因为Button抢夺了listView的焦点,只要布局文件中将Button设置为没有焦点就OK了*/
public class MyListView4 extends ListActivity {
private List&Map&String, Object&& mD
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mData = getData();
MyAdapter adapter = new MyAdapter(this);
setListAdapter(adapter);
private List&Map&String, Object&& getData() {
List&Map&String, Object&& list = new ArrayList&Map&String, Object&&();
Map&String, Object& map = new HashMap&String, Object&();
map.put(&title&, &G1&);
map.put(&info&, &google 1&);
map.put(&img&, R.drawable.i1);
list.add(map);
map = new HashMap&String, Object&();
map.put(&title&, &G2&);
map.put(&info&, &google 2&);
map.put(&img&, R.drawable.i2);
list.add(map);
map = new HashMap&String, Object&();
map.put(&title&, &G3&);
map.put(&info&, &google 3&);
map.put(&img&, R.drawable.i3);
list.add(map);
// ListView 中某项被选中后的逻辑
protected void onListItemClick(ListView l, View v, int position, long id) {
Log.v(&MyListView4-click&, (String) mData.get(position).get(&title&));
* listview中点击按键弹出对话框
public void showInfo() {
new AlertDialog.Builder(this).setTitle(&我的listview&)
.setMessage(&介绍...&)
.setPositiveButton(&确定&, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
}).show();
public final class ViewHolder {
public ImageV
public TextV
public TextV
public Button viewB
public class MyAdapter extends BaseAdapter {
private LayoutInflater mI
public MyAdapter(Context context) {
this.mInflater = LayoutInflater.from(context);
public int getCount() {
// TODO Auto-generated method stub
return mData.size();
public Object getItem(int arg0) {
// TODO Auto-generated method stub
public long getItemId(int arg0) {
// TODO Auto-generated method stub
public View getView(int position, View convertView, ViewGroup parent) {
// 显示优化(只要之前显示过的就可以不再再次从布局文件读取,直接从缓存中读取——ViewHolder的作用)
// 其实是setTag和getTag中Tag的作用
ViewHolder holder =
if (convertView == null) {// 如果是第一次显示该页面(要记得保存到viewholder中供下次直接从缓存中调用)
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.main, null);
// 以下为保存这一屏的内容,供下次回到这一屏的时候直接refresh,而不用重读布局文件
holder.img = (ImageView) convertView.findViewById(R.id.img);
holder.title = (TextView) convertView.findViewById(R.id.title);
= (TextView) convertView.findViewById();
holder.viewBtn = (Button) convertView
.findViewById(R.id.view_btn);
convertView.setTag(holder);
} else {// 如果之前已经显示过该页面,则用viewholder中的缓存直接刷屏
holder = (ViewHolder) convertView.getTag();
holder.img.setBackgroundResource((Integer) mData.get(position).get(
holder.title.setText((String) mData.get(position).get(&title&));
.setText((String) mData.get(position).get(&info&));
holder.viewBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
showInfo();
return convertV
& & & & 下面是将自定义的Adapter的构造方法定义为与系统的Adapter一致的工程,这样大家在实例化自定义的Adapter时可以按照实例化系统的Adapter的参数安排来进行哟~
& & & &&package com.test.l
import java.util.L
import java.util.M
import android.app.AlertD
import android.content.C
import android.util.L
import android.view.LayoutI
import android.view.V
import android.view.ViewG
import android.widget.BaseA
import android.widget.B
import android.widget.CheckB
import poundB
import android.widget.ImageV
import android.widget.TextV
import poundButton.OnCheckedChangeL
* @author Himi
public class MySimpleAdapter extends BaseAdapter {
private LayoutInflater mI
private List&Map&String, Object&&
private int layoutID;
private String flag[];
private int ItemIDs[];
public MySimpleAdapter(Context context, List&Map&String, Object&& list,
int layoutID, String flag[], int ItemIDs[]) {
Log.i(&TAG&,&构造方法&);
this.mInflater = LayoutInflater.from(context);
this.list =
this.layoutID = layoutID;
this.flag =
this.ItemIDs = ItemIDs;
public int getCount() {
// TODO Auto-generated method stub
return list.size();
public Object getItem(int arg0) {
// TODO Auto-generated method stub
public long getItemId(int arg0) {
// TODO Auto-generated method stub
public View getView(int position, View convertView, ViewGroup parent) {
convertView = mInflater.inflate(layoutID, null);
for (int i = 0; i & flag. i++) {//备注1
if (convertView.findViewById(ItemIDs[i]) instanceof ImageView) {
ImageView iv = (ImageView) convertView.findViewById(ItemIDs[i]);
iv.setBackgroundResource((Integer) list.get(position).get(
flag[i]));
} else if (convertView.findViewById(ItemIDs[i]) instanceof TextView) {
TextView tv = (TextView) convertView.findViewById(ItemIDs[i]);
tv.setText((String) list.get(position).get(flag[i]));
//...备注2
Log.i(&TAG&,&else&);
addListener(convertView);
return convertV
* 童鞋们只需要将需要设置监听事件的组件写在下面这方法里就可以啦!
* 别的不需要修改!
public void addListener(View convertView) {
((Button)convertView.findViewById(R.id.btn)).setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
new AlertDialog.Builder(LvSimpleAdapter.ma)
.setTitle(&自定义通用SimpleAdapter&)
.setMessage(&按钮成功触发监听事件!&)
Log.i(&TAG&,&Button&);
((CheckBox)convertView.findViewById(R.id.cb)).
setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
new AlertDialog.Builder(LvSimpleAdapter.ma)
.setTitle(&自定义通用SimpleAdapter&)
.setMessage(&CheckBox成功触发状态改变监听事件!&)
Log.i(&TAG&,&CheckBox&);
}& & & & 以上代码的注释都比较详细,而且前面的Blog中也对Adapter进行过讲解,这边就不再说一遍啦~
& & & & 其中第二段代码中没有使用ViewHolder对显示效率进行优化,大家可以参考第一段稍稍改动一下就OK了哈~
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:670735次
积分:6929
积分:6929
排名:第2208名
原创:97篇
转载:105篇
评论:218条
(1)(1)(4)(15)(6)(17)(41)(14)(23)(2)(40)(2)(36)BaseAdapter需实现的四个方法介绍
BaseAdapter需实现的四个方法介绍
& & & &android中的适配器(Adapter)是数据与视图(View)之间的桥梁,用于对要显示的数据进行处理,并通过绑定到组件进行数据的显示。
  BaseAdapter是Android应用程序中经常用到的基础数据适配器的基类,它实现了Adapter接口。其主要用途是将一组数据传到像ListView、Spinner、Gallery及GridView等UI显示组件进行显示。我们经常使用的ListView 的adapter(即SimpleAdapter),是继承自BaseAdapter基类的。BaseAdapter是一个基类,没有实现绑定数据的功能。而SimpleAdapter实现了基本控件的绑定,如TextView,Button,ImageView等。并已经为我们实现好了数据优化工作。
  这些适配器使用相同组件动态绑定数据的方式进行优化。为什么需要优化呢?因为如果我们有上亿个(较多个)项目要显示怎么办?为每个项目创建一个新视图?这不可能,因为内存有限制。实际上Android为你缓存了视图。Android中有个叫做Recycler的构件,下图是他的工作原理:
  如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中。其实我的理解Recyler就是一个队列,用来存储不在屏幕范围内的item,如果item滚出屏幕范围,那么就入队,这里的滚出是完全滚出,即边界等也要完全滚出。如果新的item要滚进来,那么android系统的framework就会查看Recyler是否含有可以重复使用的View,如果有那么就重新设置该View 的数据源,然后显示,即出队。那么这么多的item其实只需要占用一定空间的内存,这个内存大小是多少呢?我的感觉是手机屏幕所包含的item的个数,再加上1,然后乘以每个item占用的内存。但是最后我发现是加上2.可能是为了使得缓存更大吧。。。。但是为什么加上2,大家应该理解,如果你不理解,那你就把滚动list的过程好好想一想。那个队列无非就是一个缓存罢了,因为我们的目的是通过那个缓存来重复使用那些已经创建的View。
& & & &在我们需要定制自己的适配器时(adapter)时我们就需要继承BaseAdapter抽象类,除了必要的构造函数外有几个函数是必须要重载的,它们是:
&&&&public abstract int&getCount()
&&& public abstract Object&getItem(int position)&&&
&&&&public abstract long&getItemId(int position)
&&&&public abstract View&getView(int position, View convertView, ViewGroup parent)
& &四个函数的主要功能:
&& 1.先来看一下简略流程图(不一定非常精确,但大致流程是这样):
由流程图可以看到getCount()和getView()的作用:
&&&& 1.getCount()决定了我们将要绘制的资源数,当然这个数目不能大于资源的总数,不过却可以小于资源的总数。例如我们adapter管理的资源总共有10个,而我们通过getCount()返回的只有6个,那么最终GridView绘制出来的将只有6个。
& & &2.getView():通过传入的参数position,加工成我们想要的View并返回,最终共GridView使用
& & &3.getItemId()该方法的返回值决定第position处的列表项的ID,某些方法(如onclicklistener的onclick方法)有id这个参数,而这个id参数就是取决于getItemId()这个返回值的。如:public
void onItemClick(AdapterView&?& parent, View view,int position, long id)&
& &4.getItem():getItem方法不是在Baseadapter类中被调用的,而是在Adapterview.getItemAtPosition(position)&中被调用的。getItemAtPosition(position)
是在setOnItemClickListener、setOnItemLongClickListener、setOnItemSelectedListener的点击选择处理事件中方便地调用来获取当前行数据的。
getItem()和getItemId()有什么用呢?我们知道,GridView将View以方格的方式展示出来之后是要与用户交互的(比如点击某张图片),为了更好的相应用户的动作,我们就要用到这两个函数了。
我的热门文章
即使是一小步也想与你分享android(199)
Android常见问题(90)
概要:使用Adapter的注意事项与优化方案
本文的例子都可以在结尾处的示例代码连接中看到并下载,如果喜欢请star,如果觉得有纰漏请提交issue,如果你有更好的点子可以提交pull request。
本文的示例代码主要是基于这个库编写的,若你有其他的技巧和方法可以参与进来一起完善这篇文章。
固定连接:
故事发生在一个月黑风高的夜晚,那时候我知道了android世界中的一个恐怖的存在————adapter。看着无数的精英们随意地使用这强大的武器,造就了很多美轮美奂的应用时,我就暗自发誓,我一定要获得这个利器!
通过自身不断的修行,我慢慢的发现,那些系统提供的adapter没太大作用,仅仅在我写demo时才会去图省事用一用(确实挺省事的)。而在实际的开发中,我一直都在用baseAdapter。正所谓万变不离其宗,有了baseAdapter后,我就拥有了七十二变化,杀得了白骨精,斗得过二郎神,完全可以搞定设计和产品提出的需求。
直到有一天,当我看到一个包含着四五种类型,有着不同交互的item的Adapter后,一种苍白的无力感紧紧的抓住了我,我没办法一下子了解其中的所有的逻辑,在上面进行优化和添加需求都很痛苦。在一团乱麻的代码中,任何优化方案和设计模式都变得没有意义,我只能一边吐槽一边在上面写着让其更加恶心和难以维护的代码。
后来,当我教我的学弟适配器方面的内容时,我说出了上面的经历。之后他的话让我陷入了深深的沉思之中。
“adapter到底是view还是controler呢?”他说。我沉默良久,却不能给出一个回答。
是啊,adapter这个概念看似简单,但却模糊不清,于是我开始寻找这个问题的答案。 终于在菩提树下沉思三天三夜后,得出了以下答案:
adapter既不是v也不是c
adapter就是一个数据和视图的绑定装置
数据、视图、绑定器三者的代码不应该缠在一起
思考过后,我就开始进行adapter的研究,希望最后完成一个库来的进行调用。
数据不应知道adapter和view的存在Adapter不应该成为一个独立的类,而是fragment、activity中的内部类
Adapter能支持多种item类型,能仅改动几行代码即可添加一个新的item
Adapter能对自身内部的item进行复用,不需要我们手动判断convertView是否为null
Adapter中对findviewById方法应该有优化,类似于ViewHolder
item能独立的处理自身的逻辑和点击事件,自身应具有极高的独立性和可维护性
item自身的setListener应仅设置一次,而不用在getView时重复建立
Adapter应该提供item的局部刷新功能
Listview的adapter应该在稍微修改后支持recyclerView,方便未来的过渡
和适配器不太相关的需求:
1. 如果item中要加载网络或本地图片,请在线程中加载,加载好后切回主线程显示
2. 在快速滑动时不加载网络图片或停止gif图的播放
3. 判断item已经显示的数据和需要显示的新数据是否不同,如果不同就更新,否则不更新
4. 如果一个item过于复杂,可以将其拆分成多个小的item
5. 如果item中文本过多,可以采用textview的预渲染方案
6. 如果发现item因为measure任务过重,而出现掉帧,则需要通过自定义view来优化此item。这种方案适用于,某个item在应用中频繁使用的情形。
三、解决方案
1. 数据应不知道对adapter和view的存在
view肯定需要知道设置给自己的数据是什么,adapter肯定要知道view和数据是什么,但数据应该对其他的东西完全不知情。
数据的傻瓜化的好处有很多,如果这么做了,我们甚至可以把网络层和解析的model放入java项目中,利用java工程的特性进行网络层快速的单元测试(这点在以后的文章中可能会涉及)。在这次的说明中,我们建立这样一个超级简单的数据模型:
public class DemoModel {
public String
public String
* 这个model中决定数据类型的字段
public Object getDataType() {
它就是一个POJO,没有任何特别之处,它完全不知道其他对象的存在。
2. Adapter是内部类
回看我们之前写的代码,经常会把adapter中写入很多逻辑和判断的语句,现在如果想要让adapter变成一个仅仅用于与视图进行绑定的简单工具,那么它里面就不应该有不属于它的操作,它应该仅仅做到的是接收数据,然后绑定视图。因此,将其作为内部类是完全可行的,并且还会增加可读性。就像下面这样:
listView.setAdapter(new CommonAdapter&DemoModel&(data) {
public AdapterItem&DemoModel& getItemView(Object type) {
return new TextItem();
不要在onCreat或者初始化listview后直接设置adapter,adapter的初始化应该在得到数据之后。比如你的数据来自网络,你就应该在网络返回结果成功后再调用setAdapter。
这个通用的适配器接收了data,然后在getItemView的时候返回想要的item对象。需要说明的是,data的类型是list&T&,用来适应各种类型的数据源。这里的item对象,等会咱们再谈,先来看看如果是多种类型item该咋办。
3. Adapter能支持多种item类型
listView.setAdapter(new CommonAdapter&DemoModel&(data, 3) {
public Object getItemViewType(DemoModel demoModel) {
return demoModel.getDataType();
public AdapterItem&DemoModel& getItemView(Object type) {
switch ((String) type) {
case &text&:
return new TextItem();
case &button&:
return new ButtonItem();
case &image&:
return new ImageItem();
return new TextItem();
在这个例子中,我们可以看到它能支持3种类型的item。只不过需要在构造方法中传入data和item的种类。并且实现getItemViewType这个方法,这个方法用来返回model中用来做判断的类型参数。
如果对比第二点的单个类型的适配器代码看来,从上面的代码改到现在的代码,只需要增加(注意:这里不是修改)短短几行代码,而不是像以前那样又要修改适配器又要修改getView的方法体。通过“增加”代码的方式来扩展需求比通过“修改”代码扩展需求要稳定和安全很多,因为不会动原有的逻辑。如果来了新的需求,让你多支持一个item类型,你只需要在switch-case语句块中新增一个case就行,简单且安全。
在做这样的操作时,请务必写上default这个条件,以免出现不可预知的错误。毕竟来自服务器的数据也是不能完全相信的。
4. Adapter能对自身内部的item进行自动复用
我们之前对adapter的优化经常是需要在getView中判断convertView是否为null,如果不为空就不new出新的view,这样来实现item复用。但我们的目的是希望这个库能帮我们自动化的做到这点,于是赶快看看上面已经出现多次的AdapterItem是个什么东西吧。
* adapter的所有item必须实现此接口.
* 通过返回{@link #getLayoutResId()}来自动初始化view,之后在
* {@link #onBindViews(View)}中就可以初始化item的内部视图了。&br&
public interface AdapterItem&T& {
* @return item布局文件的layoutId
@LayoutRes
int getLayoutResId();
* 初始化views
void onBindViews(final View root);
* 设置view的参数
void onSetViews();
* 根据数据来设置item的内部views
* @param model
数据list内部的model
* @param position 当前adapter调用item的位置
void onUpdateViews(T model, int position);
看到这我不禁失望了起来,这是什么鬼!一个接口,没啥东西的接口。当我冷静下来,开始思考这个接口有什么用,反正我不知道它和复用item有什么关系。
它就像一个表格:
getLayoutResId
你这个item的布局文件是什么
返回一个R.layout.xxx
onBindViews
在这里做findviewById的工作吧
btn = findViewById(R.id.xx)
onSetViews
在这里初始化view各个参数吧
setcolor ,setOnClickListener...
onUpdateViews
数据更新时会调用(类似getView)
button.setText(model.text)
其实这里就是适配器中view的几个过程,首先初始化布局文件,然后绑定布局文件中的各个view,接着进行各个view的初始化操作,然后在数据更新时在updateViews中进行更新数据的操作。
分析完毕后,我去源码里面翻了一下,终于发现了这个库对item复用的优化:
LayoutInflater mI
public View getView(int position, View convertView, ViewGroup parent) {
// 不重复创建inflater对象,无论你有多少item,我都仅仅创建一次
if (mInflater == null) {
mInflater = LayoutInflater.from(parent.getContext());
AdapterItem&T&
if (convertView == null) {
// 当convertView为null,说明没有复用的item,那么就new出来
item = getItemView(mType);
convertView = mInflater.inflate(item.getLayoutResId(), parent, false);
convertView.setTag(R.id.tag_item, item);
// 调用bindView进行view的findview。
// 可以看到仅仅是新new出来的view才会调用
item.onBindViews(convertView);
// findview后开始setView。将绑定和设置分离,方便整理代码结构
item.onSetViews();
// 如果这个item是可以复用的,那么直接返回
item = (AdapterItem&T&) convertView.getTag(R.id.tag_item);
// 无论你是不是复用的item,都会在getView时触发updateViews方法,更新数据
item.onUpdateViews(mDataList.get(position), position);
return convertV
通过上面的注释得出,这个库自动帮我们做了很多优化,我们只需在对应的方法中写上对应的语句即可。
这个库最根本的方法就是这一段,所以你只需要明白这一段代码做的事情,即使你以后在使用这个库时遇到了什么问题,你都可以不必惊慌,因为你掌握了它的原理。其实用不用一个第三方库我有下面的几点建议:
如果你不了解其内部的实现,那么尽可能少用。因为出了问题无从查找
如果你遇到一个很好的库,不妨看下内部的实现,既能学到东西,又可以在以后出问题的时候快速定位问题
如果遇到复杂的库,比如网络和图片库。全部知道其原理是很难的,也需要成本,而你自己写也是不现实的,所以需要挑选很有名气的库来用。这样即使遇到了问题,也会有很多资料可以搜到
不要抵触国人的库,国人的库更加接地气,说不定还更好,还可以更加方便的提出issue。我就觉得AndroidEventBus就写的挺好的!
5. Adapter中对findviewById方法应该有优化,类似ViewHolder
这点在第四点中我已经进行了详细的说明和分析,我们只需要在bindViews中写findview方法即可让这个库自动实现优化工作。我在实际使用中结合了databinding这个库进行编写,一行代码解决问题:
private DemoItemImageBinding
public void onBindViews(View root) {
b = DataBindingUtil.bind(root);
传统的话就是这样:
TextView textV
public void onBindViews(View root) {
textView = (TextView) root.findViewById(R.id.textView);
我强烈建议使用dataBinding做视图的绑定操作,因为它自动化程度很高,再也不用看到findviewById这种方法了。而且当你更改了view的id或者类型,它也会自动识别,大大增加了可维护性。
目前我看到它对于mvvm的支持力度在idea层面遇到了无法进行代码提示的问题,所以我不建议将其用于视图绑定之外的数据绑定上。
6. item能独立的处理自身的逻辑和点击事件,自身应具有极高的独立性
举个例子,你的item就是一个textView:
&?xml version=&1.0& encoding=&utf-8&?&
&layout xmlns:android=&/apk/res/android&&
android:id=&@+id/textView&
android:layout_width=&match_parent&
android:layout_height=&wrap_content&
android:layout_gravity=&center_horizontal&
android:gravity=&center&
android:text=&New text&
android:textSize=&30sp&
现在item类应该这么写:
public class TextItem implements AdapterItem&DemoModel& {
public int getLayoutResId() {
return R.layout.demo_item_
TextView textV
public void onBindViews(View root) {
textView = (TextView) root.findViewById(R.id.textView);
public void onSetViews() {}
public void onUpdateViews(DemoModel model, int position) {
textView.setText(model.content);
这个item对于被哪个适配器绑定是不知情的,各个数据的更新应该全部由自己完成。现在,你可以将它放入不同的界面,只需要给他同样的数据模型即可。当然,我知道这种一个item被多个页面用的情形中还可以做更多的优化,比如在RecyclerView设置全局的缓存池等等。
我强烈建议不要用itemOnListener做点击的判断,而是在每个item中做判断。这样的好处就是item自身知道自己的所有操作,而listview仅仅做个容器。现在RecyclerView的设计思路也是如此的,让item独立性增加,也更加符合我们现实生活中的逻辑。
我们生活中的认知是这样的:这个列表中支持普通的微博和官方推荐的广告,如果用户点了普通的微博则进入微博,如果点了广告就进入广告页面。所以点击的是微博还是广告应该是自己判断的,而不是交给容器来做。
7. item自身的setListener应仅设置一次,不用在getView时重复建立
上面也说到了,我们之前会图省事在listview的getView中随便写监听器,以至于出现了监听器爆炸的现象。现在,我们在setViews中写上监听器,这样就只建立需要的监听器对象,降低资源的浪费。
* 优化小技巧:这个就等于一个viewHolder,用于复用,所以不会重复建立对象
public class ButtonItem implements AdapterItem&DemoModel& {
private int mP
public int getLayoutResId() {
return R.layout.demo_item_
private DemoItemButtonBinding
public void onBindViews(final View root) {
b = DataBindingUtil.bind(root);
* 优化小技巧:在这里直接设置按钮的监听器。
* 因为这个方法仅仅在item建立时才调用,所以不会重复建立监听器。
public void onSetViews() {
// 这个方法仅仅在item构建时才会触发,所以在这里也仅仅建立一次监听器,不会重复建立
b.button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(b.getRoot().getContext(), &pos = & + mPosition, Toast.LENGTH_SHORT).show();
public void onUpdateViews(DemoModel model, int position) {
// 在每次适配器getView的时候就会触发,这里避免做耗时的操作
mPosition =
b.button.setText(model.content);
8. Adapter应该提供item的局部刷新功能
这个功能在recyclerView中就已经提供了,我就不废话了。推荐直接使用recyclerView来做列表。在react-native的源码中我也看到了对recyclerView的支持。网上流传比较多的是用下面的代码做listview的单条刷新:
private void updateSingleRow(ListView listView, long id) {
if (listView != null) {
int start = listView.getFirstVisiblePosition();
for (int i = start, j = listView.getLastVisiblePosition(); i &= i++)
if (id == ((Messages) listView.getItemAtPosition(i)).getId()) {
View view = listView.getChildAt(i - start);
getView(i, view, listView);
其实就是手动调用了对应position的item的getView方法,个人觉得不是很好,为何不直接使用recyclerView呢?
9. Listview的adapter应该在稍微修改后支持recyclerView
如今recyclerView大有接替listview的趋势,我们自然要关心如何从的listview的适配器切到recyclerView的适配器。要知道listview的适配器和recyclerView的适配器的写法是不同的。
上面给出的例子都是listview的写法,我在这里在引用一下:
listView.setAdapter(new CommonAdapter&DemoModel&(data) {
public AdapterItem&DemoModel& getItemView(Object type) {
return new TextItem();
换成recyclerView的适配器应该需要改很多步吧?
不!不要998,不要98,改一行代码早回家~
recyclerView.setAdapter(new CommonRcvAdapter&DemoModel&(data) {
public AdapterItem&DemoModel& getItemView(Object type) {
return new TextItem();
注意到了么,就换了一个适配器的类名和容器名,其余的都没变,这才是我们想要的!
四、解决和Adapter不太相关的需求
1. 如果item中要加载图片,请在线程中加载,加载好了后切回主线程显示
有了RxAndroid这都不是事,当然一般的图片框架也会做这点。如果你使用的图片框架中没有做这样的处理,请务必加上!
2. 在快速滑动时不加载网络图片或停止gif图的播放
这个在QQ空间和微信朋友圈详情页中很常见,这个工作我仍旧希望交给图片加载框架做,而不是手动处理。因为手动处理对程序员的懒惰程度和知识水平有要求,所以还是交给库做放心。如果你的库没有做这样的处理,可以参考中的实现方法。
核心代码:
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_IDLE:
imageLoader.resume();
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
if (pauseOnScroll) {
imageLoader.pause();
case OnScrollListener.SCROLL_STATE_FLING:
if (pauseOnFling) {
imageLoader.pause();
if (externalListener != null) {
externalListener.onScrollStateChanged(view, scrollState);
3. 判断item已有的数据和新数据是否不同
如果是加载图片,我还是希望你去看看你用的图片框架有没有做这样的优化,如果有就请放心,如果没有,那么请自己在框架中配置或者写工具类。
这里的情况不仅仅适用于图片也适用于其他的数据,如果你的item中文字很多,经常有几百个文字。那么也可以先判断要显示的文字和textview中已经有的文字是否一致,如果不一致再调用setText方法。下面是一个例子:
* @tips 优化小技巧:对于图片这样的对象,我们先判断要加载的图片是不是之前的图片,如果是就不重复加载了
* 这里为了演示方便没从网络加图,所以url是用int标识的,一般情况下都是用string标识
* 这里仅仅是用图片做个说明,你完全可以在textview显示文字前判断一下要显示的文字和已经显示的文字是否不同
public void updateViews(DemoModel model, int position) {
if (b.imageView.getTag() != null) {
mOldImageUrl = (int) b.imageView.getTag();
int imageUrl = Integer.parseInt(model.content);
if (mOldImageUrl == 0 && mOldImageUrl != imageUrl) {
Log.d(ImageItem.class.getSimpleName(), &update image---------&&);
b.imageView.setTag(imageUrl);
b.imageView.setImageResource(imageUrl); // load local image
4. 如果一个item过于复杂,可以将其拆分成多个小的item
关于这点是facebook提出的优化技巧,后来我了解到ios本身就是这么做的。我会找机会了解一下ios的实现机制,然后看看能不能放入这个库里面,方便android们使用。
如图所示,这个item很复杂,而且很大。当你的item占据三分之二屏幕的时候就可以考虑这样的优化方案了。右图说明了将一个整体的item变成多个小item的效果,在这种拆分后,你会发现原来拆分后的小的item可能在别的界面也用到了,可以在写其他需求的时候也用一下,这就出现了item模块化的思想,总之是一个挺有意思的优化思路。
详细的文章(中文)请参考:,十分感谢作者的分享和翻译~
5. 如果item中文本过多,可以采用textview的预渲染方案
如果你是做bbs或者做新闻的,你会发现item中会有大量的文字。textview其实是一个很基本但不简单的view,里面做了大量的判断和处理。当你有心想要优化textview的时候,你会发现在我们知道这个item中textview的宽度和文字大小的情况下可以把初始化的配置做个缓存,每个textview只需要用这个配置好的东西进行文字的渲染即可。
Instagram(现已在facebook旗下)分享了他们是如何优化他们的TextView渲染的效率的,在国内有作者也专门写了一篇文章来说明实际的原理和最终的效果,。
我之后在github上问他后续的工作安排,他回答到准备做好一个优化textview的库并放出,希望到时候能帮助到大家。
下面是通过优化得到的结果:&
这里测试的机器是MX3,左侧是直接使用StaticLayout的方案,右侧是系统的默认方案,Y轴是FPS,可以看出来,使用优化之后的方案,帧率提升了许多。
6. 通过自定义viewGroup来优化item,从而减少重复的measure
facebook的工程师讲解了他们对上面这个布局的优化策略,内容翔实,是个很好的分享。中文翻译版本:
感谢大家阅读到最后,请在阅读的时候多多留心文章中穿插的注意事项。如果你有其他的优化适配器和列表控件的方法也请多多指出,如果你觉得这个库不错,请去尝试。
探索无止境,优化没底线,我还是希望能有库在库中做好很多的优化操作,降低对程序员的要求,最终希望谁都可以写代码。简单编程,快乐生活。
本文的完成离不开朋友们的支持和帮助,感谢:@MingleArch、豪哥的批评和建议。
示例代码下载:
@天之界线2010
参考文章:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1783029次
积分:18590
积分:18590
排名:第306名
原创:179篇
转载:863篇
译文:44篇
评论:195条
(1)(1)(1)(2)(5)(1)(1)(4)(12)(15)(19)(9)(3)(9)(3)(2)(7)(5)(1)(3)(14)(24)(20)(28)(14)(21)(19)(36)(26)(18)(21)(35)(49)(23)(60)(38)(33)(51)(49)(58)(44)(67)(31)(44)(70)(90)(1)}

我要回帖

更多关于 baseadapter详解 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信