前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >View 绘制源码分析

View 绘制源码分析

作者头像
Yif
发布2019-12-26 15:05:35
3560
发布2019-12-26 15:05:35
举报
文章被收录于专栏:Android 进阶Android 进阶
undefined
undefined
img
img

Measure 分析

这里的measure方法为final 所以不可重写,该方法主要是用来计算出view自身的实际大小,并设置宽高。

view的实际大小是由父view与自身决定的,只能通过重写方法onMeasure来获取

代码语言:javascript
复制
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int oWidth  = insets.left + insets.right;
        int oHeight = insets.top  + insets.bottom;
        widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
        heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
    }
 
    // Suppress sign extension for the low bytes
    long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
    if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
 
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
            || heightMeasureSpec != mOldHeightMeasureSpec;
    final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
            && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
            && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    final boolean needsLayout = specChanged
            && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
 
    if (forceLayout || needsLayout) {
        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
 
        resolveRtlPropertiesIfNeeded();
 
        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
//如果当前flags不为forceLayou或者与上次测量规格相比没有改变,就不进行重新测量,直接从缓存中获取
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } else {
            long value = mMeasureCache.valueAt(cacheIndex);
            // Casting a long to int drops the high 32 bits, no mask needed
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
            mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
 
        // flag not set, setMeasuredDimension() was not invoked, we raise
        // an exception to warn the developer
        if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
            throw new IllegalStateException("View with id " + getId() + ": "
                    + getClass().getName() + "#onMeasure() did not set the"
                    + " measured dimension by calling"
                    + " setMeasuredDimension()");
        }
 
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
 
    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;
//存入measure缓存数组中
    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

onMeasure

该方法的两个参数由两部分组成,高2位表示mode也就是测量模式,低30位表示size也就是测量大小。 这里模式对应EXACTLY表示确定大小、AT_MOST表示最大大小、UNSPECIFIED表示不确定大小,并且它不受父布局控制。通过阅读源码得知在PhoneWindowDecorView中对应的模式为EXACTLY,大小size就是对应屏幕的尺寸,而view大小是由父view与子view共同决定

代码语言:javascript
复制
//这里的两个参数都是由父布局传进来的
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
    boolean optical = isLayoutModeOptical(this);
    if (optical != isLayoutModeOptical(mParent)) {
        Insets insets = getOpticalInsets();
        int opticalWidth  = insets.left + insets.right;
        int opticalHeight = insets.top  + insets.bottom;
 
        measuredWidth  += optical ? opticalWidth  : -opticalWidth;
        measuredHeight += optical ? opticalHeight : -opticalHeight;
    }
    setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
 
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
//如果当前mode为EXACTLY或AT_MOST,就返回specSize,这是系统默认的风格
    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

建议的最小宽或高,是由背景的尺寸与设置view最小宽高共同决定的。当背景为空时,就是自身最小宽高,否则就是与背景的最大值

代码语言:javascript
复制
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
public int getMinimumWidth() {
//这里是获取drawable的原始宽度,shapeDrawable无原始宽高,而BitmapDrawable有原始宽高
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

ViewGroupmeasure绘制子view方法:

  • ViewGroup类中定义了measureChildren,measureChild,measureChildWithMargins实现对子view的测量
  • measureChildren内部循环调用measurechild方法。measureChildmeasureChildWithMargins方法区别:
    • 是否把padding与margin作为子视图大小
代码语言:javascript
复制
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
//判断子view是否是GONE,GONE的话就不进行绘制
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}
//让每一个子view去绘制它自己,必须传入父view的测量spec与子view自己的padding值
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
 
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
 
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
//获取子视图的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//这里绘制子view不仅加入了padding,还加入了margin
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);
//调用view的measure方法,在该方法中回调onMeasure实现子视图的测量
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取父view的mode与size
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
//获取父view剩余大小,小于0则返回0
    int size = Math.max(0, specSize - padding);
//定义存储变量的值
    int resultSize = 0;
    int resultMode = 0;
//根据父view的mode值进行分别处理
    switch (specMode) {
//默认mode为EXACTLY,如果父viewmode为EXACTLY
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
//子view中设置了大于0的具体数值,就直接将值赋值给size,并设置子view的mode为EXACTLY
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
//如果没有设置具体的数值,当前子view全部包裹父view,就设置子view大小为父view剩余空间,mode为EXACTLY
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
//这里childView决定自身的大小,但不能超过父view分配给子view的剩余空间
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
//现在父view的测量模式改为AT_MOST
    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
//和上面一样,具体值就设置具体大小
            // Child wants a specific size... so be it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
//这里子view的match_parent与wrap_content所展示的结果一样,于是就需要设置具体的数值
            // Child wants to be our size, but our size is not fixed.
            // Constrain child to not be bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
//测量模式为UNSECIFIED,子view的大小就不再受父view的控制
    // Parent asked to see how big we want to be
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
//根据最后得到的size与mode调用measureSpec方法转换成32位数值
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

决定view的大小是view的setMeasureDimession方法。当通过setMeasuredDimension方法最终设置完成,View的mMeasuredWidthmMeasuredHeight成员才会有具体的数值。 getMeasuredWidth方法返回的是测量后的宽度,这个宽度是当setMeasuredDimension方法被调用刷新方法(measure方法最终会调用setMeasuredDimension)被调用后刷新的。 所以getMeasuredWidth返回值的大小,取决于setMeasuredDimension

Measure 分析总结

  • MeasureSpec测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。其中specMode只有三种值:
    • MeasureSpec.EXACTLY:确定模式,子View的大小是确定的,由specSize决定;
    • MeasureSpec.AT_MOST:最多模式,子View的大小最多是specSize指定的值,不能超过父view大小
    • MeasureSpec.UNSPECIFIED:没有指定模式,子view自己决定
  • View的measure方法是final的,不允许重载,View子类只能重写onMeasure来完成自身的测量
  • 顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)
  • ViewGroup类中提供了measureChildrenmeasureChild和measureChildWithMargins方法,子view大小由父view及自身决定。
  • 当ViewGroup的子类需要使用layout_margin,那么就必须要求LayoutParams继承子MarginLayoutParams或者重写generateLayoutParams方法,否则无法使用layout_margin参数 如下: /** * 子视图需要其margin,必须重写该方法 */ override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {     return MarginLayoutParams(context, attrs) }
  • 使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须setMeasuredDimension方法被调用刷新后才行

onLayout 分析

代码语言:javascript
复制
public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
 
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
//判断布局是否改变,确定是否要重新设置布局
    boolean changed = isLayoutModeOptical(mParent) ?
//setFrame 设置4个点,即初始化mLeft,mTop,mRight,mBottom
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
 
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//改变布局重新设置布局,这个方法是父容器用来确定子元素位置的,里面是空的实现,可以自己自定义设置布局
        onLayout(changed, l, t, r, b);
 
        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }
 
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
 
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }
 
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
 
    if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
        mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
        notifyEnterOrExitForAutoFillIfNeeded(true);
    }
}

ViewGroup源码查看采用LinearLayout的onLayout 代码进行分析

代码语言:javascript
复制
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//这里根据当前布局是垂直还是其他来设置不同的布局,如果没有设置垂直布局,那么默认就是水平布局
    if (mOrientation == VERTICAL) {
        layoutVertical(l, t, r, b);
    } else {
        layoutHorizontal(l, t, r, b);
    }
}
void layoutVertical(int left, int top, int right, int bottom) {
    final int paddingLeft = mPaddingLeft;
 
    int childTop;
    int childLeft;
//拿到子view的宽度
    // Where right end of child should go
    final int width = right - left;
//计算父窗口推荐子view的位置
    int childRight = width - mPaddingRight;
//获取child的可用空间
    // Space available for child
    int childSpace = width - paddingLeft - mPaddingRight;
//通过ViewGroup的getChildCount获取Viewgroup的子view个数
    final int count = getVirtualChildCount();
//获取Gravity属性值
    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//根据majorGravity来获取childTop值
    switch (majorGravity) {
       case Gravity.BOTTOM:
           // mTotalLength contains the padding already
//childTop会逐渐增大,意味着后面的子元素会被放在越靠下的位置
           childTop = mPaddingTop + bottom - top - mTotalLength;
           break;
 
           // mTotalLength contains the padding already
       case Gravity.CENTER_VERTICAL:
           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
           break;
 
       case Gravity.TOP:
       default:
           childTop = mPaddingTop;
           break;
    }
//循坏viewgroup的子view个数
    for (int i = 0; i < count; i++) {
//获取到当前子view
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
//子view的宽高是由measure来决定的,因此measure是用于确定layout布局的视图范围
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();
 
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();
 
            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
//设置不同的childLeft
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                            + lp.leftMargin - lp.rightMargin;
                    break;
 
                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;
 
                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }
 
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }
 
            childTop += lp.topMargin;
//根据垂直排列获取child的layout来重新设置child的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
 
            i += getChildrenSkipCount(child, i);
        }
    }
}
private void setChildFrame(View child, int left, int top, int width, int height) {
//调用子view的layout布局设置
    child.layout(left, top, left + width, top + height);
}

onLayout 分析总结

  • 从源码中得知Viewlayout方法可以重载,而ViewGroup的layout方法不可以重载,子类必须实现抽象方法onLayout来设置自己的位置
  • View的测量宽高是在measure过程中获取,但是view的最终宽高是在layout过程中获取 比如:getWidth返回的是最终layout出来的宽度,在View代码中返回的是【mRight - mLeft】,这个mRightmLeft,是在setFrame方法被调用后赋值的(layout方法最终会调用setFrame)

Draw 分析

由于ViewGroup中没有重写View的draw方法,这里只分析View的draw方法

代码语言:javascript
复制
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
 
    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      1. Draw the background
     *      2. If necessary, save the canvas' layers to prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edges and restore layers
     *      6. Draw decorations (scrollbars for instance)
     */
 
    // Step 1, draw the background, if needed
    int saveCount;
 
    if (!dirtyOpaque) {
    //绘制背景
        drawBackground(canvas);
    }
 
    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {/绘制自己
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
//绘制子view,如果当前没有子view则不需要绘制,在view中是空实现,具体在viewgroup中实现方法
        // Step 4, draw the children
        dispatchDraw(canvas);
 
        drawAutofilledHighlight(canvas);
 
        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
//绘制装饰
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
//绘制默认的焦点高亮显示
        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);
 
        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
 
        // we're done...
        return;
    }
    //绘制子view,遍历所有子元素,那么这个draw就一层一层传递下去
protected void dispatchDraw(Canvas canvas) {
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;
 
    if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
        final boolean buildCache = !isHardwareAccelerated();
        for (int i = 0; i < childrenCount; i++) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                final LayoutParams params = child.getLayoutParams();
                attachLayoutAnimationParameters(child, params, i, childrenCount);
                bindLayoutAnimation(child);
            }
        }
 
        final LayoutAnimationController controller = mLayoutAnimationController;
        if (controller.willOverlap()) {
            mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
        }
 
        controller.start();
 
        mGroupFlags &= ~FLAG_RUN_ANIMATION;
        mGroupFlags &= ~FLAG_ANIMATION_DONE;
 
        if (mAnimationListener != null) {
            mAnimationListener.onAnimationStart(controller.getAnimation());
        }
    }
 
    int clipSaveCount = 0;
    final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
    if (clipToPadding) {
        clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
        canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                mScrollX + mRight - mLeft - mPaddingRight,
                mScrollY + mBottom - mTop - mPaddingBottom);
    }
 
    // We will draw our child's animation, let's reset the flag
    mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
    mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
 
    boolean more = false;
    final long drawingTime = getDrawingTime();
 
    if (usingRenderNodeProperties) canvas.insertReorderBarrier();
    final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
    int transientIndex = transientCount != 0 ? 0 : -1;
    // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
    // draw reordering internally
    final ArrayList<View> preorderedList = usingRenderNodeProperties
            ? null : buildOrderedChildList();
    final boolean customOrder = preorderedList == null
            && isChildrenDrawingOrderEnabled();
    for (int i = 0; i < childrenCount; i++) {
        while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
            final View transientChild = mTransientViews.get(transientIndex);
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {/调用view的draw方法进行绘制子view
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                transientIndex = -1;
            }
        }
 
        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    while (transientIndex >= 0) {
        // there may be additional transient views after the normal views
        final View transientChild = mTransientViews.get(transientIndex);
        if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                transientChild.getAnimation() != null) {
            more |= drawChild(canvas, transientChild, drawingTime);
        }
        transientIndex++;
        if (transientIndex >= transientCount) {
            break;
        }
    }
    if (preorderedList != null) preorderedList.clear();
 
    // Draw any disappearing views that have animations
    if (mDisappearingChildren != null) {
        final ArrayList<View> disappearingChildren = mDisappearingChildren;
        final int disappearingCount = disappearingChildren.size() - 1;
        // Go backwards -- we may delete as animations finish
        for (int i = disappearingCount; i >= 0; i--) {
            final View child = disappearingChildren.get(i);
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    if (usingRenderNodeProperties) canvas.insertInorderBarrier();
 
    if (debugDraw()) {
        onDebugDraw(canvas);
    }
 
    if (clipToPadding) {
        canvas.restoreToCount(clipSaveCount);
    }
 
    // mGroupFlags might have been updated by drawChild()
    flags = mGroupFlags;
 
    if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
        invalidate(true);
    }
 
    if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
            mLayoutAnimationController.isDone() && !more) {
        // We want to erase the drawing cache and notify the listener after the
        // next frame is drawn because one extra invalidate() is caused by
        // drawChild() after the animation is over
        mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
        final Runnable end = new Runnable() {
           @Override
           public void run() {
               notifyAnimationListener();
           }
        };
        post(end);
    }
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
//调用view的draw方法
    return child.draw(canvas, this, drawingTime);
}
 

View中的特殊方法setWillNotDraw

代码语言:javascript
复制
public void setWillNotDraw(boolean willNotDraw) {
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

当一个view不需要绘制任何内容,那么设置这个标记为true后,系统就会做出相应的优化,默认情况,view没有启用这个优化,但是viewgroup启用了这个优化,当自定义控件继承自viewgroup时并且本身不具备绘制功能,就可以开启这个标记来让系统优化。但是当viewgroup需要通过onDraw方法来绘制内容时,于是就要显示的关闭WILL_NOT_DRAW方法

Draw 分析总结

  • 当是一个viewgroup,那么要递归所有的子view已完成绘制
  • 从源码中可知viewgroup不绘制任何内容,真正绘制在子view中进行,也就是调用子view的draw方法进行绘制
  • View动画和ViewGroup布局动画区别:
    • View动画指的是View自身的动画,可以通过setAnimation添加
    • ViewGroup布局动画指的是对ViewGroup内部子视图时设置的动画,可以在xml布局文件中对ViewGroup设置layoutAnimation属性
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年7月19日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Measure 分析
    • onMeasure
      • Measure 分析总结
      • onLayout 分析
        • onLayout 分析总结
        • Draw 分析
          • Draw 分析总结
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档