先来看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. 我的Pycharm,我做主

    之间花了一周多的时间把Pycharm官方帮助文档翻译了一遍,一共43篇博客,累得要屎,感悟颇多. 发牢骚之前先总结点干货,这里把所有的翻译文档列成如下目录,方便大家索引: 最全Pycharm教程(1) ...

  2. 高质量API网关组件实现

    PI网关组件的作用? 1.网关直接代替MVC当中的Controller层,减少编码量提高开发效率 2.统一API接口的出入参格式,提高API的友好性 3.自动检测API接口规范,提高接口的质量 4.统 ...

  3. ICDM Winner's Interview: 3rd place, Roberto Diaz

    ICDM Winner's Interview: 3rd place, Roberto Diaz This summer, the ICDM 2015 conference sponsored a c ...

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

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

  5. bzoj 2456: mode ——独特水法

    Description 给你一个n个数的数列,其中某个数出现了超过n div 2次即众数,请你找出那个数. Input 第1行一个正整数n.第2行n个正整数用空格隔开. Output 一行一个正整数表 ...

  6. 边框画的三角形给shadow

    本文地址:http://www.cnblogs.com/veinyin/p/8690882.html  要写一个对话气泡样式,我们首先想到的当然给是一个盒子,然后用边框画一个三角形定位过去. 如果不需 ...

  7. Python练习-基于socket的FTPServer

    # 编辑者:闫龙 import socket,json,struct class MySocket: with open("FtpServiceConfig","r&qu ...

  8. Linux的基础优化-2

    1.启动网卡 ifup eth0 2.SSH链接 ifconfig 查看IP后SSH终端连接3.更新源 最小化安装是没有wget工具的,必须先安装再修改源 yum install wget 备份原系统 ...

  9. css_清除浮动的4种方式

    浮动布局和定位布局为css中布局的常用的两种布局方式,而且兼容性会比较好.随着flex的流行,以后会是主流,新的东西好用,兼容不太好.IE10以下不兼容flex布局. float布局会脱离文档流,对页 ...

  10. PHP简单爬虫 爬取免费代理ip 一万条

    目标站:http://www.xicidaili.com/ 代码: <?php require 'lib/phpQuery.php'; require 'lib/QueryList.php'; ...