背景
在文章安卓开发学习之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来进行