何时调用getView?——从源码的角度给出解答

先来看ListView类中的makeAndAddView方法:

没有数据变化:从mRecycler中取得可视的view

数据有变化:obtainView

 1 /**

 2      * 获取视图填充到列表的item中去,视图可以是从未使用过的视图转换过来,也可以是从回收站复用的视图。

 3      * 在该方法中,先查找是否有可重用视图,如果有,使用可重用视图。

 4      * 然后通过obtainView方法获取一个view(有可能是从未使用视图转换过来

 5      * (obtainView方法是在AbsListView方法中定义)),再重新测量和定位View。

 6      */

 7     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,

 8             boolean selected) {

 9         View child;

10         // 没有数据变化:从mRecycler中取得可视的view

11         if (!mDataChanged) {

12             // Try to use an existing view for this position

13             child = mRecycler.getActiveView(position);

14             ...

15         }

16         // 生成view,回收旧view和调用mApapter.getView的地方(AbsListView)

17         child = obtainView(position, mIsScrap);

18         ...

19         return child;

20     }

第11行调用了obtainView方法,该方法的实现是在package android.widget;的AbsListView类中

 1        View obtainView(int position, boolean[] isScrap) {

 2            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");

 3    

 4            isScrap[0] = false;

 5            View scrapView;

 6            

 7            scrapView = mRecycler.getTransientStateView(position);

 8            if (scrapView == null) {

 9                // 从回收站回收一个View

10               scrapView = mRecycler.getScrapView(position);

11           }

12   

13           View child;

14           if (scrapView != null) {

15               // 这里调用了getView!注意,第二个参数也就是convertView,传入的是刚才从回收站中回收的View(如果有的话)

16               child = mAdapter.getView(position, scrapView, this);

17           ...

18           return child;

19       }  

第16行调用了getView!根据Java多态的特性,实际执行的getView将会是我们自定义BaseAdapter中的那个getView方法。

好,现在虽然找到getView的直接调用者了,问题来了,何时去触发makeAndAddView并调用getView呢?

我们首先来看ListView中的fillDown:自顶至底去填充ListView

 1  /**

 2      填充从pos到list底部所有的item。里面调用到了makeAndAddView方法:

 3       */

 4      private View fillDown(int pos, int nextTop) {

 5         ... 
6
// 这里调用到了makeAndAddView方法 7 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); 8 ... 9 return selectedView; 10 }

有fillDown自然就有fillUp:

1      private View fillUp(int pos, int nextBottom) {

2          View selectedView = null;

3          ...

4          // 调用makeAndAddView

5          View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);

6          ...

7          return selectedView;

8      }

还有fillFromTop、fillFromMiddle、fillAboveAndBelow、fillFromSelection等,这些方法都是用来进行子元素布局的,区别是布局模式不同而已。

好了,现在布局子元素的方法有了,那么谁来触发这些方法呢?

通过查找ListView源码,发现刚才的那些方法在layoutChildren()中基本上都出现了。

 1     @Override

 2     protected void layoutChildren() {

 3             ...

 4             // 根据mLayoutMode的值来决定布局模式

 5             switch (mLayoutMode) {

 6             case LAYOUT_SET_SELECTION:

 7                 if (newSel != null) {

 8                     sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);

 9                 } else {

10                     sel = fillFromMiddle(childrenTop, childrenBottom);

11                 }

12                 break;

13             case LAYOUT_SYNC:

14                 sel = fillSpecific(mSyncPosition, mSpecificTop);

15                 break;

16             case LAYOUT_FORCE_BOTTOM:

17                 sel = fillUp(mItemCount - 1, childrenBottom);

18                 adjustViewsUpOrDown();

19                 break;

20             case LAYOUT_FORCE_TOP:

21                 mFirstPosition = 0;

22                 sel = fillFromTop(childrenTop);

23                 adjustViewsUpOrDown();

24                 break;

25             case LAYOUT_SPECIFIC:

26                 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);

27                 break;

28             case LAYOUT_MOVE_SELECTION:

29                 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);

30                 break;

31             default:// 默认的布局顺序是从上往下

32                 if (childCount == 0) {

33                     if (!mStackFromBottom) {

34                         final int position = lookForSelectablePosition(0, true);

35                         setSelectedPositionInt(position);

36                         sel = fillFromTop(childrenTop);

37                     } else {

38                         final int position = lookForSelectablePosition(mItemCount - 1, false);

39                         setSelectedPositionInt(position);

40                         sel = fillUp(mItemCount - 1, childrenBottom);

41                     }

42                 } else {

43                     if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {

44                         sel = fillSpecific(mSelectedPosition,

45                                 oldSel == null ? childrenTop : oldSel.getTop());

46                     } else if (mFirstPosition < mItemCount) {

47                         sel = fillSpecific(mFirstPosition,

48                                 oldFirst == null ? childrenTop : oldFirst.getTop());

49                     } else {

50                         sel = fillSpecific(0, childrenTop);

51                     }

52                 }

53                 break;

54             }

55 

56             ...

57     }

继续查找,我们发现layoutChildren的调用者是onFocusChanged、setSelectionInt、父类AbsListView中的onTouchMove、onLayout(这个比较特殊,后文会说明)等,说明当ListView的焦点发生变化时、选中某一项、或者滑动ListView时都会触发ListView的layoutChildren()去布局子元素。

到此为止我们已经很清楚getView的调用时机了,根据掌握的知识点,我们很自然能想到,当初始化一个ListView时,getView的调用也是避免不了的。这是因为ListView在初始化时肯定会绑定一个adapter,即调用语句listview.setAdapter(adapter),我们看一下setAdapter的源码:

 1 @Override

 2     public void setAdapter(ListAdapter adapter) {

 3         if (mAdapter != null && mDataSetObserver != null) {

 4             mAdapter.unregisterDataSetObserver(mDataSetObserver);

 5         }

 6         // 去除原有adapter、观察者、选中项等信息

 7         resetList();

 8         mRecycler.clear();

 9         // 包装adapter,加header或footer,并绑定到当前ListView

10         if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {

11             mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);

12         } else {

13             mAdapter = adapter;

14         }

15         // 重置选中项信息

16         mOldSelectedPosition = INVALID_POSITION;

17         mOldSelectedRowId = INVALID_ROW_ID;

18 

19         // AbsListView#setAdapter will update choice mode states.

20         super.setAdapter(adapter);

21         

22         if (mAdapter != null) {

23             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();

24             mOldItemCount = mItemCount;

25             mItemCount = mAdapter.getCount();

26             checkFocus();

27             // 重新注册观察者

28             mDataSetObserver = new AdapterDataSetObserver();

29             mAdapter.registerDataSetObserver(mDataSetObserver);

30             // 设置回收器中类型不同的View数目,这里与getView的回收机制紧密相关,值得深究

31             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

32             // 设置初始选中项

33             int position;

34             if (mStackFromBottom) {

35                 position = lookForSelectablePosition(mItemCount - 1, false);

36             } else {

37                 position = lookForSelectablePosition(0, true);

38             }

39             setSelectedPositionInt(position);

40             setNextSelectedPositionInt(position);

41 

42             if (mItemCount == 0) {

43                 // Nothing selected

44                 checkSelectionChanged();

45             }

46         } else {

47             mAreAllItemsSelectable = true;

48             checkFocus();

49             // Nothing selected

50             checkSelectionChanged();

51         }

52         // 请求布局重绘

53         requestLayout();

54     }

通读setAdapter源码,我们发现其中并未出现生成新子视图,即调用mAdapter.getView的语句或相关方法,说明此时ListView并未包含子视图。那么疑问来了,ListView是如何在初始化的时候生成子视图的,也就是说第一屏的视图是如何加载到屏幕上的?往后看,我们发现在第53行调用了requestLayout请求布局重绘,我们知道requestLayout最终会去调用onMeasure、onLayout、onDraw方法,因此我们猜测会不会是在onMeasure、onLayout、onDraw某个方法中生成了子视图?

答案是肯定的,AbsListVIew.onLayout过程与普通视图的layout过程完全不同,如下:

1 protected void onLayout(boolean changed, int l, int t, int r, int b) {

2         super.onLayout(changed, l, t, r, b);

3         ...

4         layoutChildren();

5         ...

6     }

该方法调用了layoutChildren();,即重新布局ListView列表视图。

由此说明调用requestLayout可以实现ListView列表视图的重新布局,这里联想到adapter.notifyDataSetChanged也会调用requestLayout,从而都能实现ListView的刷新。

 

 以上过程只是个人探索,并非绝对正确,如有差错敬请批评指正,谢谢。

 参考文献:

Android ListView初始化简单分析

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

android源码解析--ListView(上)

ListView源代码分析

你可能感兴趣的:(view)