20ListView

在Android所有常用的原生控件当中,用法最复杂的应该就是ListView了,它专门用于处理那种内容元素很多,手机屏幕无法展示出所有内容的情况。ListView可以使用列表的形式来展示内容,超出屏幕部分的内容只需要通过手指滑动就可以移动到屏幕内了。

另外ListView还有一个功能,达到成百上千条甚至更多,ListView都不会发生OOM或者崩溃,而且随着我们手指滑动来浏览更多数据时,程序所占用的内存竟然都不会跟着增长。ListView的代码量比较大,复杂度也很高,很难用文字表达清楚。

RecycleBin机制

RecycleBin机制是ListView能够实现成百上千条数据都不会OOM最重要的一个原因。其实RecycleBin的代码并不多,只有300行左右,它是写在AbsListView中的一个内部类,所以所有继承自AbsListView的子类,也就是ListView和GridView,都可以使用这个机制。RecycleBin中的主要代码,如下所示:

[java]  view plain copy
  1. /** 
  2.  * The RecycleBin facilitates reuse of views across layouts. The RecycleBin 
  3.  * has two levels of storage: ActiveViews and ScrapViews. ActiveViews are 
  4.  * those views which were onscreen at the start of a layout. By 
  5.  * construction, they are displaying current information. At the end of 
  6.  * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews 
  7.  * are old views that could potentially be used by the adapter to avoid 
  8.  * allocating views unnecessarily. 
  9.  *  
  10.  * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) 
  11.  * @see android.widget.AbsListView.RecyclerListener 
  12.  */  
  13. class RecycleBin {  
  14.     private RecyclerListener mRecyclerListener;  
  15.   
  16.     /** 
  17.      * The position of the first view stored in mActiveViews. 
  18.      */  
  19.     private int mFirstActivePosition;  
  20.   
  21.     /** 
  22.      * Views that were on screen at the start of layout. This array is 
  23.      * populated at the start of layout, and at the end of layout all view 
  24.      * in mActiveViews are moved to mScrapViews. Views in mActiveViews 
  25.      * represent a contiguous range of Views, with position of the first 
  26.      * view store in mFirstActivePosition. 
  27.      */  
  28.     private View[] mActiveViews = new View[0];  
  29.   
  30.     /** 
  31.      * Unsorted views that can be used by the adapter as a convert view. 
  32.      */  
  33.     private ArrayList<View>[] mScrapViews;  
  34.   
  35.     private int mViewTypeCount;  
  36.   
  37.     private ArrayList<View> mCurrentScrap;  
  38.   
  39.     /** 
  40.      * Fill ActiveViews with all of the children of the AbsListView. 
  41.      *  
  42.      * @param childCount 
  43.      *            The minimum number of views mActiveViews should hold 
  44.      * @param firstActivePosition 
  45.      *            The position of the first view that will be stored in 
  46.      *            mActiveViews 
  47.      */  
  48.     void fillActiveViews(int childCount, int firstActivePosition) {  
  49.         if (mActiveViews.length < childCount) {  
  50.             mActiveViews = new View[childCount];  
  51.         }  
  52.         mFirstActivePosition = firstActivePosition;  
  53.         final View[] activeViews = mActiveViews;  
  54.         for (int i = 0; i < childCount; i++) {  
  55.             View child = getChildAt(i);  
  56.             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();  
  57.             // Don't put header or footer views into the scrap heap  
  58.             if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  59.                 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in  
  60.                 // active views.  
  61.                 // However, we will NOT place them into scrap views.  
  62.                 activeViews[i] = child;  
  63.             }  
  64.         }  
  65.     }  
  66.   
  67.     /** 
  68.      * Get the view corresponding to the specified position. The view will 
  69.      * be removed from mActiveViews if it is found. 
  70.      *  
  71.      * @param position 
  72.      *            The position to look up in mActiveViews 
  73.      * @return The view if it is found, null otherwise 
  74.      */  
  75.     View getActiveView(int position) {  
  76.         int index = position - mFirstActivePosition;  
  77.         final View[] activeViews = mActiveViews;  
  78.         if (index >= 0 && index < activeViews.length) {  
  79.             final View match = activeViews[index];  
  80.             activeViews[index] = null;  
  81.             return match;  
  82.         }  
  83.         return null;  
  84.     }  
  85.   
  86.     /** 
  87.      * Put a view into the ScapViews list. These views are unordered. 
  88.      *  
  89.      * @param scrap 
  90.      *            The view to add 
  91.      */  
  92.     void addScrapView(View scrap) {  
  93.         AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();  
  94.         if (lp == null) {  
  95.             return;  
  96.         }  
  97.         // Don't put header or footer views or views that should be ignored  
  98.         // into the scrap heap  
  99.         int viewType = lp.viewType;  
  100.         if (!shouldRecycleViewType(viewType)) {  
  101.             if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  102.                 removeDetachedView(scrap, false);  
  103.             }  
  104.             return;  
  105.         }  
  106.         if (mViewTypeCount == 1) {  
  107.             dispatchFinishTemporaryDetach(scrap);  
  108.             mCurrentScrap.add(scrap);  
  109.         } else {  
  110.             dispatchFinishTemporaryDetach(scrap);  
  111.             mScrapViews[viewType].add(scrap);  
  112.         }  
  113.   
  114.         if (mRecyclerListener != null) {  
  115.             mRecyclerListener.onMovedToScrapHeap(scrap);  
  116.         }  
  117.     }  
  118.   
  119.     /** 
  120.      * @return A view from the ScrapViews collection. These are unordered. 
  121.      */  
  122.     View getScrapView(int position) {  
  123.         ArrayList<View> scrapViews;  
  124.         if (mViewTypeCount == 1) {  
  125.             scrapViews = mCurrentScrap;  
  126.             int size = scrapViews.size();  
  127.             if (size > 0) {  
  128.                 return scrapViews.remove(size - 1);  
  129.             } else {  
  130.                 return null;  
  131.             }  
  132.         } else {  
  133.             int whichScrap = mAdapter.getItemViewType(position);  
  134.             if (whichScrap >= 0 && whichScrap < mScrapViews.length) {  
  135.                 scrapViews = mScrapViews[whichScrap];  
  136.                 int size = scrapViews.size();  
  137.                 if (size > 0) {  
  138.                     return scrapViews.remove(size - 1);  
  139.                 }  
  140.             }  
  141.         }  
  142.         return null;  
  143.     }  
  144.   
  145.     public void setViewTypeCount(int viewTypeCount) {  
  146.         if (viewTypeCount < 1) {  
  147.             throw new IllegalArgumentException("Can't have a viewTypeCount < 1");  
  148.         }  
  149.         // noinspection unchecked  
  150.         ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];  
  151.         for (int i = 0; i < viewTypeCount; i++) {  
  152.             scrapViews[i] = new ArrayList<View>();  
  153.         }  
  154.         mViewTypeCount = viewTypeCount;  
  155.         mCurrentScrap = scrapViews[0];  
  156.         mScrapViews = scrapViews;  
  157.     }  
  158.   
  159. }  

  160. view plaincopy
  1. /** 
  2.  * Subclasses should NOT override this method but {@link #layoutChildren()} 
  3.  * instead. 
  4.  */  
  5. @Override  
  6. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  7.     super.onLayout(changed, l, t, r, b);  
  8.     mInLayout = true;  
  9.     if (changed) {  
  10.         int childCount = getChildCount();  
  11.         for (int i = 0; i < childCount; i++) {  
  12.             getChildAt(i).forceLayout();  
  13.         }  
  14.         mRecycler.markChildrenDirty();  
  15.     }  
  16.     layoutChildren();  
  17.     mInLayout = false;  
  18. }  
ListView的layoutChildren()方法,代码如下所示:
[java]  view plain copy
  1. @Override  
  2. protected void layoutChildren() {  
  3.     final boolean blockLayoutRequests = mBlockLayoutRequests;  
  4.     if (!blockLayoutRequests) {  
  5.         mBlockLayoutRequests = true;  
  6.     } else {  
  7.         return;  
  8.     }  
  9.     try {  
  10.         super.layoutChildren();  
  11.         invalidate();  
  12.         if (mAdapter == null) {  
  13.             resetList();  
  14.             invokeOnItemScrollListener();  
  15.             return;  
  16.         }  
  17.         int childrenTop = mListPadding.top;  
  18.         int childrenBottom = getBottom() - getTop() - mListPadding.bottom;  
  19.         int childCount = getChildCount();  
  20.         int index = 0;  
  21.         int delta = 0;  
  22.         View sel;  
  23.         View oldSel = null;  
  24.         View oldFirst = null;  
  25.         View newSel = null;  
  26.         View focusLayoutRestoreView = null;  
  27.         // Remember stuff we will need down below  
  28.         switch (mLayoutMode) {  
  29.         case LAYOUT_SET_SELECTION:  
  30.             index = mNextSelectedPosition - mFirstPosition;  
  31.             if (index >= 0 && index < childCount) {  
  32.                 newSel = getChildAt(index);  
  33.             }  
  34.             break;  
  35.         case LAYOUT_FORCE_TOP:  
  36.         case LAYOUT_FORCE_BOTTOM:  
  37.         case LAYOUT_SPECIFIC:  
  38.         case LAYOUT_SYNC:  
  39.             break;  
  40.         case LAYOUT_MOVE_SELECTION:  
  41.         default:  
  42.             // Remember the previously selected view  
  43.             index = mSelectedPosition - mFirstPosition;  
  44.             if (index >= 0 && index < childCount) {  
  45.                 oldSel = getChildAt(index);  
  46.             }  
  47.             // Remember the previous first child  
  48.             oldFirst = getChildAt(0);  
  49.             if (mNextSelectedPosition >= 0) {  
  50.                 delta = mNextSelectedPosition - mSelectedPosition;  
  51.             }  
  52.             // Caution: newSel might be null  
  53.             newSel = getChildAt(index + delta);  
  54.         }  
  55.         boolean dataChanged = mDataChanged;  
  56.         if (dataChanged) {  
  57.             handleDataChanged();  
  58.         }  
  59.         // Handle the empty set by removing all views that are visible  
  60.         // and calling it a day  
  61.         if (mItemCount == 0) {  
  62.             resetList();  
  63.             invokeOnItemScrollListener();  
  64.             return;  
  65.         } else if (mItemCount != mAdapter.getCount()) {  
  66.             throw new IllegalStateException("The content of the adapter has changed but "  
  67.                     + "ListView did not receive a notification. Make sure the content of "  
  68.                     + "your adapter is not modified from a background thread, but only "  
  69.                     + "from the UI thread. [in ListView(" + getId() + ", " + getClass()   
  70.                     + ") with Adapter(" + mAdapter.getClass() + ")]");  
  71.         }  
  72.         setSelectedPositionInt(mNextSelectedPosition);  
  73.         // Pull all children into the RecycleBin.  
  74.         // These views will be reused if possible  
  75.         final int firstPosition = mFirstPosition;  
  76.         final RecycleBin recycleBin = mRecycler;  
  77.         // reset the focus restoration  
  78.         View focusLayoutRestoreDirectChild = null;  
  79.         // Don't put header or footer views into the Recycler. Those are  
  80.         // already cached in mHeaderViews;  
  81.         if (dataChanged) {  
  82.             for (int i = 0; i < childCount; i++) {  
  83.                 recycleBin.addScrapView(getChildAt(i));  
  84.                 if (ViewDebug.TRACE_RECYCLER) {  
  85.                     ViewDebug.trace(getChildAt(i),  
  86.                             ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);  
  87.                 }  
  88.             }  
  89.         } else {  
  90.             recycleBin.fillActiveViews(childCount, firstPosition);  
  91.         }  
  92.         // take focus back to us temporarily to avoid the eventual  
  93.         // call to clear focus when removing the focused child below  
  94.         // from messing things up when ViewRoot assigns focus back  
  95.         // to someone else  
  96.         final View focusedChild = getFocusedChild();  
  97.         if (focusedChild != null) {  
  98.             // TODO: in some cases focusedChild.getParent() == null  
  99.             // we can remember the focused view to restore after relayout if the  
  100.             // data hasn't changed, or if the focused position is a header or footer  
  101.             if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {  
  102.                 focusLayoutRestoreDirectChild = focusedChild;  
  103.                 // remember the specific view that had focus  
  104.                 focusLayoutRestoreView = findFocus();  
  105.                 if (focusLayoutRestoreView != null) {  
  106.                     // tell it we are going to mess with it  
  107.                     focusLayoutRestoreView.onStartTemporaryDetach();  
  108.                 }  
  109.             }  
  110.             requestFocus();  
  111.         }  
  112.         // Clear out old views  
  113.         detachAllViewsFromParent();  
  114.         switch (mLayoutMode) {  
  115.         case LAYOUT_SET_SELECTION:  
  116.             if (newSel != null) {  
  117.                 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);  
  118.             } else {  
  119.                 sel = fillFromMiddle(childrenTop, childrenBottom);  
  120.             }  
  121.             break;  
  122.         case LAYOUT_SYNC:  
  123.             sel = fillSpecific(mSyncPosition, mSpecificTop);  
  124.             break;  
  125.         case LAYOUT_FORCE_BOTTOM:  
  126.             sel = fillUp(mItemCount - 1, childrenBottom);  
  127.             adjustViewsUpOrDown();  
  128.             break;  
  129.         case LAYOUT_FORCE_TOP:  
  130.             mFirstPosition = 0;  
  131.             sel = fillFromTop(childrenTop);  
  132.             adjustViewsUpOrDown();  
  133.             break;  
  134.         case LAYOUT_SPECIFIC:  
  135.             sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);  
  136.             break;  
  137.         case LAYOUT_MOVE_SELECTION:  
  138.             sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);  
  139.             break;  
  140.         default:  
  141.             if (childCount == 0) {  
  142.                 if (!mStackFromBottom) {  
  143.                     final int position = lookForSelectablePosition(0true);  
  144.                     setSelectedPositionInt(position);  
  145.                     sel = fillFromTop(childrenTop);  
  146.                 } else {  
  147.                     final int position = lookForSelectablePosition(mItemCount - 1false);  
  148.                     setSelectedPositionInt(position);  
  149.                     sel = fillUp(mItemCount - 1, childrenBottom);  
  150.                 }  
  151.             } else {  
  152.                 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {  
  153.                     sel = fillSpecific(mSelectedPosition,  
  154.                             oldSel == null ? childrenTop : oldSel.getTop());  
  155.                 } else if (mFirstPosition < mItemCount) {  
  156.                     sel = fillSpecific(mFirstPosition,  
  157.                             oldFirst == null ? childrenTop : oldFirst.getTop());  
  158.                 } else {  
  159.                     sel = fillSpecific(0, childrenTop);  
  160.                 }  
  161.             }  
  162.             break;  
  163.         }  
  164.         // Flush any cached views that did not get reused above  
  165.         recycleBin.scrapActiveViews();  
  166.         if (sel != null) {  
  167.             // the current selected item should get focus if items  
  168.             // are focusable  
  169.             if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {  
  170.                 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&  
  171.                         focusLayoutRestoreView.requestFocus()) || sel.requestFocus();  
  172.                 if (!focusWasTaken) {  
  173.                     // selected item didn't take focus, fine, but still want  
  174.                     // to make sure something else outside of the selected view  
  175.                     // has focus  
  176.                     final View focused = getFocusedChild();  
  177.                     if (focused != null) {  
  178.                         focused.clearFocus();  
  179.                     }  
  180.                     positionSelector(sel);  
  181.                 } else {  
  182.                     sel.setSelected(false);  
  183.                     mSelectorRect.setEmpty();  
  184.                 }  
  185.             } else {  
  186.                 positionSelector(sel);  
  187.             }  
  188.             mSelectedTop = sel.getTop();  
  189.         } else {  
  190.             if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {  
  191.                 View child = getChildAt(mMotionPosition - mFirstPosition);  
  192.                 if (child != null) positionSelector(child);  
  193.             } else {  
  194.                 mSelectedTop = 0;  
  195.                 mSelectorRect.setEmpty();  
  196.             }  
  197.             // even if there is not selected position, we may need to restore  
  198.             // focus (i.e. something focusable in touch mode)  
  199.             if (hasFocus() && focusLayoutRestoreView != null) {  
  200.                 focusLayoutRestoreView.requestFocus();  
  201.             }  
  202.         }  
  203.         // tell focus view we are done mucking with it, if it is still in  
  204.         // our view hierarchy.  
  205.         if (focusLayoutRestoreView != null  
  206.                 && focusLayoutRestoreView.getWindowToken() != null) {  
  207.             focusLayoutRestoreView.onFinishTemporaryDetach();  
  208.         }  
  209.         mLayoutMode = LAYOUT_NORMAL;  
  210.         mDataChanged = false;  
  211.         mNeedSync = false;  
  212.         setNextSelectedPositionInt(mSelectedPosition);  
  213.         updateScrollIndicators();  
  214.         if (mItemCount > 0) {  
  215.             checkSelectionChanged();  
  216.         }  
  217.         invokeOnItemScrollListener();  
  218.     } finally {  
  219.         if (!blockLayoutRequests) {  
  220.             mBlockLayoutRequests = false;  
  221.         }  
  222.     }  
  223. }  
fillDown()方法,代码如下所示:
[java]  view plain copy
  1. /** 
  2.  * Fills the list from pos down to the end of the list view. 
  3.  * 
  4.  * @param pos The first position to put in the list 
  5.  * 
  6.  * @param nextTop The location where the top of the item associated with pos 
  7.  *        should be drawn 
  8.  * 
  9.  * @return The view that is currently selected, if it happens to be in the 
  10.  *         range that we draw. 
  11.  */  
  12. private View fillDown(int pos, int nextTop) {  
  13.     View selectedView = null;  
  14.     int end = (getBottom() - getTop()) - mListPadding.bottom;  
  15.     while (nextTop < end && pos < mItemCount) {  
  16.         // is this the selected item?  
  17.         boolean selected = pos == mSelectedPosition;  
  18.         View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);  
  19.         nextTop = child.getBottom() + mDividerHeight;  
  20.         if (selected) {  
  21.             selectedView = child;  
  22.         }  
  23.         pos++;  
  24.     }  
  25.     return selectedView;  
  26. }  
makeAndAddView()方法代码如下所示:
[java]  view plain copy
  1. /** 
  2.  * Obtain the view and add it to our list of children. The view can be made 
  3.  * fresh, converted from an unused view, or used as is if it was in the 
  4.  * recycle bin. 
  5.  * 
  6.  * @param position Logical position in the list 
  7.  * @param y Top or bottom edge of the view to add 
  8.  * @param flow If flow is true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @return View that was added 
  13.  */  
  14. private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,  
  15.         boolean selected) {  
  16.     View child;  
  17.     if (!mDataChanged) {  
  18.         // Try to use an exsiting view for this position  
  19.         child = mRecycler.getActiveView(position);  
  20.         if (child != null) {  
  21.             // Found it -- we're using an existing child  
  22.             // This just needs to be positioned  
  23.             setupChild(child, position, y, flow, childrenLeft, selected, true);  
  24.             return child;  
  25.         }  
  26.     }  
  27.     // Make a new view for this position, or convert an unused view if possible  
  28.     child = obtainView(position, mIsScrap);  
  29.     // This needs to be positioned and measured  
  30.     setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);  
  31.     return child;  
  32. }  

getView()方法接受的三个参数,第一个参数position代表当前子元素的的位置,我们可以通过具体的位置来获取与其相关的数据。第二个参数convertView,刚才传入的是null,说明没有convertView可以利用,因此我们会调用LayoutInflater的inflate()方法来去加载一个布局。接下来会对这个view进行一些属性和值的设定,最后将view返回。

那么这个View也会作为obtainView()的结果进行返回,并最终传入到setupChild()方法当中。其实也就是说,第一次layout过程当中,所有的子View都是调用LayoutInflater的inflate()方法加载出来的,这样就会相对比较耗时,但是不用担心,后面就不会再有这种情况了,那么我们继续往下看:

[java]  view plain copy
  1. /** 
  2.  * Add a view as a child and make sure it is measured (if necessary) and 
  3.  * positioned properly. 
  4.  * 
  5.  * @param child The view to add 
  6.  * @param position The position of this child 
  7.  * @param y The y position relative to which this view will be positioned 
  8.  * @param flowDown If true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @param recycled Has this view been pulled from the recycle bin? If so it 
  13.  *        does not need to be remeasured. 
  14.  */  
  15. private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,  
  16.         boolean selected, boolean recycled) {  
  17.     final boolean isSelected = selected && shouldShowSelector();  
  18.     final boolean updateChildSelected = isSelected != child.isSelected();  
  19.     final int mode = mTouchMode;  
  20.     final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&  
  21.             mMotionPosition == position;  
  22.     final boolean updateChildPressed = isPressed != child.isPressed();  
  23.     final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();  
  24.     // Respect layout params that are already in the view. Otherwise make some up...  
  25.     // noinspection unchecked  
  26.     AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();  
  27.     if (p == null) {  
  28.         p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
  29.                 ViewGroup.LayoutParams.WRAP_CONTENT, 0);  
  30.     }  
  31.     p.viewType = mAdapter.getItemViewType(position);  
  32.     if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&  
  33.             p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {  
  34.         attachViewToParent(child, flowDown ? -1 : 0, p);  
  35.     } else {  
  36.         p.forceAdd = false;  
  37.         if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  38.             p.recycledHeaderFooter = true;  
  39.         }  
  40.         addViewInLayout(child, flowDown ? -1 : 0, p, true);  
  41.     }  
  42.     if (updateChildSelected) {  
  43.         child.setSelected(isSelected);  
  44.     }  
  45.     if (updateChildPressed) {  
  46.         child.setPressed(isPressed);  
  47.     }  
  48.     if (needToMeasure) {  
  49.         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,  
  50.                 mListPadding.left + mListPadding.right, p.width);  
  51.         int lpHeight = p.height;  
  52.         int childHeightSpec;  
  53.         if (lpHeight > 0) {  
  54.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);  
  55.         } else {  
  56.             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  57.         }  
  58.         child.measure(childWidthSpec, childHeightSpec);  
  59.     } else {  
  60.         cleanupLayoutState(child);  
  61.     }  
  62.     final int w = child.getMeasuredWidth();  
  63.     final int h = child.getMeasuredHeight();  
  64.     final int childTop = flowDown ? y : y - h;  
  65.     if (needToMeasure) {  
  66.         final int childRight = childrenLeft + w;  
  67.         final int childBottom = childTop + h;  
  68.         child.layout(childrenLeft, childTop, childRight, childBottom);  
  69.     } else {  
  70.         child.offsetLeftAndRight(childrenLeft - child.getLeft());  
  71.         child.offsetTopAndBottom(childTop - child.getTop());  
  72.     }  
  73.     if (mCachingStarted && !child.isDrawingCacheEnabled()) {  
  74.         child.setDrawingCacheEnabled(true);  
  75.     }  
  76. }  
在编写过程中,也有出现乱码,错误,后来在不断试验下改正错误。最后结果还是可以运行的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值