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

先来看ListView类中的makeAndAddView方法:

 1 /**
 2      * 获取视图填充到列表的item中去,视图可以是从未使用过的视图转换过来,也可以是从回收站复用的视图。
 3      * 在该方法中,先查找是否有可重用视图,如果有,使用可重用视图。
 4      * 然后通过obtainView方法获取一个view(有可能是从未使用视图转换过来
 5      * (obtainView方法是在AbsListView方法中定义)),再重新测量和定位View。
 6      * Obtain the view and add it to our list of children. The view can be made
 7      * fresh, converted from an unused view, or used as is if it was in the
 8      * recycle bin.
 9      *
10      * @param position Logical position in the list
11      * @param y Top or bottom edge of the view to add
12      * @param flow If flow is true, align top edge to y. If false, align bottom
13      *        edge to y.
14      * @param childrenLeft Left edge where children should be positioned
15      * @param selected Is this position selected?
16      * @return View that was added
17      */
18     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
19             boolean selected) {
20         View child;
21
22
23         if (!mDataChanged) {
24             // Try to use an existing view for this position
25             child = mRecycler.getActiveView(position);
26             if (child != null) {
27                 if (ViewDebug.TRACE_RECYCLER) {
28                     ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
29                             position, getChildCount());
30                 }
31
32                 // Found it -- we‘re using an existing child
33                 // This just needs to be positioned
34                 setupChild(child, position, y, flow, childrenLeft, selected, true);
35
36                 return child;
37             }
38         }
39
40         // Make a new view for this position, or convert an unused view if possible
41         child = obtainView(position, mIsScrap);
42
43         // This needs to be positioned and measured
44         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
45
46         return child;
47     }

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

 1 /**
 2      * Get a view and have it show the data associated with the specified
 3      * position. This is called when we have already discovered that the view is
 4      * not available for reuse in the recycle bin. The only choices left are
 5      * converting an old view or making a new one.
 6      *
 7      * @param position The position to display
 8      * @param isScrap Array of at least 1 boolean, the first entry will become true if
 9      *                the returned view was taken from the scrap heap, false if otherwise.
10      *
11      * @return A view displaying the data associated with the specified position
12      */
13     View obtainView(int position, boolean[] isScrap) {
14         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView");
15
16         isScrap[0] = false;
17         View scrapView;
18
19         scrapView = mRecycler.getTransientStateView(position);
20         if (scrapView == null) {
21             // 从回收站回收一个View
22             scrapView = mRecycler.getScrapView(position);
23         }
24
25         View child;
26         if (scrapView != null) {
27             // 这里调用了getView!注意,第二个参数也就是convertView传    入的是刚才从回收站中回收的View(如果有的话)
28             child = mAdapter.getView(position, scrapView, this);
29
30             if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
31                 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
32             }
33
34             if (child != scrapView) {
35                 mRecycler.addScrapView(scrapView, position);
36                 if (mCacheColorHint != 0) {
37                     child.setDrawingCacheBackgroundColor(mCacheColorHint);
38                 }
39             } else {
40                 isScrap[0] = true;
41
42                 // Clear any system-managed transient state so that we can
43                 // recycle this view and bind it to different data.
44                 if (child.isAccessibilityFocused()) {
45                     child.clearAccessibilityFocus();
46                 }
47
48                 child.dispatchFinishTemporaryDetach();
49             }
50         } else {
51             child = mAdapter.getView(position, null, this);
52
53             if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
54                 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
55             }
56
57             if (mCacheColorHint != 0) {
58                 child.setDrawingCacheBackgroundColor(mCacheColorHint);
59             }
60         }
61
62         if (mAdapterHasStableIds) {
63             final ViewGroup.LayoutParams vlp = child.getLayoutParams();
64             LayoutParams lp;
65             if (vlp == null) {
66                 lp = (LayoutParams) generateDefaultLayoutParams();
67             } else if (!checkLayoutParams(vlp)) {
68                 lp = (LayoutParams) generateLayoutParams(vlp);
69             } else {
70                 lp = (LayoutParams) vlp;
71             }
72             lp.itemId = mAdapter.getItemId(position);
73             child.setLayoutParams(lp);
74         }
75
76         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
77             if (mAccessibilityDelegate == null) {
78                 mAccessibilityDelegate = new ListItemAccessibilityDelegate();
79             }
80             if (child.getAccessibilityDelegate() == null) {
81                 child.setAccessibilityDelegate(mAccessibilityDelegate);
82             }
83         }
84
85         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
86
87         return child;
88     }    

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

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

我们首先来看ListView中的fillDown:

 1 /**
 2     填充从pos到list底部所有的item。里面调用到了makeAndAddView方    法:
 3      * Fills the list from pos down to the end of the list view.
 4      *
 5      * @param pos The first position to put in the list
 6      *
 7      * @param nextTop The location where the top of the item associated with pos
 8      *        should be drawn
 9      *
10      * @return The view that is currently selected, if it happens to be in the
11      *         range that we draw.
12      */
13     private View fillDown(int pos, int nextTop) {
14         View selectedView = null;
15
16         int end = (mBottom - mTop);
17         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
18             end -= mListPadding.bottom;
19         }
20
21         while (nextTop < end && pos < mItemCount) {
22             // is this the selected item?
23             boolean selected = pos == mSelectedPosition;
24             View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
25
26             nextTop = child.getBottom() + mDividerHeight;
27             if (selected) {
28                 selectedView = child;
29             }
30             pos++;
31         }
32
33         return selectedView;
34     }

有fillDown自然就有fillUp:

 1 /**
 2      * Fills the list from pos up to the top of the list view.
 3      *
 4      * @param pos The first position to put in the list
 5      *
 6      * @param nextBottom The location where the bottom of the item associated
 7      *        with pos should be drawn
 8      *
 9      * @return The view that is currently selected
10      */
11     private View fillUp(int pos, int nextBottom) {
12         View selectedView = null;
13
14         int end = 0;
15         if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
16             end = mListPadding.top;
17         }
18
19         while (nextBottom > end && pos >= 0) {
20             // is this the selected item?
21             boolean selected = pos == mSelectedPosition;
22             View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
23             nextBottom = child.getTop() - mDividerHeight;
24             if (selected) {
25                 selectedView = child;
26             }
27             pos--;
28         }
29
30         mFirstPosition = pos + 1;
31
32         return selectedView;
33     }

还有fillFromTop、fillFromMiddle、fillAboveAndBelow、fillFromSelection等,这些方法都是用来填充ListView的,区别是参照的起始位置不同而已。

好了,现在填充的方法有了,那么谁来触发这些方法呢?

通过查找ListView源码,发现刚才的那些填充方法在layoutChildren()中基本上都被调用到,而layoutChildren的调用者是onFocusChanged、setSelectionInt、父类AbsListView中的onTouchMove等,说明当ListView的焦点发生变化时、选中某一项、或者滑动ListView时都会触发ListView的layoutChildren()重新装载应当显示的item。

 1 @Override
 2     protected void layoutChildren() {
 3         final boolean blockLayoutRequests = mBlockLayoutRequests;
 4         if (blockLayoutRequests) {
 5             return;
 6         }
 7
 8         mBlockLayoutRequests = true;
 9
10         try {
11             super.layoutChildren();
12
13             invalidate();

我们仔细阅读layoutChildren()方法,发现在第13行,调用了invalidate();,也就是重绘了ListView。

到此为止我们已经很清楚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
 7         resetList();
 8         mRecycler.clear();
 9
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
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最终会去调用ListView或父类的onMeasure、onLayout、onDraw方法,因此我们猜测会不会是在onMeasure、onLayout、onDraw某个方法中生成了子视图?

答案是肯定的,ListView.onMeasure中有AbsListView.obtainView的调用语句(这里还需深入研究,因为涉及到ListView高度探测),也即生成了新的子视图。此外,ListVIew.onLayout过程与普通视图的layout过程完全不同,该方法调用了layoutChildren();,即对ListView中的子视图进行重新布局,具体过程请参考《Android ListView初始化简单分析》一文。

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

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

参考文献:

Android ListView初始化简单分析

android源码解析--ListView(上)

ListView源代码分析

时间: 2024-10-16 14:58:12

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

Android 源码系列之&lt;四&gt;从源码的角度深入理解LayoutInflater.Factory之主题切换(上)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/51252401 现在越来越多的APP都加入了主题切换功能或者是日间模式和夜间模式功能切换等,这些功能不仅增加了用户体验也增强了用户好感,众所周知QQ和网易新闻的APP做的用户体验都非常好,它们也都有日间模式和夜间模式的主题切换功能.体验过它们的主题切换后你会发现大部分效果是更换相关背景图片.背景颜色.字体颜色等来完成的,网上这篇文章对主题切换讲解的比较不错,今天我们从源码的角度来学习一下

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

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴趣的朋友可以参考 Android Handler.Message完全解析,带你从源码的角度彻底理解 . 不过为了更加方便我们在子线程中更新UI元素,Android从1.

Android 源码系列之&lt;十一&gt;从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(下)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52843637 在上篇文章Android 源码系列之<十>从源码的角度深入理解AccessibilityService,打造自己的APP小外挂(上)中我们讲解了通过AccessibilityService实现自动安装APK小外挂的操作流程,如果你还没有看过上篇文章请点击这里.在这篇文章中我将带领小伙伴从源码的角度来深入学习一下AccessibilityServie的技术实现原理,希望这

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

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用Ima

[学习总结]7、Android AsyncTask完全解析,带你从源码的角度彻底理解

我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴趣的朋友可以参考 Android Handler.Message完全解析,带你从源码的角度彻底理解 . 不过为了更加方便我们在子线程中更新UI元素,Android从1.5版本就引入了一个AsyncTask类,使用它就可以非常灵活方便地从子线程切换到UI线程,我们本篇文章的主角也就正是它了. Asyn

[学习总结]6、Android异步消息处理机制完全解析,带你从源码的角度彻底理解

开始进入正题,我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃.相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了. 这种处理方式被称为异步消息处理线程,虽然我相信大家都会用,可是你知道它背后的原理是什么样的吗?今天我们就来一起

Android 源码系列之&lt;十四&gt;从源码的角度深入理解LeakCanary的内存泄露检测机制(下)

转载请注明出处:http://blog.csdn.net/llew2011/article/details/52958567 在上边文章Android 源码系列之<十三>从源码的角度深入理解LeakCanary的内存泄露检测机制(中)由于篇幅原因仅仅向小伙伴们讲述了在Android开发中如何使用LeakCanary来检测应用中出现的内存泄露,并简单的介绍了LeakCanary的相关配置信息.根据上篇文章的介绍我们知道LeakCanary为了不给APP进程造成影响所以新开启了一个进程,在新开启的

从Java源码的角度来分析HashMap与HashTable的区别

由于HashMap与HashTable都是用来存储Key-Value的键值对,所以经常拿来对比二者的区别,下面就从源码的角度来分析一下HashMap与HashTable的区别, 首先介绍一下两者的区别,然后再从源码分析. HahMap与HahTable两者主要区别: 1.继承的父类不同 <span style="font-size:18px;">public class HashMap<K, V> extends AbstractMap<K, V>

Android 源码系列之&lt;七&gt;从源码的角度深入理解IntentService及HandlerThread

转载请注明出处:http://blog.csdn.net/llew2011/article/details/51373243 提起Service大家都很熟悉,它乃Android四(si)大(da)组(jing)件(gang)之一.但是说起IntentService有童靴或许有点陌生,看名字感觉和Service有关连.不错,不仅有关联而且关系还不一般,IntentService是Service的子类,所以它也是正宗的Service,由于IntentService借助了HandlerThread,我