先来看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初始化简单分析

Android ListView工作原理完全解析,带你从源码的角度彻底理解

android源码解析--ListView(上)

ListView源代码分析

何时调用getView?——从源码的角度给出解答的更多相关文章

  1. Android AsyncTask完全解析,带你从源码的角度彻底理解

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进 ...

  2. [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...

  3. 从源码的角度分析ViewGruop的事件分发

    从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...

  4. 从源码的角度解析View的事件分发

    有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图 ...

  5. 从源码的角度看Activity是如何启动的

    欢迎访问我的个人博客,原文链接:http://wensibo.top/2017/07/03/Binder/ ,未经允许不得转载! 大家好,今天想与大家一起分享的是Activity.我们平时接触的最多的 ...

  6. 第九节:从源码的角度分析MVC中的一些特性及其用法

    一. 前世今生 乍眼一看,该标题写的有点煽情,最近也是在不断反思,怎么能把博客写好,让人能读下去,通俗易懂,深入浅出. 接下来几个章节都是围绕框架本身提供特性展开,有MVC程序集提供的,也有其它程序集 ...

  7. 从源码的角度看 React JS 中批量更新 State 的策略(下)

    这篇文章我们继续从源码的角度学习 React JS 中的批量更新 State 的策略,供我们继续深入学习研究 React 之用. 前置文章列表 深入理解 React JS 中的 setState 从源 ...

  8. 从源码的角度看 React JS 中批量更新 State 的策略(上)

    在之前的文章「深入理解 React JS 中的 setState」与 「从源码的角度再看 React JS 中的 setState」 中,我们分别看到了 React JS 中 setState 的异步 ...

  9. 从源码的角度再学「Thread」

    前言 Java中的线程是使用Thread类实现的,Thread在初学Java的时候就学过了,也在实践中用过,不过一直没从源码的角度去看过它的实现,今天从源码的角度出发,再次学习Java Thread, ...

随机推荐

  1. 百度语音合成 composer

    https://packagist.org/packages/jormin/baidu-speech http://ai.baidu.com/docs#/TTS-Online-PHP-SDK/top

  2. vue的全局指令

    vue有四个全局指令:directive.extent.set.component directive:自定义指令 //写一个改变颜色的指令 Vue.directive('amie',function ...

  3. Hadoop生态圈-hbase介绍-伪分布式安装

    Hadoop生态圈-hbase介绍-伪分布式安装 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.HBase简介 HBase是一个分布式的,持久的,强一致性的存储系统,具有近似最 ...

  4. Android 使用GPS获取到经纬度后 无法在Android8.0上使用Geocoder类获取位置信息

    由于我的应用在获取到经纬度后在Android8.0不能使用如下代码获取位置信息.只好使用百度地图 WEB服务API 通过调接口的方式获取位置信息. Geocoder geocoder = new Ge ...

  5. BFS搜索:POJ No 3669 Meteor Shower

    #include <iostream> #include <cstring> #include <queue> #include <cstdio> #i ...

  6. java 去除末尾的零 如果小数点可以去除同时去除小数点

    String s; if(s.indexOf(".") > 0){ //正则表达 s = s.replaceAll("0+?$", "" ...

  7. 解决组合排列问题 A (m ,n) m>=n

    转载自http://blog.csdn.net/sunyujia/article/details/4124011 从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取 ...

  8. sort函数(cmp)、map用法---------------Tju_Oj_2312Help Me with the Game

    这道题里主要学习了sort函数.sort的cmp函数写法.C++的map用法(其实和数组一样) Your task is to read a picture of a chessboard posit ...

  9. 【译】第十二篇 Integration Services:高级日志记录

    本篇文章是Integration Services系列的第十二篇,详细内容请参考原文. 简介在前一篇文章我们配置了SSIS内置日志记录,演示了简单和高级日志配置,保存并查看日志配置,生成自定义日志消息 ...

  10. 彻底搞懂字符编码(unicode,mbcs,utf-8,utf-16,utf-32,big endian,little endian...)[转]

    最近有一些朋友常问我一些乱码的问题,和他们交流过程中,发现这个编码的相关知识还真是杂乱不堪,不少人对一些知识理解似乎也有些偏差,网上百度, google的内容,也有不少以讹传讹,根本就是错误的(例如说 ...