码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-07-23 23:28:01      阅读:452      评论:0      收藏:0      [点我收藏+]

标签:

先来看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源代码分析

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

标签:

原文地址:http://www.cnblogs.com/nailperry/p/4671951.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!