安卓开发学习之ListView的绘制流程源码阅读

背景

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


猜你喜欢

转载自blog.csdn.net/qq_37475168/article/details/80356941