joshuayingwhat IT Engineer

三:view绘制

2024-08-09

当decorview创建完成后,解析xml布局。最终就到了requestlayout开始计算布局的宽高位置以及绘制。

viewrootimpl@performTraversals

   private void performTraversals() {
		   //获取decorview的宽高规格
        WindowManager.LayoutParams lp = mWindowAttributes;
        //顶层视图decorview所需要的宽高
        int desiredWindowWidth;
        int desiredWindowHeight;
        //此时mFirst = true
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            //如果窗口类型是有状态栏的,则顶层视图的窗口宽度和高度就是除了状态栏
            if (shouldUseDisplaySize(lp)) {
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } } else {
		            //如果没有则decorview的宽高就是整个屏幕的宽高
                desiredWindowWidth = mWinFrame.width();
                desiredWindowHeight = mWinFrame.height();
            }
              int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
              int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
              ...
               // Ask host how big it wants to be
               //执行测量操作
              performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
              ...
              //执行设置位置操作
              performLayout(lp, mWidth, mHeight);
              ...
              //绘制操作
              performDraw();
    }

……

viewrootimpl@performMeasure

    /**
     * <p>
     * This is called to find out how big a view should be. The parent
     * supplies constraint information in the width and height parameters.
     * </p>
     *
     * <p>
     * The actual measurement work of a view is performed in
     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
     * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
     * </p>
     *
     *
     * @param widthMeasureSpec Horizontal space requirements as imposed by the
     *        parent
     * @param heightMeasureSpec Vertical space requirements as imposed by the
     *        parent
     *
     * @see #onMeasure(int, int)
     */
     //final方法,子类不可重写
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ......
        //回调onMeasure()方法
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ......
    }

view中measure是final的也就是view的子类是不可以重写这个方法,每个view控件的实际宽高都是由父控件和自身决定的。实际子view是在onmeasure中实现自己的测量逻辑,其中widthMeasureSpec和heightMeasureSpec代表着父view的规格。

调用setMeasuredDimension对view的宽和高进行设值。

    /**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            //默认 精确模式和最大模式下都是使用后边的 specSize ,这会导致我们设置的 wrap_content 无效,始终是充满父容器。
            result = specSize;
            break;
        }
        return result;
    }

getdefaultsize中的measurespec是父view的规格。因此子view中设置wrap_content或者match_parent都会填充满父布局。因此如果需要设置子view想要的宽高就要重写onmeasure方法中的setmeasureddimension。

measure是怎样递归找到这些view的?viewgroup的measure过程(https://www.sukaidev.top/2018/12/10/bfd836fe/)?

在viewrootimpl调用setview时传入的第一个参数view=decorview,因此performMeasure中的view就是decorview最终decorview的onmeasure会被调用 viewgroup是一个抽象类,具体的实现由它的子类实现,例如linearlayout中的onmeasure

linearlayout中为什么没有调用measureChildren(https://blog.csdn.net/liuqinhou/article/details/125899594)?
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];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
    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);
    }

如果直接调用viewgroup的measureChildren对于linearlayout来说每个子view的测量没有计算margin属性导致每个view没有占位viewgroup的剩余大小都是math.max(0,spaceszie-padding).因此linearlayout没有调用measureChildren

viewrootimpl@performLayout

执行完测量后就开始需要viewgroup摆放view

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
						        ...
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
                    ...
						        mInLayout = false;
    }

host就是decorview,最终执行到了最终的父类view.layout.

 public void layout(int l, int t, int r, int b) {
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                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);
    }

首先changed需要等于true,setframe就是用来判断当前视图view上下左右的位置判断布局是否改变:

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            // Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;

            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            int newWidth = right - left;
            int newHeight = bottom - top;
            // Invalidate our old position
            invalidate(sizeChanged);
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
        }
        return changed;
    }

如果和之前的上下左右不相同,则更改上下左右的位置信息返回true,onlayout会被调用。 此时decorview的onlayout就会被调用

 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

调用decorview的父类framelayout@onlayout

 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
    
    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();
        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();
        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();
                int childLeft;
                int childTop;
                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }
                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

不断的设置子view的位置。layout只会被viewgroup调用,view中layout是一个空实现没有意义。在遍历view的时候如果当前view设置了GONE则不会计算它在viewgroup的空间。view的getwidth和getheight只能在layout执行完成后调用才有数值

/**
     * Return the width of your view.
     *
     * @return The width of your view, in pixels.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }

    /**
     * Return the height of your view.
     *
     * @return The height of your view, in pixels.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getHeight() {
        return mBottom - mTop;
    }

mRight和mLeft,吗BOttom,mTop都是在setFrame中被赋值。

viewrootimpl@performDraw

执行draw方法

private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
        return useAsyncReport;
    }
    
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
            try {
                mView.draw(canvas);
            } finally {
            try {
                surface.unlockCanvasAndPost(canvas);
            } 
        return true;
    }

mView调用自己的draw方法 其中mview=decorview.

 @Override
      public void draw(Canvas canvas) {
          super.draw(canvas);
          if (mMenuBackground != null) {
             mMenuBackground.draw(canvas);
          }
      }

super.draw调用的是framelayout中的draw方法,由于framelayout没有实现因此只能找它的父类view。

view@draw:

/**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    @CallSuper
    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);

            // 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;
        }

1.首先绘制view的背景drawBackground。

2.其次调用onDraw绘制自身.

3.绘制children(view@dispatchDraw):在view中dispatchDraw是一个空方法因此它的实现一定是在viewgroup中。viewGroup@dispathDraw:

@Override
    protected void dispatchDraw(@NonNull Canvas canvas) {
        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) {
                    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);
            }
        }
    }
    
    protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

遍历出childview然后调用它的draw方法.

View@invalidate()和View@PostInvalidate()

设置都是重新绘制view。不同的是invalidate是在主线程中更新重绘,而postinvalidate是可以在子线程调用重绘。

参考: https://www.jianshu.com/p/58d22426e79e

https://blog.csdn.net/yanbober/article/details/46128379


Similar Posts

Content