美文网首页
ViewTree的绘制过程

ViewTree的绘制过程

作者: 三十二蝉 | 来源:发表于2018-03-14 05:48 被阅读24次

    概述

    ViewTree的绘制,起点是ViewRootImpl的performTraversals方法

    private void performTraversals() {
        ...
        windowSizeMayChange |= measureHierarchy(host, lp, res,desiredWindowWidth,     desiredWindowHeight);
        ...
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ...
        performDraw();
        ...
    }
    

    View系统的绘制流程会从ViewRootImpl的performTraversals()方法中开始, 每一个视图的绘制过程都必须经历三个最主要的阶段onMeasure()、onLayout()和onDraw()。

    measure

    measure函数的作用是为整个View树计算实际的大小, 设置每个View对象的布局大小(“窗口”大小)。实际对应属性就是View中的mMeasuredHeight(高)和mMeasureWidth(宽)。方法中参数widthMeasureSpec和heightMeasureSpec, 这两个值分别用于确定视图的宽度和高度的规格和大小。
    MeasureSpec的值由specSize和specMode共同组成的, 其中specSize记录的是大小, specMode记录的是规格。

    specMode 含义
    EXACTLY 表示父视图希望子视图的大小应该是由specSize的值来决定的。子元素将被限定在给定的边界里而忽略它本身大小
    AT_MOST 表示子视图最多只能是specSize中指定的大小, 开发人员应该尽可能小得去设置这个视图, 并且保证不会超过specSize
    UNSPECIFIED 表示开发人员可以将视图按照自己的意愿设置成任意的大小, 没有任何限制。这种情况比较少见, 不太会用到

    View的measure方法代码:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            ...
            final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
            final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
                    MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
            final boolean matchingSize = isExactly &&
                    getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&
                    getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
            if (forceLayout || !matchingSize &&
                    (widthMeasureSpec != mOldWidthMeasureSpec ||
                            heightMeasureSpec != mOldHeightMeasureSpec)) {
    
                // first clears the measured dimension flag
                mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
    
                resolveRtlPropertiesIfNeeded();
    
                int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
                if (cacheIndex < 0 || sIgnoreMeasureCache) {
                    // measure ourselves, this should set the measured dimension flag back
                    onMeasure(widthMeasureSpec, heightMeasureSpec);
                    mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
                } else {
                    ...
                }
    
                mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
            }
    
            mOldWidthMeasureSpec = widthMeasureSpec;
            mOldHeightMeasureSpec = heightMeasureSpec;
            ...
        }
    

    View的onMeasure默认实现:

     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
           setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                   getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
       }
    

    子View不可重写的setMeasureDimension方法

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
            ...
            mMeasuredWidth = measuredWidth;
            mMeasuredHeight = measuredHeight;
    
            mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
            ...
        }
    

    measure()这个方法是final的, 因此我们无法在子类中去重写这个方法, 说明Android是不允许我们改变View的measure框架的。measure方法中会调用onMeasure方法。

    FrameLayout的onMeasure实现:

      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int count = getChildCount();
            ...
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (mMeasureAllChildren || child.getVisibility() != GONE) {
                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                    ...
                }
            }
            ...
        }
    

    ViewGroup的measureChildWithMargins方法:

        protected void measureChildWithMargins(View child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            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);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    

    layout

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

    传入测量出的宽度、高度。

        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) ?
                    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);
                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;
        }
    
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        }
    

    layout()方法接收四个参数, 分别代表着左、上、右、下的坐标, 当然这些坐标是相对于当前视图的父视图而言的。在layout()方法中, 首先会调用setFrame()方法来判断视图的大小是否发生过变化, 以确定有没有必要对当前的视图进行重绘, 同时还会在这里把传递过来的四个参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量。View中的onLayout()方法就是一个空方法, 因为onLayout()过程是为了确定视图在布局中所在的位置, 而这个操作应该是由布局来完成的, 即父视图决定子视图的显示位置。
    ViewGroup的onLayout:

        protected abstract void onLayout(boolean changed,
                int l, int t, int r, int b);
    

    所有ViewGroup的子类都必须重写这个方法。在onLayout()过程结束后, 我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了。

    getWidth()方法和getMeasureWidth()方法到底有什么区别

    getMeasureWidth()方法在measure()过程结束后就可以获取到了, 而getWidth()方法要在layout()过程结束后才能获取到。另外, getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的, 而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。

    draw

    调用栈

    ViewRootImpl首先会判断是否要硬件加速。没有硬件加速,走drawSoftware方法:

        private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                boolean scalingRequired, Rect dirty) {
            ...
            // Draw with software renderer.
            final Canvas canvas;
            ...//设置canvas
            mView.draw(canvas);
            ...
        }
    

    View中的draw方法:

        public void draw(Canvas canvas) {
            ...
            /*
             * 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);
    
                // Step 6, draw decorations (scrollbars)
                onDrawScrollBars(canvas);
    
                if (mOverlay != null && !mOverlay.isEmpty()) {
                    mOverlay.getOverlayView().dispatchDraw(canvas);
                }
    
                // we're done...
                return;
            }
            ...
        }
    
        protected void onDraw(Canvas canvas) {
        }
    
        /**
         * Called by draw to draw the child views. This may be overridden
         * by derived classes to gain control just before its children are drawn
         * (but after its own view has been drawn).
         */
        protected void dispatchDraw(Canvas canvas) {
    
        }
    

    View的onDraw为空方法, 因为每个视图的内容部分肯定都是各不相同的, 这部分的功能需交给子类去实现。dispatchDraw这一步的作用是对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图, 那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码。onDrawScrollBars 是对视图的滚动条进行绘制。

    其他

    窗口的UI最终是需要通过SurfaceFlinger服务来统一渲染的, 而SurfaceFlinger服务在渲染窗口的UI之前, 需要计算基于各个窗口的Z轴位置来计算它们的可见区域。而WindowManagerService服务就是负责计算好每一个窗口的Z轴位置之后, 还需要将它们设置到SurfaceFlinger服务中去, 以便SurfaceFlinger服务可以正确地渲染每一个窗口的UI。

    相关文章

      网友评论

          本文标题:ViewTree的绘制过程

          本文链接:https://www.haomeiwen.com/subject/oshvfftx.html