• 何时调用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源代码分析

  • 相关阅读:
    数字证书
    数字签名

    共享密钥加密
    公开密钥加密
    迪菲赫尔曼密钥交换
    线性查找
    深度优先搜索
    广度优先搜索
    混合加密
  • 原文地址:https://www.cnblogs.com/nailperry/p/4671951.html
Copyright © 2020-2023  润新知