相信很多人在使用自定义Adapter的时候都遇到这样的问题:

假设Adapter数据源中只有30个Item,理论上每显示一个新的Item的时候就会调用一次getView,均显示一次的话是要调用getView() 30次的,然而当我们在getView输出Log信息时,前几个会被重复多轮调用,之后每滑动到一个新的Item便会正常调用getView?

针对这个问题,网上很多帖子指明这与ListView的Item的高度计算方法有关,并强调解决该问题的办法是在XML文件里面定义listView的时候需要设置height为fill_parent或者是指定的高度值,不仅仅是ListView,甚至ListView的父控件也要是fill_parent或指定高度。那么问题来了,ListView的Item的高度计算方法究竟是如何影响getView的调用的呢?

既然是计算ListView的高度,那一定少不了onMeasure过程,所以我们首先来分析ListView.onMeasure源码:

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
... int childWidth = 0;
// 默认为0
int childHeight = 0;
int childState = 0; mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
// ViewMode 处于UNSPECIFIED 状态,且mAdapter.getCount() > 0时绘制首项来探测大小
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED ||
heightMode == MeasureSpec.UNSPECIFIED)) {
// getView(0)
final View child = obtainView(0, mIsScrap); measureScrapChild(child, 0, widthMeasureSpec); childWidth = child.getMeasuredWidth();
// 更新为item0的高度
childHeight = child.getMeasuredHeight();
...
}
...
/*
* ViewMode 处于UNSPECIFIED 状态,当mItemCount > 0时,heightSize为getView(0)的高度+ListView距离顶部和底部的距离+2*getVerticalFadingEdgeLength;
* 当mItemCount==0时,childHeight=0,则heightSize返回的仅仅是ListView距离顶部和底部的距离+2*getVerticalFadingEdgeLength
*/
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
// ViewMode 处于AT_MOST 状态,绘制多个列表项以确定高度。
if (heightMode == MeasureSpec.AT_MOST) {
// 会调用多个getView,这些view将不会被复用
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
} setMeasuredDimension(widthSize , heightSize);
mWidthMeasureSpec = widthMeasureSpec;
}

从以上源码及注释,我们得到三条信息:

(1)对于测量规格为UNSPECIFIED 的情形,若mAdapter.getCount() > 0时绘制首项来探测大小,最后返回首项高度+ListView的某些高度属性;若mAdapter.getCount() = 0或mAdapter==null时仅返回ListView的某些高度属性。

(2)对于测量规格为AT_MOST的情形,会调用measureHeightOfChildren,该方法会调用多个getView去探测ListView高度,并且探测过程中inflate生成的ItemView最后不会被回收,稍候我们会分析,正是measureHeightOfChildren方法的多次调用导致重复多轮调用getView。

(3)对于测量规格为EXACTLY的情形,源码中没有if分支判断,那就是默认执行到最后,也就是直接设置heightSize为测量规格中的size,对于这种情形,没有涉及getView的调用,是最高效的。

讲到这里,我们其实很清楚问题的根源是在ListView.measureHeightOfChildren这个方法,该方法的第35行执行heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);这里重点说明一下传入的参数,第二个参数为探测的起始位置startPosition,这里传入0,第三个参数为探测的结束位置,这里传入NO_POSITION,该值为ListView类的常数-1,第四个参数为最大高度,也就是父控件强加给ListView的高度限制。下面我们来阅读一下measureHeightOfChildren方法的源码:

 final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
final int maxHeight, int disallowPartialChildPosition) { final ListAdapter adapter = mAdapter;
if (adapter == null) {
return mListPadding.top + mListPadding.bottom;
}
int returnedHeight = mListPadding.top + mListPadding.bottom;
...
View child;
// onMeasure传递过来的endPosition==NO_POSITION,也就是-1,则令endPosition取数据源的adapter.getCount() - 1
endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
final AbsListView.RecycleBin recycleBin = mRecycler;
final boolean recyle = recycleOnMeasure();
final boolean[] isScrap = mIsScrap;
/*
* 从起始位置0(onMeasure中传入的是0)开始,循环地创建ItemView,并累加ItemView的高度,当高度和超过onMeasure传递过来的maxHeight(其实是测量规格中的size)时,跳出循环
*/
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i, isScrap); measureScrapChild(child, i, widthMeasureSpec); if (i > 0) {
// 计算高度的时候还需将ItemView间的分隔距离考虑进来
returnedHeight += dividerHeight;
} // Recycle the view before we possibly return from the method
if (recyle && recycleBin.shouldRecycleViewType(
((LayoutParams) child.getLayoutParams()).viewType)) {
/*
* 注意,这里addScrapView方法传入的第二个参数也就是position为-1,
* 由于Layout过程中调用getScrapView方法时传入的position>=0,
* 故position为-1的ScrapView都不会被回收,读懂这句话需要了解RecycleBin类,这里暂不深究。
*/
recycleBin.addScrapView(child, -1);
} returnedHeight += child.getMeasuredHeight();
// 循环提前终止条件
if (returnedHeight >= maxHeight) {
// 加上第i个的高度后returnedHeight已经超过了maxHeight,故高度探测应结束,说明最多只能容纳到第i个item
return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
&& (i > disallowPartialChildPosition) // We've past the min pos
&& (prevHeightWithoutPartialChild > 0) // We have a prev height
&& (returnedHeight != maxHeight) // 第i个Item不能显示完全,即超出容器
? prevHeightWithoutPartialChild
: maxHeight;
} if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
prevHeightWithoutPartialChild = returnedHeight;
}
}
return returnedHeight;
}

通过measureHeightOfChildren第19行的循环探测,ListView能容纳多少个ItemView就能够计算出来了,在这个过程中,每执行一次循环体就会创建一个ItemView,也就是调用一次getView,整个循环算调用了一轮getView。从这里我们也能看出,在高度探测过程中,只会去探测mdapter中的前面的ItemView,也就是只会调用到position为0到i的那些ItemView。

接下来先看一下Android官方文档关于Layout的解释:

Layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented in measure(int, int) and is a top-down traversal of the view tree. Each view pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every view has stored its measurements. The second pass happens in layout(int, int, int, int, int, int) and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.

When a view's measure() method returns, its getMeasuredWidth() and getMeasuredHeight() values must be set, along with those for all of that view's descendants. A view's measured width and measured height values must respect the constraints imposed by the view's parents. This guarantees that at the end of the measure pass, all parents accept all of their children's measurements. A parent view may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children's unconstrained sizes is too big or too small.

标红的一段英文的翻译为:一个父视图可能多次调用子视图的measure()方法。比如,父视图首先不指定大小调用子视图方法来确定需要多大尺寸,如果所有子视图尺寸总值过大或过小,会使用实际尺寸再次调用该方法。

从上面的文档来看,一趟ListView.onMeasure计算操作完毕之后,ListView的onMeasure方法返回,ListView的父视图是否接受这一测量结果还待定,若不接受可能会再次发起onMeasure测量请求,因此可能会导致measureHeightOfChildren方法的多次执行,从而会导致getView的多轮重复调用,也就是position为0到i的那些ItemView会被反复多次调用。

以上过程属于个人探索,疏漏之处在所难免,如有差错,敬请批评指正。

参考文献:

视图 - View(http://blog.sina.com.cn/s/blog_4fe2ba90010080jk.html)

自定义Adapter为什么会重复多轮调用getView?——原来是ListView.onMeasure在作祟的更多相关文章

  1. Android中的自定义Adapter(继承自BaseAdapter)——与系统Adapter的调用方法一致——含ViewHolder显示效率的优化(转)

    Android中很多地方使用的是适配器(Adapter)机制,那我们就要好好把这个Adapter利用起来,并且用出自己的特色,来符合我们自行设计的需要喽~~~ 下面先上一个例子,是使用ViewHold ...

  2. 自定义adapter 的getView方法被重复执行了n次的解决方法

    1. getView执行的次数和你的getCount没有直接的关系   ,getCount和你listView里面的条目数量(行数量)有关系 ,getView方法执行次数取决于你屏幕上显示几个条目,比 ...

  3. 【转】 Pro Android学习笔记(二二):用户界面和控制(10):自定义Adapter

    目录(?)[-] 设计Adapter的布局 代码部分 Activity的代码 MyAdapter的代码数据源和构造函数 MyAdapter的代码实现自定义的adapter MyAdapter的代码继续 ...

  4. Android ListView 自定义 Adapter

    自定义Adapter类 public class ListViewAdapter extends BaseAdapter { private static final String TAG = Mai ...

  5. android 自定义adapter和线程结合 + ListView中按钮滑动后状态丢失解决办法

    adapter+线程 1.很多时候自定义adapter的数据都是来源于服务器的,所以在获取服务器的时候就需要异步获取,这里就需要开线程了(线程池)去获取服务器的数据了.但这样有的时候adapter的中 ...

  6. 关于自定义adapter使用getApplicationContext()影响主题

    最近弄了一个东西,listview+switch构成界面 关于android自定义adapter,继承自baseadapter,发现界面的switch开关主题变了想要的是浅色的主题,但是却发现变成了深 ...

  7. Android中的普通对话框、单选对话框、多选对话框、带Icon的对话框、以及自定义Adapter和自定义View对话框详解

    对话框就是一个AlertDialog,但是一个简单的AlertDialog,我们却可以将它玩出许多花样来,下面我们就来一起总结一下AlertDialog的用法.看看各位童鞋在平时的工作中否都用到了Al ...

  8. [Android] Android RecycleView和ListView 自定义Adapter封装类

    在网上查看了很多对应 Android RecycleView和ListView 自定义Adapter封装类 的文章,主要存在几个问题: 一).网上代码一大抄,复制来复制去,大部分都运行不起来,或者 格 ...

  9. Android 自定义Adapter实现多视图Item的ListView

    自定义Adapter实现多视图Item的ListView http://www.devdiv.com/adapter_item_listview-blog-20-7539.html 1.原理分析 Ad ...

随机推荐

  1. tp5.1 入口文件即报错

    ---------------------------------------------------------------[ 2018-10-10T10:51:49+08:00 ] 218.93. ...

  2. NATS_12:NATS Streaming详解

    NATS Streaming NATS Streaming是一个以NATS为驱动的数据流系统且它的源码也是由Golang语言编写的.其中NATS Streaming服务是一个可执行的文件名为:nats ...

  3. P1558 色板游戏

    P1558 色板游戏 题目背景 阿宝上学了,今天老师拿来了一块很长的涂色板. 题目描述 色板长度为L,L是一个正整数,所以我们可以均匀地将它划分成L块1厘米长的小方格.并从左到右标记为1, 2, .. ...

  4. Vue入坑教程(二)——项目结构详情介绍

    之前已经介绍了关于Vue的脚手架vue-cli的安装,以及一些文件目录介绍.具体可以查看<vue 入坑教程(一)--搭建vue-cli脚手架> 下面简单说一下具体的文件介绍 (一) pac ...

  5. textview 使drawable与text一起居中的textview,这里仅支持drawableleft

    package cc.hent.www.ramo_cmedcial.CustomView; import android.content.Context; import android.graphic ...

  6. 遇到can not resolve app 依赖包的问题

    1.第一种解决方式:build->sdkmannger->sdk tool ->安装android support responsitory和google responsitory ...

  7. 【转载】BP神经网络

    原文地址:http://blog.csdn.net/acdreamers/article/details/44657439 今天来讲BP神经网络,神经网络在机器学习中应用比较广泛,比如函数逼近,模式识 ...

  8. JavaScript实现单向链表

    JavaScript 本身提供了十分好用的数据类型,以满足大家的日常使用.单靠 Array  和 Object 也的确足够应付日常的绝大部分需求,这也导致了很多前端er对数据结构这一块不是十分的了解. ...

  9. Android Dalvik和JVM的区别

    JVM运行的Java字节码,它从.class文件或Jar包中加载字节码然后执行: Dalvik 运行的是 dex 文件(Dalvik Executable),生成APK时,Dx工具把所有.class文 ...

  10. Donald Ervin Knuth:最年轻的图灵奖高德纳

    高德纳(Donald Ervin Knuth,1938年),美国著名计算机科学家,斯坦福大学电脑系荣誉教授.高德纳教授被誉为现代计算机科学的鼻祖,在计算机科学及数学领域发表了多部 具广泛影响的论文和著 ...