• android view绘制流程 面试


    一、view树的绘制流程

    measure--->layout--->draw

    measure

    1、ViewGroup.LayoutParams 指定部件的长宽

    2、MeasureSpec 32位的int值 前两位代表模式 后30位测量规格的大小

    layout

    draw

    invalidate()请求android系统 如果大小没有发生变化 就不会调用layout放置这个过程

    requestLayout() 当布局发生变化时 希望重新测量尺寸大小 就会触发 measure和layout 但不会调用draw方法

    View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()

    各步骤的主要工作:

    • OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

    • OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。

    • OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制视图的背景;②、保存画布的图层(Layer);③、绘制View的内容;④、绘制View子视图,如果没有就不用;⑤、还原图层(Layer);⑥、绘制滚动条。

    OnMeasure

    View树的绘制是从ViewRoot的performTraversals()方法开始,这个方法的主要作用是判断是否重新measure、是否重新layout、是否重新draw。

      private void performTraversals() {
            ......
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            ......
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            ......
            mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
            ......
            mView.draw(canvas);
            ......
        }
    
        private int getRootMeasureSpec(int windowSize, int rootDimension) {
            int measureSpec;
            switch (rootDimension) {
                case ViewGroup.LayoutParams.MATCH_PARENT:
                    measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
                    break;
                case ViewGroup.LayoutParams.WRAP_CONTENT:
                    measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
                    break;
                default:
                    measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
                    break;
            }
            return measureSpec;
        } 
    
        public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                    widthMeasureSpec != mOldWidthMeasureSpec ||
                    heightMeasureSpec != mOldHeightMeasureSpec) {
                mPrivateFlags &= ~MEASURED_DIMENSION_SET;
                if (ViewDebug.TRACE_HIERARCHY) {
                    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
                }
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                    throw new IllegalStateException("onMeasure() did not set the"
                            + " measured dimension by calling"
                            + " setMeasuredDimension()");
                }
                mPrivateFlags |= LAYOUT_REQUIRED;
            }
            mOldWidthMeasureSpec = widthMeasureSpec;
            mOldHeightMeasureSpec = heightMeasureSpec;
    
        //View的onMeasure默认实现方法,设置View的长和宽;如果写死,则外边变化时界面不变化
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    
        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:
                    result = specSize;
                    break;
            }
            return result;
        }  

    如果是ViewGroup,还应该进行嵌套测量

     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);
        }  
    
        public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);
            int size = Math.max(0, specSize - padding);
            int resultSize = 0;
            int resultMode = 0;
            switch (specMode) {
                case MeasureSpec.EXACTLY:
                    if (childDimension >= 0) {
                        resultSize = childDimension;
                        resultMode = MeasureSpec.EXACTLY;
                    } else if (childDimension == LayoutParams.MATCH_PARENT) {
                        resultSize = size;
                        resultMode = MeasureSpec.EXACTLY;
                    } 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;
                ......
            }
            //将mode与size通过MeasureSpec方法整合为32位整数返回
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }

    MeasureSpec 的值由specSize和specMode共同组成的,高2位代表specMode,低30代表spceSize,其中specSize记录的是大小,specMode记录的是规格。

    specMode一共有三种类型:

    1. EXACTLY 
      表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

    2. AT_MOST 
      表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

    3. UNSPECIFIED 
      表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见。

    每个View控件的实际宽高都是由父视图和自身决定的。实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,这是因为measure方法是final的,不允许重载,所以View子类只能通过重载onMeasure来实现自己的测量逻辑。

    OnLayout

    测量完各个组件的大小之后,就可以排列他们的位置了。

    private void performTraversals() {
            ......
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            ......
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            ......
            mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
            ......
            mView.draw(canvas);
            ......
        }
    
        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 = setFrame(l, t, r, b);//视图大小是否发生变化
            if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
                if (ViewDebug.TRACE_HIERARCHY) {
                    ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
                }
                onLayout(changed, l, t, r, b);
                mPrivateFlags &= ~LAYOUT_REQUIRED;
                if (mOnLayoutChangeListeners != null) {
                    ArrayList<OnLayoutChangeListener> listenersCopy =
                            (ArrayList<OnLayoutChangeListener>) 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 &= ~FORCE_LAYOUT;
        }  

    View中的onLayout()方法就是一个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置。

    @Override  
    /**
    *ViewGroupd的onLayout方法
    */
    protected abstract void onLayout(boolean changed, int l, int t, int r, int b);  

    然而,ViewGroup中的onLayout()方法是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法。自定义ViewGroup控件中,onLayout配合onMeasure方法一起使用可以实现自定义View的复杂布局。自定义View首先调用onMeasure进行测量,然后调用onLayout方法动态获取子View和子View的测量大小,然后进行layout布局。重载onLayout的目的就是安排其children在父View的具体位置,重载onLayout通常做法就是写一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。

    下面代码以LinearLayout举例:

    public class LinearLayout extends ViewGroup {
        @Override
        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;
            final int width = right - left;
            int childRight = width - mPaddingRight;
            int childSpace = width - paddingLeft - mPaddingRight;
            final int count = getVirtualChildCount();
            final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
            final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
            switch (majorGravity) {
                case Gravity.BOTTOM:
                    childTop = mPaddingTop + bottom - top - mTotalLength;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
                    break;
                case Gravity.TOP:
                default:
                    childTop = mPaddingTop;
                    break;
            }
            for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    childTop += measureNullChild(i);
                } else if (child.getVisibility() != GONE) {
                    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) {
                        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;
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight);
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
                    i += getChildrenSkipCount(child, i);
                }
            }
        }

    到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别(上面分析measure过程已经说过getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效)。可以看出来getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效。那我们看下View源码中这些方法的实现吧,如下:

     public final int getMeasuredWidth() {
            return mMeasuredWidth & MEASURED_SIZE_MASK;
        }
    
        public final int getMeasuredHeight() {
            return mMeasuredHeight & MEASURED_SIZE_MASK;
        }
    
        public final int getWidth() {
            return mRight - mLeft;
        }
    
        public final int getHeight() {
            return mBottom - mTop;
        }
    
        public final int getLeft() {
            return mLeft;
        }
    
        public final int getRight() {
            return mRight;
        }
    
        public final int getTop() {
            return mTop;
        }
    
        public final int getBottom() {
            return mBottom;
        }

    OnDraw

    public void draw(Canvas canvas) {  
        if (ViewDebug.TRACE_HIERARCHY) {  
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);  
        }  
        final int privateFlags = mPrivateFlags;  
        final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&  
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);  
        mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;  
        // Step 1, draw the background, if needed  
        int saveCount;  
        if (!dirtyOpaque) {  
            final Drawable background = mBGDrawable;  
            if (background != null) {  
                final int scrollX = mScrollX;  
                final int scrollY = mScrollY;  
                if (mBackgroundSizeChanged) {  
                    background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);  
                    mBackgroundSizeChanged = false;  
                }  
                if ((scrollX | scrollY) == 0) {  
                    background.draw(canvas);  
                } else {  
                    canvas.translate(scrollX, scrollY);  
                    background.draw(canvas);  
                    canvas.translate(-scrollX, -scrollY);  
                }  
            }  
        }  
        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);  
            // we're done...  
            return;  
        }  
    }  
  • 相关阅读:
    VisualCaptcha – 灵活的可视化验证码解决方案
    Web Uploader
    Concise
    15个前卫的 HTML5 & CSS3 网页设计作品
    Breach
    字体大宝库:20款超细英文字体免费下载
    使用 JavaScript 实现灵活的固定导航功能
    10套免费的 Photoshop UI 元素以及 PSD 素材
    使用 CSS & jQuery 制作一款漂亮的多彩时钟
    我在美国读博士:才发现美国高等教育如此残酷
  • 原文地址:https://www.cnblogs.com/zquan/p/9611022.html
Copyright © 2020-2023  润新知