背景
在文章安卓开发学习之ListView的布局流程源码阅读中,我记录了对ListView布局过程源码的阅读。现在,我记录一下ListView测绘过程最后一个步骤--绘制流程的源码阅读。
其实ListView的绘制主要是在自己覆写的方法dispatchView()中,而且只是画一些分割线,至于如何画子view,请参考文章安卓开发学习之View的draw(canvas)方法
ListView#dispatchDraw
源码如下
@Override protected void dispatchDraw(Canvas canvas) { if (mCachingStarted) { mCachingActive = true; } // Draw the dividers // listView的绘制只是绘制分割线和滑动过度时上下的header final int dividerHeight = mDividerHeight; final Drawable overscrollHeader = mOverScrollHeader; final Drawable overscrollFooter = mOverScrollFooter; final boolean drawOverscrollHeader = overscrollHeader != null; final boolean drawOverscrollFooter = overscrollFooter != null; final boolean drawDividers = dividerHeight > 0 && mDivider != null; if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) { // Only modify the top and bottom in the loop, we set the left and right here final Rect bounds = mTempRect; bounds.left = mPaddingLeft; bounds.right = mRight - mLeft - mPaddingRight; final int count = getChildCount(); final int headerCount = getHeaderViewsCount(); // header的数量 final int itemCount = mItemCount; final int footerLimit = (itemCount - mFooterViewInfos.size()); // footer开始的索引 final boolean headerDividers = mHeaderDividersEnabled; final boolean footerDividers = mFooterDividersEnabled; final int first = mFirstPosition; final boolean areAllItemsSelectable = mAreAllItemsSelectable; final ListAdapter adapter = mAdapter; // If the list is opaque *and* the background is not, we want to // fill a rect where the dividers would be for non-selectable items // If the list is opaque and the background is also opaque, we don't // need to draw anything since the background will do it for us final boolean fillForMissingDividers = isOpaque() && !super.isOpaque(); if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) { mDividerPaint = new Paint(); mDividerPaint.setColor(getCacheColorHint()); } final Paint paint = mDividerPaint; int effectivePaddingTop = 0; int effectivePaddingBottom = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { effectivePaddingTop = mListPadding.top; effectivePaddingBottom = mListPadding.bottom; } final int listBottom = mBottom - mTop - effectivePaddingBottom + mScrollY; // 更新list底部位置:有效长度+纵向滑动的距离 if (!mStackFromBottom) { // 从上而下绘制 int bottom = 0; // Draw top divider or header for overscroll // 画上面的分割线或者画上滑过度的header final int scrollY = mScrollY; if (count > 0 && scrollY < 0) { // scrollY < 0,表示向上滑 if (drawOverscrollHeader) { // 判断有没有设置上滑过度的header bounds.bottom = 0; bounds.top = scrollY; drawOverscrollHeader(canvas, overscrollHeader, bounds); // 有的话绘制上滑过度时,出现在list上面的header } else if (drawDividers) { bounds.bottom = 0; bounds.top = -dividerHeight; // 矩形高度是负的dividerHeight,意为top在bottom的上方 drawDivider(canvas, bounds, -1); // 否则就只是画分割线 } } // 为每一个子view画分割线 for (int i = 0; i < count; i++) { final int itemIndex = (first + i); final boolean isHeader = (itemIndex < headerCount); // 是不是header final boolean isFooter = (itemIndex >= footerLimit); // 是不是footer if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) { // 给每一个不是header也不是footer的子view绘制分割线,或者header和footer的divider使能 final View child = getChildAt(i); bottom = child.getBottom(); final boolean isLastItem = (i == (count - 1)); if (drawDividers && (bottom < listBottom) && !(drawOverscrollFooter && isLastItem)) { // 不到底部,而且也不是最后一项或不需要画下滑过度时的footer final int nextIndex = (itemIndex + 1); // Draw dividers between enabled items, headers // and/or footers when enabled and requested, and // after the last enabled item. if (adapter.isEnabled(itemIndex) && (headerDividers || !isHeader && (nextIndex >= headerCount)) && (isLastItem || adapter.isEnabled(nextIndex) && (footerDividers || !isFooter && (nextIndex < footerLimit)))) { /* 判断分支:1、当前子view是enable的 2、不是header或header的Divider使能,而且下一个子view不是header 3、3.1、当前是最后一个子view 3.2、下一个子view是enable的 3.3、footer的divider使能,或当前不是footer,并且下一个子view也不是footer 当(3.1 || 3.2 && 3.3) = true时,3为true 当1 && 2 && 3为true时,判断成立 */ bounds.top = bottom; bounds.bottom = bottom + dividerHeight; drawDivider(canvas, bounds, i); // 正常地画divider } else if (fillForMissingDividers) { bounds.top = bottom; bounds.bottom = bottom + dividerHeight; canvas.drawRect(bounds, paint); // 否则就画一个矩形,分割header、footer和正文 } } } } final int overFooterBottom = mBottom + mScrollY; // 下滑过度时的footer位置,保持最底部 if (drawOverscrollFooter && first + count == itemCount && overFooterBottom > bottom) { bounds.top = bottom; // 此时bottom时最后一个子view的底部 bounds.bottom = overFooterBottom; drawOverscrollFooter(canvas, overscrollFooter, bounds); // 绘制下滑过度时,出现的footer } } else { .. // 逆序展示 } } // Draw the indicators (these should be drawn above the dividers) and children super.dispatchDraw(canvas); }
逻辑并不复杂,耐心看注释和代码就可以,最后调用了父类AbsListView.dispatchDraw()方法,代码如下
@Override protected void dispatchDraw(Canvas canvas) { int saveCount = 0; final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { saveCount = canvas.save(); // 保存当前画布 final int scrollX = mScrollX; final int scrollY = mScrollY; canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, scrollX + mRight - mLeft - mPaddingRight, scrollY + mBottom - mTop - mPaddingBottom); mGroupFlags &= ~CLIP_TO_PADDING_MASK; } final boolean drawSelectorOnTop = mDrawSelectorOnTop; if (!drawSelectorOnTop) { // 是否在所有子View上面绘制selector(选中后的背景),是的话,选中背景覆盖所有子View,否则所有子View覆盖选中背景 drawSelector(canvas); } super.dispatchDraw(canvas); // 正式给子view绘制下发流程 if (drawSelectorOnTop) { drawSelector(canvas); } if (clipToPadding) { // 恢复画布 canvas.restoreToCount(saveCount); mGroupFlags |= CLIP_TO_PADDING_MASK; } }
AbsListView再调用super.dispatchDraw(),就到了ViewGroup的同名方法,关于它的源码阅读,请参见文章安卓开发学习之View的draw(canvas)方法
结语
看来,ListView的绘制很简单,只是画了所有分割线和大的selector,而子view自己的绘制,就交给了祖宗ViewGroup来进行