Listview加载的性能优化是如何实现的

在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。

listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次:

0、最原始的加载

1、利用convertView

2、利用ViewHolder

3、实现局部刷新

〇、最原始的加载

这里是不经任何优化的adapter,为了看起来方便,把listview的数据直接在构造函数里传给adapter了,代码如下:

private class AdapterOptmL extends BaseAdapter { private LayoutInflater mLayoutInflater; private ArrayList<Integer> mListData; public AdapterOptmL(Context context, ArrayList<Integer> data) { mLayoutInflater = LayoutInflater.from(context); mListData = data; } @Override public int getCount() { return mListData == null ? : mListData.size(); } @Override public Object getItem(int position) { return mListData == null ? : mListData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View viewRoot = mLayoutInflater.inflate(R.layout.listitem, parent, false); if (viewRoot != null) { TextView txt = (TextView)viewRoot.findViewById(R.id.listitem_txt); txt.setText(getItem(position) + ""); } return viewRoot; } }

一、利用convertView

上述代码的第27行在Eclipse中已经提示警告:

Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling

这个意思就是说,被移出可视区域的view是可以回收复用的,它作为getview的第二个参数已经传进来了,所以没必要每次都从xml里inflate。

经过优化后的代码如下:

@Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false); } if (convertView != null) { TextView txt = (TextView)convertView.findViewById(R.id.listitem_txt); txt.setVisibility(View.VISIBLE); txt.setText(getItem(position) + ""); } return convertView; }

上述代码加了判断,如果传入的convertView不为null,则直接复用,否则才会从xml里inflate。

按照上述代码,如果手机一屏最多同时显示5个listitem,则最多需要从xml里inflate 5 次,比AdapterOptmL0中每个listitem都需要inflate显然效率高多了。

上述的用法虽然提高了效率,但带来了一个陷阱,如果复用convertView,则需要重置该view所有可能被修改过的属性。

举个例子:

如果第一个view中的textview在getview中被设置成INVISIBLE了,而现在第一个view在滚动过程中出可视区域,并假设它作为参数传入第十个view的getview而被复用

那么,在第十个view的getview里面不仅要setText,还要重新setVisibility,因为这个被复用的view当前处于INVISIBLE状态!

二、利用ViewHolder

从AdapterOptmL0第27行的警告中,我们还可以看到编译器推荐了一种模型叫ViewHolder,这是个什么东西呢,先看代码:

private class AdapterOptmL extends BaseAdapter {

private LayoutInflater mLayoutInflater; private ArrayList<Integer> mListData; public AdapterOptmL(Context context, ArrayList<Integer> data) { mLayoutInflater = LayoutInflater.from(context); mListData = data; } private class ViewHolder { public ViewHolder(View viewRoot) { txt = (TextView)viewRoot.findViewById(R.id.listitem_txt); } public TextView txt; } @Override public int getCount() { return mListData == null ? : mListData.size(); } @Override public Object getItem(int position) { return mListData == null ? : mListData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false); ViewHolder holder = new ViewHolder(convertView); convertView.setTag(holder); } if (convertView != null && convertView.getTag() instanceof ViewHolder) { ViewHolder holder = (ViewHolder)convertView.getTag(); holder.txt.setVisibility(View.VISIBLE); holder.txt.setText(getItem(position) + ""); } return convertView; } }

从代码中可以看到,这一步做的优化是用一个类ViewHolder来保存listitem里面所有找到的子控件,这样就不用每次都通过耗时的findViewById操作了。

这一步的优化,在listitem布局越复杂的时候效果越为明显。

三、实现局部刷新

OK,到目前为止,listview普遍需要的优化已经做的差不多了,那就该考虑实际使用场景中的优化需求了。

实际使用listview过程中,通常会在后台更新listview的数据,然后调用Adatper的notifyDataSetChanged方法来更新listview的UI。

那么问题来了,一般情况下,一次只会更新listview的一条/几条数据,而调用notifyDataSetChanged方法则会把所有可视范围内的listitem都刷新一遍,这是不科学的!

所以,进一步优化的空间在于,局部刷新listview,话不多说见代码:

private class AdapterOptmL3 extends BaseAdapter { private LayoutInflater mLayoutInflater; private ListView mListView; private ArrayList<Integer> mListData; public AdapterOptmL3(Context context, ListView listview, ArrayList<Integer> data) { mLayoutInflater = LayoutInflater.from(context); mListView = listview; mListData = data; } private class ViewHolder { public ViewHolder(View viewRoot) { txt = (TextView)viewRoot.findViewById(R.id.listitem_txt); } public TextView txt; } @Override public int getCount() { return mListData == null ? 0 : mListData.size(); } @Override public Object getItem(int position) { return mListData == null ? 0 : mListData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false); ViewHolder holder = new ViewHolder(convertView); convertView.setTag(holder); } if (convertView != null && convertView.getTag() instanceof ViewHolder) { updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position)); } return convertView; } public void updateView(ViewHolder holder, Integer data) { if (holder != null && data != null) { holder.txt.setVisibility(View.VISIBLE); holder.txt.setText(data + ""); } } public void notifyDataSetChanged(int position) { final int firstVisiablePosition = mListView.getFirstVisiblePosition(); final int lastVisiablePosition = mListView.getLastVisiblePosition(); final int relativePosition = position - firstVisiablePosition; if (position >= firstVisiablePosition && position <= lastVisiablePosition) { updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position)); } else { //不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新 } } }

修改后的Adapter新增了一个方法 public void notifyDataSetChanged(int position) 可以根据position只更新指定的listitem。

局部刷新番外篇

在局部刷新数据的接口中,实际上还可以再干点事情:listview正在滚动的时候不去刷新。

具体的思路是,如果当前正在滚动,则记住一个pending任务,等listview停止滚动的时候再去刷,这样不会造成滚动的时候刷新错乱。代码如下:

private class AdapterOptmLPlus extends BaseAdapter implements OnScrollListener{ private LayoutInflater mLayoutInflater; private ListView mListView; private ArrayList<Integer> mListData; private int mScrollState = SCROLL_STATE_IDLE; private List<Runnable> mPendingNotify = new ArrayList<Runnable>(); public AdapterOptmLPlus(Context context, ListView listview, ArrayList<Integer> data) { mLayoutInflater = LayoutInflater.from(context); mListView = listview; mListData = data; mListView.setOnScrollListener(this); } private class ViewHolder { public ViewHolder(View viewRoot) { txt = (TextView)viewRoot.findViewById(R.id.listitem_txt); } public TextView txt; } @Override public int getCount() { return mListData == null ? : mListData.size(); } @Override public Object getItem(int position) { return mListData == null ? : mListData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false); ViewHolder holder = new ViewHolder(convertView); convertView.setTag(holder); } if (convertView != null && convertView.getTag() instanceof ViewHolder) { updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position)); } return convertView; } public void updateView(ViewHolder holder, Integer data) { if (holder != null && data != null) { holder.txt.setVisibility(View.VISIBLE); holder.txt.setText(data + ""); } } public void notifyDataSetChanged(final int position) { final Runnable runnable = new Runnable() { @Override public void run() { final int firstVisiablePosition = mListView.getFirstVisiblePosition(); final int lastVisiablePosition = mListView.getLastVisiblePosition(); final int relativePosition = position - firstVisiablePosition; if (position >= firstVisiablePosition && position <= lastVisiablePosition) { if (mScrollState == SCROLL_STATE_IDLE) { //当前不在滚动,立刻刷新 Log.d("Snser", "notifyDataSetChanged position=" + position + " update now"); updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position)); } else { synchronized (mPendingNotify) { //当前正在滚动,等滚动停止再刷新 Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending"); mPendingNotify.add(this); } } } else { //不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新 Log.d("Snser", "notifyDataSetChanged position=" + position + " update skip"); } } }; runnable.run(); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { mScrollState = scrollState; if (mScrollState == SCROLL_STATE_IDLE) { //滚动已停止,把需要刷新的listitem都刷新一下 synchronized (mPendingNotify) { final Iterator<Runnable> iter = mPendingNotify.iterator(); while (iter.hasNext()) { iter.next().run(); iter.remove(); } } } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }

以上所述是针对Listview加载的性能优化是如何实现的全部叙述,希望对大家有所帮助。

时间: 2024-10-31 14:30:53

Listview加载的性能优化是如何实现的的相关文章

Listview加载的性能优化是如何实现的_Android

在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建. listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次: 0.最原始的加载 1.利用convertView 2.利用ViewHolder 3.实现

android使用LruCache对listview加载图片时候优化处理

注意:LruCache是有版本限制的,低版本的sdk需要在libs文件夹添加相应的support-4v文件. 本文改造的大部分是参考http://www.iteye.com/topic/1118828,感谢. 不废话直接上工程代码,内有关键注释,项目就不上传了,自己对照着上面网址改呗. 首先是Application文件,负责创建图片存储文件夹: public class MyApp extends Application{ @Override public void onCreate() { s

列表-listview加载圆形图片

问题描述 listview加载圆形图片 最进项目中用到列表加载圆形图片,我是用异步下载后再裁剪图片然后显示出来,可是老是引发oom.不知道哪位大神能给个可行的方法. 解决方案 http://www.cnblogs.com/imlucky/archive/2012/07/31/2616317.html 解决方案二: listView的懒加载图片ImageLoader加载圆形图片用Swift和Core Animatoin创建圆形图片加载动画

安卓listview加载网络图片时,当时用了软引用之后,是不是就不用在使用二级缓存机制了

问题描述 安卓listview加载网络图片时,当时用了软引用之后,是不是就不用在使用二级缓存机制了 软引用不就是避免OOM的吗,和二级缓存机制的作用是一样的吧,可能二级缓存会更打程度的减少访问网络的次数吧,,但是如果在一个listview加载网络图片的项目中,是不是这二者选择一个用就行了? 解决方案 首先,ListView加载图片,使用什么软引用什么的,其实都是避免oom,那么你可以做到一下几点 1.使用缓存,至于你说的两种,任意一种都是可以的,起码起到了作用 2.当ListView去网络读取图

C#中listview加载的图片,批量删除时出现异常“文件被另一个程序使用”,具体代码如下

问题描述 C#中listview加载的图片,批量删除时出现异常"文件被另一个程序使用",具体代码如下 自动加载图片: private void FrmPicListView_Load(object sender EventArgs e) { listView1.View = View.LargeIcon; listView1.MultiSelect = true; string[] files = GetImages(); if (files != null) { ImageList

scollview内嵌套listview加载图片时显示在了listview上。

问题描述 scollview内嵌套listview加载图片时显示在了listview上. 我的scollview里面有imageview.和listview,listview加载图片完成 之后只显示listview,而imageview被翻到了上面,需要手动滑下来才看得见. 请教各位大神帮我解决下. 解决方案 加载完后,延时滚动到顶部. scrollView.postDelayed(new Runnable() { @Override public void run() { scrollView

listview-安卓ListView加载大量网络数据时卡

问题描述 安卓ListView加载大量网络数据时卡 最近小弟在做一个导购类项目,用到ListView控件,要在ListView中加载大量图片和文字,现在的问题是,ListView中的Button在点击时要改变Button上的文字,此时要等待好长时间(大概2-3秒),才能将改变后的文字显示出来.Log显示,每次点击,getView方法都要调用4次,而且每次点击都要从第一条到点击的条数依次寻找position.由于数据量大,条数多,因此条数越往后,等待的时间越长,不知道各位大神有什么好的解决方案,本

listview加载数据前10行需要下拉才显示数据

问题描述 listview加载数据前10行需要下拉才显示数据 Listview数据量大,刚加载数据的时候,1-10行只显示布局不显示数据,往下拉才能慢慢显示1-10行的数据,这是为什么? 解决方案 没怎么用过SimpleAdapter,推荐你用ArrayAdapter,都可以解决的 解决方案二: 查查使用分页吧 效率高 尤其针对大量数据显示

easyui datagrid 大数据加载效率慢,优化解决方法(推荐)_jquery

在使用easyui datagrid途中发现加载数据的效率真的不是一般的差.经测试IE8加载300条数据就感觉明显的慢了,加载2000条数据就另人崩溃用时差不多60秒,就算在google浏览器测试结果也快不了几秒. 平时听闻easyui datagrid效率底下,自己测试才发现真是使人无法忍受. 笔者只好百度,google解决方法,发现一篇文章说改 //1.3.3版本是这样的,其它版本也是这句代码 $(_1e0).html(_1e4.join("")); 改为: $(_1e0)[0].