PS:满打满算,差不多三个月没写博客了...前一阵忙的不可开交...总算是可以抽出时间研究研究其他事情了...

1.ListView的复用机制
ListView是我们经常使用的一个控件,虽然说都会用,但是却并不一定完全清楚ListView的复用机制,虽然在Android 5.0版本之后提供了RecycleView去替代ListView和GridView,提供了一种插拔式的体验,也就是所谓的模块化。本篇主要针对ListView的复用机制进行探讨,因此就 提RecycleView。昨天看了一下郭霖大神的ListView原理深度解析的一篇博客,因此学习了一段时间,自己也说一下自己的理解。
i.RecycleBin的基本原理
首先需要说一下RecycleBin的基本原理,这个类也是实现复用的关键类。接着我们需要明确ActiveView的概念,ActivityView其实就是在UI屏幕上可见的视图(onScreenView),也是与用户进行交互的View,那么这些View会通过RecycleBin直接存储到mActivityView数组当中,以便为了直接复用,那么当我们滑动ListView的时候,有些View被滑动到屏幕之外(offScreen) View,那么这些View就成为了ScrapView,也就是废弃的View,已经无法与用户进行交互了,这样在UI视图改变的时候就没有绘制这些无用视图的必要了。他将会被RecycleBin存储到mScrapView数组当中,但是没有被销毁掉,目的是为了二次复用,也就是间接复用。当新的View需要显示的时候,先判断mActivityView中是否存在,如果存在那么我们就可以从mActivityView数组当中直接取出复用,也就是直接复用,否则的话从mScrapView数组当中进行判断,如果存在,那么二次复用当前的视图,如果不存在,那么就需要inflate View了。
这是一个总体的流程图,复用机制就是这样的。那么我们先来理解一下ListView第一次加载的时候都做了哪些工作,首先会执行onLayout方法。。
/**
* Subclasses should NOT override this method but {@link #layoutChildren()}
* instead.
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
if (changed) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
mRecycler.markChildrenDirty();
}
layoutChildren();
mInLayout = false;
}
这里可以看到onLayout方法会调用layoutChildren()方法,也就是对item进行布局的流程,layoutChildren()方法就不进行粘贴了,代码量过长我们只需要知道,这是对ListView中的子View进行布局的一个方式就可以了,在我们第一次加载ListView的时候,RecycleBin中的数组都没有任何的数据,因此第一次加载都需要inflate View,也就是创建新的View。并且第一次加载的时候是自顶向下对数据进行加载的,因此在layoutChildren()会执行fillFromTop()方法。fillFromTop()会执行filleDown()方法。
/**
* Fills the list from pos down to the end of the list view.
*
* @param pos The first position to put in the list
*
* @param nextTop The location where the top of the item associated with pos
* should be drawn
*
* @return The view that is currently selected, if it happens to be in the
* range that we draw.
*
* @param pos:列表中的一个绘制的Item在Adapter数据源中对应的位置
* @param nextTop:表示当前绘制的Item在ListView中的实际位置..
*/
private View fillDown(int pos, int nextTop) {
View selectedView = null;
/**
* end用来判断Item是否已经将ListView填充满
*/
int end = (getBottom() - getTop()) - mListPadding.bottom;
while (nextTop < end && pos < mItemCount) {
/**
* nextTop < end确保了我们只要将新增的子View能够覆盖ListView的界面就可以了
*pos < mItemCount确保了我们新增的子View在Adapter中都有对应的数据源item
*/
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
/**
*将最新child的bottom值作为下一个child的top值,存储在nextTop中
*/
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
return selectedView;
}
这里存在一个关键方法,也就是makeAndAddView()方法,这是ListView将Item显示出来的核心部分,也是这个部分涉及到了ListView的复用
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
//判断数据源是否发生了变化.
if (!mDataChanged) {
// Try to use an exsiting view for this position
//如果mActivityView[]数组中存在可以直接复用的View,那么直接获取,然后重新布局.
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
/**
*如果mActivityView[]数组中没有可用的View,那么尝试从mScrapView数组中读取.然后重新布局.
*如果可以从mScrapView数组中可以获取到,那么直接返回调用mAdapter.getView(position,scrapView,this);
*如果获取不到那么执行mAdapter.getView(position,null,this)方法.
*/
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
这里可以看到如果数据源没有变化的时候,会从mActivityView数组中判断是否存在可以直接复用的View,可能很多读者都不太明白直接复用到底是怎么个过程,举个例子,比如说我们ListView一页可以显示10条数据,那么我们在这个时候滑动一个Item的距离,也就是说把position = 0的Item移除屏幕,将position = 10 的Item移入屏幕,那么position = 1的Item是不是就直接能够从mActivityView数组中拿到呢?这是可以的,我们在第一次加载Item数据的时候,已经将position = 0~9的Item加入到了mActivityView数组当中,那么在第二次加载的时候,由于position = 1 的Item还是ActivityView,那么这里就可以直接从数组中获取,然后重新布局。这里也就表示的是Item的直接复用。
如果我们在mActivityView数组中获取不到position对应的View,那么就尝试从mScrapView废弃View数组中尝试去获取,还拿刚才的例子来说当position = 0的Item被移除屏幕的时候,首先会Detach让View和视图进行分离,清空children,然后将废弃View添加到mScrapView数组当中,当加载position = 10的Item时,mActivityView数组肯定是没有的,也就无法获取到,同样mScrapView中也是不存在postion = 10与之对应的废弃View,说白了就是mScrapView数组只有mScrapView[0]这一项数据,肯定是没有mScrapView[10]这项数据的,那么我们就会这样想,肯定是从Adapter中的getView方法获取新的数据喽,其实并不是这样,虽然mScrapView中虽然没有与之对应的废弃View,但是会返回最后一个缓存的View传递给convertview。那么也就是将mScrapView[0]对应的View返回。总体的流程就是这样。
这里我们可以看到,ListView始终只会在getView方法中inflate一页的Item,也就是new View只会执行一页Item的次数。后续的Item通过直接复用和间接复用完成。
注意一种情况:比如说还是一页的Item,但是position = 0的Item没有完全滑动出UI,position = 10的Item没有完全进入到UI的时候,那么position = 0的Item不会被detach掉,同样不会被加入到废弃View数组,这时mScrapView是空的,没有任何数据,那么position = 10的Item即无法从mActivityView中直接复用View,因为是第一次加载。mActivityView[10]是不存在的,同时mScrapView是空的,因此position = 10的Item只能重新生成View,也就是从getView方法中inflate。这里obtainView方法没有具体贴出,大家可以自己进去看看。obtainView其实就是判断能否从废弃View中获取到View,获取到了则执行:
if (scrapView != null) {
child = mAdapter.getView(position, scrapView, this);
}
这里是可以获取到,那么getView会传递scrapView。否则的话:
else {
child = mAdapter.getView(position, null, this);
}
获取不到就传递null,这样就会执行我们定义的Adapter中的方法。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = View.inflate(context, R.layout.list_item_layout, null);
}
return convertView;
}
至于向上滑动会执行其他的一些方法,也就是自底向上铺满ListView,同样也会直接或者间接复用控件。理解了复用的机制才是关键,因此向上滑基本就不难理解了。补充一点,RecycleBin中还存在一个方法,setViewTypeCount()方法。这个是针对Adapter中的getViewTypeCount()设定的。针对每一种数据类型,setViewTypeCount()会为每种数据类型开启一个单独的RecycleBin回收机制。这里我们只需要知道就可以了。至于在郭神博客中看到ListView会onLayout多次,这是肯定的,由于Android View加载机制问题,子控件需要根据父控件的大小要重新测量大小,经过多次测量才能够显示在UI上。这是View测量多次的原因。至于ListView在多次布局的问题我就不进行赘余了,总之无论几次测量,ListView是不会多次执行重复的逻辑的,也就是说数据不会有多份,只会存在一份数据。
这里也就是ListView复用的基本原理和RecycleBin的回收机制了。代码贴的很少,都是一些关键代码,没必要去一行一行的研究代码,毕竟和大神还差很大的一个档次。我们只需要知道这个执行过程和原理就可以了。
2.ViewHolder
最后说一说ViewHolder这个东西,很多Android学习者会把这个东西和ListView的复用机制搞混。这里ViewHolder也是在复用的时候进行使用,但是和复用机制是没太大关系的。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
ListViewItem itemData = items.get(position);
if(convertView == null){
convertView = View.inflate(context, R.layout.list_item_layout, null);
holder = new ViewHolder();
holder.userImg = (ImageView) convertView.findViewById(R.id.user_header_img);
holder.userName = (TextView) convertView.findViewById(R.id.user_name);
holder.userComment = (TextView) convertView.findViewById(R.id.user_coomment);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.userImg.setImageResource(itemData.getUserImg());
holder.userName.setText(itemData.getUserName());
holder.userComment.setText(itemData.getUserComment());
return convertView;
}
static class ViewHolder{
ImageView userImg;
TextView userName;
TextView userComment;
}
在实现Adapter的时候,我们一般会加上ViewHolder这个东西,ViewHolder和复用机制和原理是无关的,他的主要目的是持有Item中控件的引用,从而减少findViewById()的次数,因为findViewById()方法也是会影响效率的,因此在复用的时候他起的作用是这个,减少方法执行次数增加效率。这里做个简单的提醒,别弄混就行。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# android
# listview复用
# listview复用机制
# android listview优化几种写法详细介绍
# Android自定义Adapter的ListView的思路及代码
# Android之带group指示器的ExpandableListView(自写)
# android中ListView多次刷新重复执行getView的解决方法
# android ListView内数据的动态添加与删除实例代码
# android二级listview列表实现代码
# android ListView和GridView拖拽移位实现代码
# Android ListView数据绑定显示的三种解决方法
# android ListView和ProgressBar(进度条控件)的使用方法
# Android ListView长按弹出菜单二种实现方式示例
# 复用
# 组中
# 这是
# 加载
# 就可以
# 也就
# 只需
# 不存在
# 可以看到
# 要知道
# 的是
# 就会
# 都有
# 基本原理
# 最多
# 是从
# 只会
# 大神
# 也就是说
# 可以直接
相关文章:
关于BootStrap modal 在IOS9中不能弹出的解决方法(IOS 9 bootstrap modal ios 9 noticework)
,南京靠谱的征婚网站?
公司网站制作价格怎么算,公司办个官网需要多少钱?
mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?
如何挑选最适合建站的高性能VPS主机?
如何用花生壳三步快速搭建专属网站?
如何用虚拟主机快速搭建网站?详细步骤解析
南京网站制作费用,南京远驱官方网站?
网站海报制作教学视频教程,有什么免费的高清可商用图片网站,用于海报设计?
建站之星24小时客服电话如何获取?
购物网站制作公司有哪些,哪个购物网站比较好?
网站好制作吗知乎,网站开发好学吗?有什么技巧?
nginx修改上传文件大小限制的方法
C++中的Pimpl idiom是什么,有什么好处?(隐藏实现)
,如何利用word制作宣传手册?
上海网站制作网页,上海本地的生活网站有哪些?最好包括生活的各个方面的?
如何在IIS中新建站点并解决端口绑定冲突?
如何用美橙互联一键搭建多站合一网站?
rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted
常州企业建站如何选择最佳模板?
如何在香港免费服务器上快速搭建网站?
如何在云服务器上快速搭建个人网站?
如何在Golang中实现微服务服务拆分_Golang微服务拆分与接口管理方法
如何快速生成凡客建站的专业级图册?
Python路径拼接规范_跨平台处理说明【指导】
企业网站制作公司网页,推荐几家专业的天津网站制作公司?
微网站制作教程,我微信里的网站怎么才能复制到浏览器里?
赚钱网站制作软件,建一个网站怎样才能赚钱?是如何盈利的?
如何在阿里云虚拟服务器快速搭建网站?
盐城做公司网站,江苏电子版退休证办理流程?
如何快速搭建FTP站点实现文件共享?
如何在阿里云虚拟主机上快速搭建个人网站?
表情包在线制作网站免费,表情包怎么弄?
详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)
制作网站的公司有哪些,做一个公司网站要多少钱?
Swift开发中switch语句值绑定模式
视频网站制作教程,怎么样制作优酷网的小视频?
弹幕视频网站制作教程下载,弹幕视频网站是什么意思?
如何在局域网内绑定自建网站域名?
如何确保FTP站点访问权限与数据传输安全?
深圳网站制作费用多少钱,读秀,深圳文献港这样的网站很多只提供网上试读,但有些人只要提供试读的文章就能全篇下载,这个是怎么弄的?
成都网站制作公司哪家好,四川省职工服务网是做什么用?
油猴 教程,油猴搜脚本为什么会网页无法显示?
如何选择域名并搭建高效网站?
北京制作网站的公司,北京铁路集团官方网站?
唐山网站制作公司有哪些,唐山找工作哪个网站最靠谱?
建站之星会员如何解锁更多建站功能?
如何选择高性价比服务器搭建个人网站?
c# await 一个已经完成的Task会发生什么
网站制作模板下载什么软件,ppt模板免费下载网站?
*请认真填写需求信息,我们会在24小时内与您取得联系。