何时调用getView?——从源码的角度给出解答
先来看ListView类中的makeAndAddView方法:
没有数据变化:从mRecycler中取得可视的view
数据有变化:obtainView
/**
* 获取视图填充到列表的item中去,视图可以是从未使用过的视图转换过来,也可以是从回收站复用的视图。
* 在该方法中,先查找是否有可重用视图,如果有,使用可重用视图。
* 然后通过obtainView方法获取一个view(有可能是从未使用视图转换过来
* (obtainView方法是在AbsListView方法中定义)),再重新测量和定位View。
*/
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
// 没有数据变化:从mRecycler中取得可视的view
if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
...
}
// 生成view,回收旧view和调用mApapter.getView的地方(AbsListView)
child = obtainView(position, mIsScrap);
...
return child;
}
第11行调用了obtainView方法,该方法的实现是在package android.widget;的AbsListView类中
View obtainView(int position, boolean[] isScrap) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); isScrap[0] = false;
View scrapView; scrapView = mRecycler.getTransientStateView(position);
if (scrapView == null) {
// 从回收站回收一个View
scrapView = mRecycler.getScrapView(position);
} View child;
if (scrapView != null) {
// 这里调用了getView!注意,第二个参数也就是convertView,传入的是刚才从回收站中回收的View(如果有的话)
child = mAdapter.getView(position, scrapView, this);
...
return child;
}
第16行调用了getView!根据Java多态的特性,实际执行的getView将会是我们自定义BaseAdapter中的那个getView方法。
好,现在虽然找到getView的直接调用者了,问题来了,何时去触发makeAndAddView并调用getView呢?
我们首先来看ListView中的fillDown:自顶至底去填充ListView
/**
填充从pos到list底部所有的item。里面调用到了makeAndAddView方法:
*/
private View fillDown(int pos, int nextTop) {
...
6 // 这里调用到了makeAndAddView方法
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
...
return selectedView;
}
有fillDown自然就有fillUp:
private View fillUp(int pos, int nextBottom) {
View selectedView = null;
...
// 调用makeAndAddView
View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
...
return selectedView;
}
还有fillFromTop、fillFromMiddle、fillAboveAndBelow、fillFromSelection等,这些方法都是用来进行子元素布局的,区别是布局模式不同而已。
好了,现在布局子元素的方法有了,那么谁来触发这些方法呢?
通过查找ListView源码,发现刚才的那些方法在layoutChildren()中基本上都出现了。
@Override
protected void layoutChildren() {
...
// 根据mLayoutMode的值来决定布局模式
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition, mSpecificTop);
break;
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
case LAYOUT_SPECIFIC:
sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
break;
case LAYOUT_MOVE_SELECTION:
sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
break;
default:// 默认的布局顺序是从上往下
if (childCount == 0) {
if (!mStackFromBottom) {
final int position = lookForSelectablePosition(0, true);
setSelectedPositionInt(position);
sel = fillFromTop(childrenTop);
} else {
final int position = lookForSelectablePosition(mItemCount - 1, false);
setSelectedPositionInt(position);
sel = fillUp(mItemCount - 1, childrenBottom);
}
} else {
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition,
oldSel == null ? childrenTop : oldSel.getTop());
} else if (mFirstPosition < mItemCount) {
sel = fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
} else {
sel = fillSpecific(0, childrenTop);
}
}
break;
} ...
}
继续查找,我们发现layoutChildren的调用者是onFocusChanged、setSelectionInt、父类AbsListView中的onTouchMove、onLayout(这个比较特殊,后文会说明)等,说明当ListView的焦点发生变化时、选中某一项、或者滑动ListView时都会触发ListView的layoutChildren()去布局子元素。
到此为止我们已经很清楚getView的调用时机了,根据掌握的知识点,我们很自然能想到,当初始化一个ListView时,getView的调用也是避免不了的。这是因为ListView在初始化时肯定会绑定一个adapter,即调用语句listview.setAdapter(adapter),我们看一下setAdapter的源码:
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
// 去除原有adapter、观察者、选中项等信息
resetList();
mRecycler.clear();
// 包装adapter,加header或footer,并绑定到当前ListView
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
// 重置选中项信息
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID; // AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter); if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
// 重新注册观察者
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
// 设置回收器中类型不同的View数目,这里与getView的回收机制紧密相关,值得深究
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
// 设置初始选中项
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position); if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
// 请求布局重绘
requestLayout();
}
通读setAdapter源码,我们发现其中并未出现生成新子视图,即调用mAdapter.getView的语句或相关方法,说明此时ListView并未包含子视图。那么疑问来了,ListView是如何在初始化的时候生成子视图的,也就是说第一屏的视图是如何加载到屏幕上的?往后看,我们发现在第53行调用了requestLayout请求布局重绘,我们知道requestLayout最终会去调用onMeasure、onLayout、onDraw方法,因此我们猜测会不会是在onMeasure、onLayout、onDraw某个方法中生成了子视图?
答案是肯定的,AbsListVIew.onLayout过程与普通视图的layout过程完全不同,如下:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
...
layoutChildren();
...
}
该方法调用了layoutChildren();,即重新布局ListView列表视图。
由此说明调用requestLayout可以实现ListView列表视图的重新布局,这里联想到adapter.notifyDataSetChanged也会调用requestLayout,从而都能实现ListView的刷新。
以上过程只是个人探索,并非绝对正确,如有差错敬请批评指正,谢谢。
参考文献:
Android ListView工作原理完全解析,带你从源码的角度彻底理解
何时调用getView?——从源码的角度给出解答的更多相关文章
- Android AsyncTask完全解析,带你从源码的角度彻底理解
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进 ...
- [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...
- 从源码的角度分析ViewGruop的事件分发
从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...
- 从源码的角度解析View的事件分发
有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图 ...
- 从源码的角度看Activity是如何启动的
欢迎访问我的个人博客,原文链接:http://wensibo.top/2017/07/03/Binder/ ,未经允许不得转载! 大家好,今天想与大家一起分享的是Activity.我们平时接触的最多的 ...
- 第九节:从源码的角度分析MVC中的一些特性及其用法
一. 前世今生 乍眼一看,该标题写的有点煽情,最近也是在不断反思,怎么能把博客写好,让人能读下去,通俗易懂,深入浅出. 接下来几个章节都是围绕框架本身提供特性展开,有MVC程序集提供的,也有其它程序集 ...
- 从源码的角度看 React JS 中批量更新 State 的策略(下)
这篇文章我们继续从源码的角度学习 React JS 中的批量更新 State 的策略,供我们继续深入学习研究 React 之用. 前置文章列表 深入理解 React JS 中的 setState 从源 ...
- 从源码的角度看 React JS 中批量更新 State 的策略(上)
在之前的文章「深入理解 React JS 中的 setState」与 「从源码的角度再看 React JS 中的 setState」 中,我们分别看到了 React JS 中 setState 的异步 ...
- 从源码的角度再学「Thread」
前言 Java中的线程是使用Thread类实现的,Thread在初学Java的时候就学过了,也在实践中用过,不过一直没从源码的角度去看过它的实现,今天从源码的角度出发,再次学习Java Thread, ...
随机推荐
- python 模块之hashlib
Hashlib模块 Python里面的hashlib模块提供了很多加密的算法,这里介绍一下hashlib的简单使用事例,用hashlib的md5算法加密数据,其他的所有加密算法使用方式上基本类似. h ...
- Go_16:GoLang中flag标签使用
正如其他语言一样,在 linux 系统上通过传入不同的参数来使得代码执行不同逻辑实现不同功能,这样的优点就是执行想要的既定逻辑而不需要修改代码重新编译与打包.在 Golang 语言中也为我们提供了相应 ...
- json转java对象
用了平台之后很少再接触到java和js的底层代码,前几天远程帮一个萌新远程调试代码,这个萌新按照网上的教程去将json字符转java对象却一直报错.真相是它的json字符串格式不对,他的明明是一个数组 ...
- Redis配置及使用
1.参考资料 在线命令说明: http://doc.redisfans.com/ redis安装列表: https://github.com/rgl/redis/downloads Redis工具使用 ...
- Centos7一键编译安装zabbix-4.0.2
##只针对centos7的系统有效,centos6无效,mysql zabbix用户:zabbix,密码:zabbix;建议用全新的centos7服务器 软件版本: (nginx-1.14.2.php ...
- [整理]前端模块化开发AMD CMD
前端模块化开发的价值 AMD (中文版) CMD 模块定义规范 标准构建 http://seajs.org http://chaoskeh.com/blog/why-seajs.html http:/ ...
- 环境变量ANDROID_SDK_HOME的作用
默认情况下,开发者创建的AVD(Android Virtual Device)存放在家目录的.android下. 如果是Linux,其路径就是 /home/<your_user_name> ...
- java后台代码发送邮件
1:安装 eyoumailserversetup 易邮邮件服务器 注册账号 2:安装Foxmail 登录以后会有个还原页面 3:测试 4:java 代码编写 配置文件: mail.host=http ...
- PHP在Linux下Apache环境中执行exec,system,passthru等服务器命令函数
更多内容推荐微信公众号,欢迎关注: 若在服务器中使用php test.php运行exec,system,passthru等命令相关的脚本能成功运行,在web页面却没反应, [可能是服务器端,PHP脚本 ...
- 2016.5.21——Compare Version Numbers
Compare Version Numbers 本题收获: 1.字符串型数字转化为整型数字的方法:s[i] - '0',( 将字母转化为数字是[i]-'A' ) 2.srt.at(),substr ...