view的绘制_view的绘制流程 - CSDN
精华内容
参与话题
  • Measure过程决定了View的宽高,Measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽高,在几乎所有的情况下它都等于View的最终宽高,这仅仅是在代码规范的前提之下。...

    每个人对于Android中的自定义View的测量、布局及绘制原理,都有自己独特的分析与看法,但终究的理论都是一样的。


    • 自定义控件的分类
      [1]通过系统提供的原生控件进行组合 来达到自定义的需求
      [2]
      • 定义一个类继承View
      • 定义一个类继承ViewGroup
        [3]View&ViewGroup特点
        • View 是构建页面的基本模块 view类在屏幕上占据的是一个矩形区域 职责:是绘制和事件的处理
        • ViewGroup:可以包裹孩子
          View的绘制过程就是从ViewRoot的performTraversals方法开始的,它经过measure、layout、draw三个过程才能最终将一个View绘制出来:

    1. measure用来测量View的宽和高。
    2. layout用来确定View在父容器中放置的位置。
    3. draw用来将view绘制在屏幕上。
      那么,对于ViewRoot是什么呢?
    初始ViewRoot 与 DecorView
    ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的,在ActivityThread中,当ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。

    那也许会问DecorView又是什么呢?
    其实它就是Activity的顶级View

    在Activity中加载布局使用的方法中setContentView,那个contentview就是我们这个DecroView中的子布局,View的点击事件都要先经过DecorView,然后再传递给我们的View。

    一、View绘制的流程框架

    这里写图片描述

    performTraversals会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout、draw这个三个流程。也就是说View的绘制是从上往下一层层迭代下来的。

    其中:**1.perfromMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程。接着子元素会重复父元素的measure过程,如此反复就完成整个View树的遍历。

    2.performLayout的传递流程和performMeasure是一样的。

    3.performDraw的传递过程是在draw方法中通过dispathDraw来实现的,本质上并没有区别。

    二、Measure流程

    测量每一个控件的大小。
    调用measure()方法,进行一些逻辑处理,然后调用onMeasure()方法,在其中调用setMeasuredDimension()设定View的宽高信息,完成View的测量操作。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    }

    measure()方法中,传入了两个参数 widthMeasureSpec, heightMeasureSpec 表示View的宽高的一些信息。

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

    Measure过程决定了View的宽高,Measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽高,在几乎所有的情况下它都等于View的最终宽高,这仅仅是在代码规范的前提之下。
    由上述流程来看Measure流程很简单,关键点是在于widthMeasureSpec, heightMeasureSpec这两个参数信息怎么获得?

    如果有了widthMeasureSpec, heightMeasureSpec,通过一定的处理(可以重写,自定义处理步骤),从中获取View的宽/高,调用setMeasuredDimension()方法,指定View的宽高,完成测量工作。

    MeasureSpec的理解:
    MeasureSpec的中文意思是测量规格的意思,MeasureSpec代表一个32的int值,高2位代表SpecMode测量模式,低30位代表SpecSize测量规格大小。

    经常使用的三个函数:
    1.public static int makeMeasureSpec(int size,int mode)
    构造一个MeasureSpec

    2.public static int getMode(int measureSpec)
    获取MeasureSpec的测量模式

    3.public static int getSize(int measureSpec)
    获取MeasureSpec的测量大小

    由图可知其中MeasureSpec由两部分组成,一部分是测量模式,另一部分是测量的尺寸大小。
    Mode的模式又分为三类

    UNSPECIFIED :不对View进行任何限制,要多大给多大,一般用于系统内部

    EXACTLY:对应LayoutParams中的match_parent和具体数值这两种模式。检测到View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值,

    AT_MOST :对应LayoutParams中的wrap_content。View的大小不能大于父容器的大小。

    那么MeasureSpec又是如何确定的?

    对于DecorView,其确定是通过屏幕的大小,和自身的布局参数LayoutParams。

    这部分很简单,根据LayoutParams的布局格式(match_parent,wrap_content或指定大小),将自身大小,和屏幕大小相比,设置一个不超过屏幕大小的宽高,以及对应模式。

    对于其他View(包括ViewGroup),其确定是通过父布局的MeasureSpec和自身的布局参数LayoutParams。

    MeasureSpec和LayoutParams的关系

    在上面系统中,使用MeasureSpec来进行View的测量,但是正常情况下我们使用View指定MeasureSpec,尽管如此,但是我们可以给View设置layoutparams,在View测量的时候,系统会将LayoutParams在父容器的约束下自动转化成对应的MeasureSpec,然后再根据MeasureSpec来确定View最终的宽高。

    MeasureSpec不是由LayoutParams唯一决定的,子View的宽高由自身的layoutparams和父容器的MeasureSpec。

    对于DecorView,其MeasureSpec由窗口的尺寸和自身的Layoutparams决定;
    对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同决定;

    遵循的规则:

    1.LayoutParams.MATCH_PARENT:精确模式,大小就是窗口的大小
    2.LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过窗口的大小
    3.固定大小(比如100dp):精确模式,大小为LayoutParams中指定的大小
    **4.当view采用固定的宽高时,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且大小遵循LayoutParams的大小
    5.当View的宽高时matchparent时,如果父容器是精确模式,那么View也是精确模式,并且View也是精确模式并且大小是父容器的剩余空间。如果父容器是最大模式,那么View也是最大模式但是大小不能超过剩余的空间。
    6.当View是wrap_content,那么不管父容器的模式,View一定是最大模式,但是不能超过父容器的剩余空间。**

    总结:当子View的LayoutParams的布局格式是wrap_content,可以看到子View的大小是父View的剩余尺寸,和设置成match_parent时,子View的大小没有区别。为了显示区别,一般在自定义View时,需要重写onMeasure方法,处理wrap_content时的情况,进行特别指定。

    从这里看出MeasureSpec的指定也是从顶层布局开始一层层往下去,父布局影响子布局。

    三、View的工作流程

    View的工作流程主要是指measure、layout、draw这三个流程,即测量、布局、绘制,其中measure确定View的测量宽高,layout确定View的最终宽高和上下左右的位置,draw将View绘制到屏幕上。
    清晰View测量流程

    Measure过程决定了View的宽高,Measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽高,在几乎所有的情况下它都等于View的最终宽高,这仅仅是在代码规范的前提之下。

    layout最终决定了View的四个顶点的坐标和实际View的宽/高,完成以后,可以通过getTop、getBottom、getLeft、getRight来拿到View的四个顶点坐标位置,并可以通过getWidth和getHeight来得到View的最终宽高

    draw过程决定了View的显示,只有draw方法完成以后View的内容才会最终显示在屏幕上。

    ①measure

    measure过程有时分两种情况

    • 针对View的measure
    • 针对ViewGroup的measure

    1.针对View的measure
    View的measure过程由其meaure方法完成,measure方法是一个final类型的方法,子类不可以重写此方法,因为View的measure内部会调用onMeasure方法

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

    里面就是将getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)获取到的宽和高赋给View。
    再看一下getDefalutSize的代码实现

    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;
        }
        protected int getSuggestedMinimumWidth() {
            return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
        }

    其中,如果当前的模式为测量模式为UnSpecified,那么View的宽高就是传递过来Size的大小,如果测量模式为AT_MOST或者EXACTLY,那么View的宽高就是SpecSize的大小。也就是说View的内部将matchparent和wrapcontent是一样处理的,没有区别,所以这就是我们自定义View时,要自己处理wrapcontent的原因。

    结论:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时自身的大小,否则在布局中使用wrap_content就相当于使用match_parent。matchparent使用的是父View剩余的控件。

    解决方法:重写View的onMeasure方法

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int measuredWidth = 你自己计算出来的宽;
            int measureHeight = 你自己计算出来的高;
            int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
            int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
            int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);
    
           if (heightSpaceMode == MeasureSpec.AT_MOST && widthSpaceMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(measuredWidth, measureHeight);
            } else if (heightSpaceMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(widthSpaceSize, measureHeight);
            } else if (widthSpaceMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(measuredWidth, heightSpaceSize);
            }
        }

    所以只需要判断measureSpec的mode是不是AT_MOST,如果是就我们自己计算好的值赋给View。

    2.针对ViewGroup的measure过程
    和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是他提供了一个叫measureChildren的方法。

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

    可以看出measureChild的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec传递给View的measure方法进行测量。

    总结:
    1.View的onmeasure在measure中,onmeasure中处理wrap_content和padding。
    2.ViewGroup的onmeasure在measure中,viewgroup除了要测量自身的宽高,还要通过measureChild测量子View的宽高,只有里面子View的宽高确定了,才好计算Viewgroup的宽高。
    3.注意MeasureSpec和layoutParams的对应关系。

    ②Layout
    layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用Layout方法,在layout方法中onLayout方法又会被调用。这样一层一层的完成所有子View的布局过程。
    layout里面通过setFrame来确定View的四个顶点的位置,然后再layout中调用onLayout,这个方法用来确定子View的位置,起到一层层传递的作用。

    public void layout(int l, int t, int r, int b) {  
    
        // 当前视图的四个顶点
        int oldL = mLeft;  
        int oldT = mTop;  
        int oldB = mBottom;  
        int oldR = mRight;  
    
        // setFrame() / setOpticalFrame():确定View自身的位置
        // 即初始化四个顶点的值,然后判断当前View大小和位置是否发生了变化并返回  
     boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
        //如果视图的大小和位置发生变化,会调用onLayout()
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  
    
            // onLayout():确定该View所有的子View在父容器的位置     
            onLayout(changed, l, t, r, b);      
      ...
    
    }
    

    ③draw操作
    draw的过程也比较简单,它的作用是将View绘制到屏幕上面。View的绘制过程遵循下面:

    1.绘制背景 (drawBackground)
    2.绘制自己 (onDraw(canvas))
    3.绘制children ( dispatchDraw(canvas))
    4.绘制装饰 onDrawForeground(canvas);

    @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);
    
                // 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);
    
                // we're done...
                return;
            }
    
            /*
             * Here we do the full fledged routine...
             * (this is an uncommon case where speed matters less,
             * this is why we repeat some of the tests that have been
             * done above)
             */
    
            boolean drawTop = false;
            boolean drawBottom = false;
            boolean drawLeft = false;
            boolean drawRight = false;
    
            float topFadeStrength = 0.0f;
            float bottomFadeStrength = 0.0f;
            float leftFadeStrength = 0.0f;
            float rightFadeStrength = 0.0f;
    
            // Step 2, save the canvas' layers
            int paddingLeft = mPaddingLeft;
    
            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
            }
    
            int left = mScrollX + paddingLeft;
            int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
            int top = mScrollY + getFadeTop(offsetRequired);
            int bottom = top + getFadeHeight(offsetRequired);
    
            if (offsetRequired) {
                right += getRightPaddingOffset();
                bottom += getBottomPaddingOffset();
            }
    
            final ScrollabilityCache scrollabilityCache = mScrollCache;
            final float fadeHeight = scrollabilityCache.fadingEdgeLength;
            int length = (int) fadeHeight;
    
            // clip the fade length if top and bottom fades overlap
            // overlapping fades produce odd-looking artifacts
            if (verticalEdges && (top + length > bottom - length)) {
                length = (bottom - top) / 2;
            }
    
            // also clip horizontal fades if necessary
            if (horizontalEdges && (left + length > right - length)) {
                length = (right - left) / 2;
            }
    
            if (verticalEdges) {
                topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
                drawTop = topFadeStrength * fadeHeight > 1.0f;
                bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
                drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
            }
    
            if (horizontalEdges) {
                leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
                drawLeft = leftFadeStrength * fadeHeight > 1.0f;
                rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
                drawRight = rightFadeStrength * fadeHeight > 1.0f;
            }
    
            saveCount = canvas.getSaveCount();
    
            int solidColor = getSolidColor();
            if (solidColor == 0) {
                final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
    
                if (drawTop) {
                    canvas.saveLayer(left, top, right, top + length, null, flags);
                }
    
                if (drawBottom) {
                    canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
                }
    
                if (drawLeft) {
                    canvas.saveLayer(left, top, left + length, bottom, null, flags);
                }
    
                if (drawRight) {
                    canvas.saveLayer(right - length, top, right, bottom, null, flags);
                }
            } else {
                scrollabilityCache.setFadeColor(solidColor);
            }
    
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);
    
            // Step 4, draw the children
            dispatchDraw(canvas);
    
            // Step 5, draw the fade effect and restore layers
            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
    
            if (drawTop) {
                matrix.setScale(1, fadeHeight * topFadeStrength);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, right, top + length, p);
            }
    
            if (drawBottom) {
                matrix.setScale(1, fadeHeight * bottomFadeStrength);
                matrix.postRotate(180);
                matrix.postTranslate(left, bottom);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, bottom - length, right, bottom, p);
            }
    
            if (drawLeft) {
                matrix.setScale(1, fadeHeight * leftFadeStrength);
                matrix.postRotate(-90);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, left + length, bottom, p);
            }
    
            if (drawRight) {
                matrix.setScale(1, fadeHeight * rightFadeStrength);
                matrix.postRotate(90);
                matrix.postTranslate(right, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(right - length, top, right, bottom, p);
            }
    
            canvas.restoreToCount(saveCount);
    
            // 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);
        }

    无论是ViewGroup还是单一的View,都需要实现这套流程,不同的是,在ViewGroup中,实现了 dispatchDraw()方法,而在单一子View中不需要实现该方法。自定义View一般要重写onDraw()方法,在其中绘制不同的样式。

    四、总结

    从View的测量、布局和绘制原理来看,要实现自定义View,根据自定义View的种类不同,可能分别要自定义实现不同的方法。但是这些方法不外乎:onMeasure()方法onLayout()方法onDraw()方法

    onMeasure()方法:单一View,一般重写此方法,针对wrap_content情况,规定View默认的大小值,避免于match_parent情况一致。ViewGroup,若不重写,就会执行和单子View中相同逻辑,不会测量子View。一般会重写onMeasure()方法,循环测量子View。

    onLayout()方法:单一View,不需要实现该方法。ViewGroup必须实现,该方法是个抽象方法,实现该方法,来对子View进行布局。

    onDraw()方法:无论单一View,或者ViewGroup都需要实现该方法,因其是个空方法

    嗯,对于自定义View的绘制原理都透彻明了了,相信对大家回头看的时候还有些看得见的理论可去弥补。

    可参考博客
    借鉴博客
    资料提供

    展开全文
  • View绘制流程

    2018-12-16 12:59:16
    View的绘制流程前情提要2.1 View 树的绘图流程2.2 View绘制流程函数调用链2.3 绘制顺序2.4 measure过程传递尺寸的两个类2.4.1 ViewGroup.LayoutParams2.4.2 MeasureSpecs [...speks]3. View的绘制流程3.1 View的...

    1. 视图坐标系

    1.1 Android坐标系

    android坐标系

    1.2 视图坐标系

    视图坐标系

    2. View的绘制流程前情提要

    2.1 View 树的绘图流程

    当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw。整个 View 树的绘图流程在ViewRoot.java类的performTraversals()函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw),流程图如下:
    View树的绘制流程
    在此之前
    DecorView添加到窗口window的过程
    详见https://blog.csdn.net/jacklam200/article/details/50039189

    2.2 View绘制流程函数调用链

    函数调用链
    可以说是: (测量)大小 -->(安排)位置 --> (绘制)内容

    2.3 绘制顺序

    树的遍历

    • DecorView是View树的根
    • 树的遍历是有序的,由父视图到子视图,每个ViewGroup负责绘制他所有的子视图,最底层的View负责绘制自身。
      或者说,measure流程分为View的measure流程和ViewGroup的measure流程,只不过ViewGroup的measure流程除了要完成自己的测量还要遍历去调用子元素的measure()方法

    2.4 measure过程传递尺寸的两个类

    • ViewGroup.LayoutParams(View自身的布局参数)
    • MeasureSpecs类(父视图对自视图的测量要求)

    2.4.1 ViewGroup.LayoutParams

    用来指定视图的宽度和高度等参数,对于height和width有以下选择:

    • 具体值
    • MATCH_PARENT 子视图希望和父视图一样大(不包含padding值)
    • WRAP_CONTENT 视图正好能包裹内容大小(包含padding值)
      ViewGroup的子类有其对应的ViewGroup.LayoutParams的子类。
      比如RelativeLayout拥有的ViewGroup.LayoutParams的子类RelativeLayoutParams.
      注意:我们使用view.getLayoutParams()方法获取的是其所在父视图类型的LayoutParams,比如View的父控件为RelativeLayout,那么得到的LayoutParams类型就是RelativeLayoutParams。

    2.4.2 MeasureSpecs […speks]

    测量规格,包含测量要求和尺寸信息,有三个模式:

    • UNSPECIFIED
      父视图不对子视图有任何约束,它可以达到所期望的任意尺寸。比如 ListView、ScrollView,一般自定义 View 中用不到,

    • EXACTLY
      父视图为子视图指定一个确切的尺寸,而且无论子视图期望多大,它都必须在该指定大小的边界内,对应的属性为 match_parent 或具体值,比如 100dp,父控件可以通过MeasureSpec.getSize(measureSpec)直接得到子控件的尺寸。

    • AT_MOST
      父视图为子视图指定一个最大尺寸。子视图必须确保它自己所有子视图可以适应在该尺寸范围内,对应的属性为 wrap_content,这种模式下,父控件无法确定子 View 的尺寸,只能由子控件自己根据需求去计算自己的尺寸,这种模式就是我们自定义视图需要实现测量逻辑的情况。

    3. View的绘制流程

    3.1 View的measure流程:

    一个View的真正测量工作在onMeasure(int,int)中,由measure()方法调用。
    而measure()方法为final所以只有onMeasure(int,int)可以而且必须被子类复写。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
          ...
          onMeasure(widthMeasureSpec, heightMeasureSpec);
          ...
        }
    

    首先看一下onMeasure()方法(View.java):

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

    往里看setMeasuredDimension()方法:

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

    我们看到measuredWidthmeasuredHeight 显然它是用来设置View的宽高的。
    我们再来看看getDefaultSize()方法处理了什么:

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

    specMode是View的测量模式,而specSize是View的测量大小,我们有必要查看一下MeasureSpec类:

     public static class MeasureSpec {
            private static final int MODE_SHIFT = 30;
            private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    
            /**
             * Measure specification mode: The parent has not imposed any constraint
             * on the child. It can be whatever size it wants.
             */
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
            /**
             * Measure specification mode: The parent has determined an exact size
             * for the child. The child is going to be given those bounds regardless
             * of how big it wants to be.
             */
            public static final int EXACTLY     = 1 << MODE_SHIFT;
    
            /**
             * Measure specification mode: The child can be as large as it wants up
             * to the specified size.
             */
            public static final int AT_MOST     = 2 << MODE_SHIFT;
    
    ...省略
    
     public static int getMode(int measureSpec) {
                return (measureSpec & MODE_MASK);
            }
      public static int getSize(int measureSpec) {
                return (measureSpec & ~MODE_MASK);
            }
    ...省略        
    }
    

    MeasureSpec类帮助我们来测量View

    我们回头再看getDefaultSize()方法,显然在AT_MOST和EXACTLY模式下,都返回specSize这个值,也就是View测量后的大小,而在UNSPECIFIED模式(未指定模式)返回的是getDefaultSize()方法的第一个参数的值,这第一个参数从onMeasure()方法来看是getSuggestedMinimumWidth()方法和getSuggestedMinimumHeight()得到的,看一下这两个方法:(类似的只放Width)

     protected int getSuggestedMinimumWidth() {
            return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
        }
    

    如果View没有设置背景(MBackground==null)则取值为mMinWidth,mMinWidth是可以设置的,它对应于android:minWidth这个属性设置的值或者View的setMinimumWidth的值,若不指定则默认0:

      public void setMinimumWidth(int minWidth) {
            mMinWidth = minWidth;
            requestLayout();
    
        }
    

    如果View设置了背景 在取值为max(mMinWidth,mBackground.getMinimumWidth()),取值mMinWidth和mBackground.getMinimumWidth()两者中的最大值,mMinWidth上面已经看过,我们接下来看看mBackground.getMinimumWidth(),这个mBackground是Drawable类型的,看一下Drawable类的getMInimumWidth()方法(Drawable.java):

     public int getMinimumWidth() {
            final int intrinsicWidth = getIntrinsicWidth();
            return intrinsicWidth > 0 ? intrinsicWidth : 0;
        }
    

    intrinsicWidth得到的是这个Drawable的固有宽度,如果其大于0则返回固有宽度否则返回0。
    我们可以看到,getSuggestedMinimumWidth()方法:如果View没有设置背景则返回mMinWidth,如果设置了背景就返回mMinWidth和Drawable最小宽度两个值的最大值。

    3.1.1 ViewGroup的measure流程

    我们知道对于ViewGroup,它除了需要measure自己本身,还要遍历调用子视图的measure()方法
    ViewGroup_measure

    ViewGroup中没有定义onMeasure()方法,然而它定义了measureChildren()方法(ViewGroup.java):

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

    显然它是一个遍历children的方法,看一下调用的measureChild()方法:

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

    调用child.getLayoutParams()方法来获得子元素的LayoutParams属性,并获取到子元素的MeasureSpec并调用其measure()方法进行测量。
    看一下getChildMeasureSpec()方法:

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    ...省略
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }
    

    这是根据父容器的MeasureSpec的模式,结合子元素的LayoutParams属性得出自容器的MeasureSpec属性。

    3.1.2 LinearLayout的measure流程

    ViewGroup没有提供onMeasure方法,而是让其子类来个字实现测量的方法,我们看一下ViewGroup的子类LinearLayout的measure流程,看一下onMeasure()方法(LinearLayout.java):

     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mOrientation == VERTICAL) {
                measureVertical(widthMeasureSpec, heightMeasureSpec);
            } else {
                measureHorizontal(widthMeasureSpec, heightMeasureSpec);
            }
        }
    

    复写了View的onMeasure()方法。
    看一下measureVertical()方法部分源码:

    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
            mTotalLength = 0;
         mTotalLength = 0;       
     ...省略
      for (int i = 0; i < count; ++i) {
                final View child = getVirtualChildAt(i);
    
                if (child == null) {
                    mTotalLength += measureNullChild(i);
                    continue;
                }
    
                if (child.getVisibility() == View.GONE) {
                   i += getChildrenSkipCount(child, i);
                   continue;
                }
    
                if (hasDividerBeforeChildAt(i)) {
                    mTotalLength += mDividerHeight;
                }
    
                LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
    
                totalWeight += lp.weight;
    
                 final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
                if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                    // Optimization: don't bother measuring children who are only
                    // laid out using excess space. These views will get measured
                    // later if we have space to distribute.
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                    skippedMeasure = true;
                } else {
                    if (useExcessSpace) {
                        // The heightMode is either UNSPECIFIED or AT_MOST, and
                        // this child is only laid out using excess space. Measure
                        // using WRAP_CONTENT so that we can find out the view's
                        // optimal height. We'll restore the original height of 0
                        // after measurement.
                        lp.height = LayoutParams.WRAP_CONTENT;
                    }
                    // Determine how big this child would like to be. If this or
                    // previous children have given a weight, then we allow it to
                    // use all available space (and we will shrink things later
                    // if needed).
                   final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                    measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                            heightMeasureSpec, usedHeight);
    
                    final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                    measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                            heightMeasureSpec, usedHeight);
    
                    final int childHeight = child.getMeasuredHeight();
                    if (useExcessSpace) {
                        // Restore the original height and record how much space
                        // we've allocated to excess-only children so that we can
                        // match the behavior of EXACTLY measurement.
                        lp.height = 0;
                        consumedExcessSpace += childHeight;
                    }
    
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                           lp.bottomMargin + getNextLocationOffset(child));
    ...省略
    
            if (useLargestChild &&
                    (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
                mTotalLength = 0;
    
                for (int i = 0; i < count; ++i) {
                    final View child = getVirtualChildAt(i);
    
                    if (child == null) {
                        mTotalLength += measureNullChild(i);
                        continue;
                    }
    
                    if (child.getVisibility() == GONE) {
                        i += getChildrenSkipCount(child, i);
                        continue;
                    }
    
                    final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                            child.getLayoutParams();
                    // Account for negative margins
                    final int totalLength = mTotalLength;
                    mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                            lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
                }
            }
    
            // Add in our padding
            mTotalLength += mPaddingTop + mPaddingBottom;
    
            int heightSize = mTotalLength;
    
            // Check against our minimum height
    

    定义了mTotalLength用来储存LinearLayout在垂直方向的高度,然后遍历子元素,根据子元素的MeasureSpec模式分别计算每个子元素的高度,如果是wrap_content则将每个子元素的高度和margin垂直高度等值相加 并且赋值给mTotalLength得出LinearLayout的高度。如果布局高度设置为match_parent,具体数值则和view的测量方法一样。

    3.3 View的layout流程

    先来看一下View的layout()方法:

    public void layout(int l, int t, int r, int b) {
    ...省略
     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);
    ...省略          
        }
    

    传进来的分别是View的四个点的坐标,注意其是对于父布局来说的。
    看看setFrame()方法:

    protected boolean setFrame(int left, int top, int right, int bottom) {
            boolean changed = false;
    
            if (DBG) {
                Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                        + right + "," + bottom + ")");
            }
            
            //在setFrame()方法里主要是用来设置View的四个顶点的值,
            //即mLeft、mTop、mRight和mBottom
    
            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;
                boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
    
                // Invalidate our old position
                invalidate(sizeChanged);
    
                mLeft = left;
                mTop = top;
                mRight = right;
                mBottom = bottom;
                mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
    
            ...省略  
            }
            return changed;
        }
    

    在setFrame()方法里主要是用来设置View的四个顶点的值,即mLeft、mTop、mRight和mBottom。调用setFrame()方法后调用onLayout()方法:

      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        }
    

    和onMeasure()方法类似,确定位置时候,不同控件有不同的实现,所以需要子类重写这个方法。我们看一下LinearLayout的onLayout()方法:

        @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);
            }
        }
    

    看一看layoutVertical()方法:

     void layoutVertical(int left, int top, int right, int bottom) {
         ...省略
            for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);
               ...
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
                    
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
    
                    i += getChildrenSkipCount(child, i);
                }
            }
        }
    

    该方法会遍历子元素,调用setChildFrame()方法:

    private void setChildFrame(View child, int left, int top, int width, int height) {        
            child.layout(left, top, left + width, top + height);
        }
    

    该方法中调用子元素的layout()方法来确定自己的位置。我们看到childTop这个值逐渐增大,这是为了在垂直方向,子元素一个接一个安排而不重叠。(setChildFrame()传入的第三个参数为childTop + getLocationOffset(child));

    3.4 View的draw流程

    绘制流程图
    先来看下与 draw 过程相关的函数:

    • View.draw(Canvas canvas):
      由于 ViewGroup 并没有复写此方法,因此,所有的视图最终都是调用 View 的 draw 方法进行绘制的。在自定义的视图中,也不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制,如果自定义的视图确实要复写该方法,那么请先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制。

    • View.onDraw():
      View 的onDraw(Canvas)默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容。

    • dispatchDraw()
      发起对子视图的绘制。View 中默认是空实现,ViewGroup 复写了dispatchDraw()来对其子视图进行绘制。该方法我们不用去管,自定义的 ViewGroup 不应该对dispatchDraw()进行复写。

    我们先来看一下ViewGroup的dispatchDraw的源码

    protected void dispatchDraw(Canvas canvas){
    
    ...省略
    
     if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {//处理 ChildView 的动画
         final boolean buildCache = !isHardwareAccelerated();
                for (int i = 0; i < childrenCount; i++) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {//只绘制 Visible 状态的布局,因此可以通过延时加载来提高效率
                        final LayoutParams params = child.getLayoutParams();
                        attachLayoutAnimationParameters(child, params, i, childrenCount);// 添加布局变化的动画
                        bindLayoutAnimation(child);//为 Child 绑定动画
                        if (cache) {
                            child.setDrawingCacheEnabled(true);
                            if (buildCache) {
                                child.buildDrawingCache(true);
                            }
                        }
                    }
                }
    
         final LayoutAnimationController controller = mLayoutAnimationController;
                if (controller.willOverlap()) {
                    mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
                }
    
        controller.start();// 启动 View 的动画
    }
    
     // 绘制 ChildView
     for (int i = 0; i < childrenCount; i++) {
                int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
                final View child = (preorderedList == null)
                        ? children[childIndex] : preorderedList.get(childIndex);
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);//在drawChild中调用view.draw
                }
            }
    
    ...
    if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
                invalidate(true);
            }
    
    }
    
    
    • drawChild(canvas, this, drawingTime)
      直接调用了 View 的child.draw(canvas, this,drawingTime)方法,文档中也说明了,除了被ViewGroup.drawChild()方法外,你不应该在其它任何地方去复写或调用该方法,它属于 ViewGroup。而View.draw(Canvas)方法是我们自定义控件中可以复写的方法,具体可以参考上述对view.draw(Canvas)的说明。从参数中可以看到,child.draw(canvas, this, drawingTime) 肯定是处理了和父视图相关的逻辑,但 View 的最终绘制,还是 View.draw(Canvas)方法。

    • invalidate()
      请求重绘 View 树,即 draw 过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些调用了invalidate()方法的 View。

    dispatchDraw()–>drawChild()–>view.draw()

    注意:view.draw()中调用的onDraw(Canvas)默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容。

    我们再来看一下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);
            }
    ...省略
       // Step 2, save the canvas' layers
            int paddingLeft = mPaddingLeft;
    
            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
            }
    ...省略
      // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);	//onDraw空实现
    
            // Step 4, draw the children
            dispatchDraw(canvas);
    ...省略
       // Step 5, draw the fade effect and restore layers
            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
    ...省略
      // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);
    
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }
       }
    

    从源码的注释我们看到draw流程有六个步骤,其中第2步和第5步可以跳过:
    1.若有背景则绘制
    2.保存canvas层
    3.绘制自身内容
    4.如果有子元素则绘制子元素
    5.绘制效果
    6.绘制装饰品(scrollbars)

    注意:由上面的处理过程,我们也可以得出一些优化的小技巧:当不需要绘制 Layer 的时候第二步和第五步会跳过。因此在绘制的时候,能省的 layer 尽可省,可以提高绘制效率

    引用:
    https://blog.csdn.net/itachi85/article/details/50708391
    http://developer.android.com/guide/topics/ui/how-android-draws.html
    http://blog.csdn.net/wangjinyu501/article/details/9008271
    http://blog.csdn.net/qinjuning/article/details/7110211
    http://blog.csdn.net/qinjuning/article/details/8074262

    展开全文
  • view绘制过程

    2018-03-17 23:11:05
    一、view绘制view的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout、和draw三个过程才能最终将一个View绘制出来,其中measure用来测量View发的宽和高,layout用来确认View在父容器中的...

    一、view绘制

    view的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout、和draw三个过程才能最终将一个View绘制出来,其中measure用来测量View发的宽和高,layout用来确认View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。

    performTraversals会一次调用performMeasure、performLayut、performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三大流程,其中在perfomMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传到子元素中了,这样就完成了一次measure过程。接着子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。同理,performLayout和performDraw的传递流程和performMeasure是类似的。

    measure过程决定了View的宽/高,measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽/高,在几乎所有的情况下他都等同于View最终的宽/高。Layout过程决定了View的四个顶点的坐标和实际的View的宽/高,完成以后,可以通过getTop、getBottom、getRight、getLeft拿到View四个顶点的位置,并可以通过getWidth和getHeight方法来拿到View的最终宽/高。Draw过程则决定了View的显示,只用draw方法完成以后View的内容才能呈现在屏幕上。

    小结:

    getMeasuredWidth和getMeasuredHeight方法获取的高度和宽度是measure过程中计算得到到,所以要在measure之后调用。

    getWidt()获取的宽度 = right - left 这是layout之后才能确定,所以getWidth()要在layout之后调用才能获取到值。同理,getHeight()。

    Measure过程

    对于测量我们来说几个知识点,了解这几个知识点,之后的实例分析你才看得懂。

    1、MeasureSpec 的理解

    MeasureSpec中的值是一个整型(32位)将size和mode打包成一个Int型,其中高两位是mode,后面30位存的是size。

    注:-1 代表的是EXACTLY,-2 是AT_MOST

    UPSPECIFIED : 父容器对于子容器没有任何限制,子容器想要多大就多大
    EXACTLY: 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。(match_parent / 固定大小)

    AT_MOST:子容器可以是声明大小内的任意大小 (wrap_content)

    • 普通View的MeasureSpec是由自身的LayoutParams和父容器共同决定的
    • 顶层View的MeasureSpec是由自身的LayoutParams和窗口的尺寸共同决定的;
    • ViewGroup在调用子元素的measure方法之前,会先通过getChildMeasureSpec方法得到子元素的MeasureSpec。

     

    普通view的MeasureSpec的创建规则:

    • 如果父View的MeasureSpec 是EXACTLY,说明父View的大小是确切的,(确切的意思很好理解,如果一个View的MeasureSpec 是EXACTLY,那么它的size 是多大,最后展示到屏幕就一定是那么大)。

    (1)、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是确切,子View的大小又MATCH_PARENT(充满整个父View),那么子View的大小肯定是确切的,而且大小值就是父View的size。所以子View的size=父View的size,mode=EXACTLY

    (2)、如果子View 的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根据自己的content 来决定的,但是子View的毕竟是子View,大小不能超过父View的大小,但是子View的是WRAP_CONTENT,我们还不知道具体子View的大小是多少,要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 调用的时候才去真正测量子View 自己content的大小(比如TextView wrap_content 的时候你要测量TextView content 的大小,也就是字符占用的大小,这个测量就是在child.measure(childWidthMeasureSpec, childHeightMeasureSpec)的时候,才能测出字符的大小,MeasureSpec 的意思就是假设你字符100px,但是MeasureSpec 要求最大的只能50px,这时候就要截掉了)。通过上述描述,子View MeasureSpec mode的应该是AT_MOST,而size 暂定父View的 size,表示的意思就是子View的大小没有不确切的值,子View的大小最大为父View的大小,不能超过父View的大小(这就是AT_MOST 的意思),然后这个MeasureSpec 做为子View measure方法 的参数,做为子View的大小的约束或者说是要求,有了这个MeasureSpec子View再实现自己的测量。

    (3)、如果如果子View 的layout_xxxx是确定的值(200dp),那么就更简单了,不管你父View的mode和size是什么,我都写死了就是200dp,那么控件最后展示就是就是200dp,不管我的父View有多大,也不管我自己的content 有多大,反正我就是这么大,所以这种情况MeasureSpec 的mode = EXACTLY 大小size=你在layout_xxxx 填的那个值。

    • 如果父View的MeasureSpec 是AT_MOST,说明父View的大小是不确定,最大的大小是MeasureSpec 的size值,不能超过这个值。

    (1)、如果子View 的layout_xxxx是MATCH_PARENT,父View的大小是不确定(只知道最大只能多大),子View的大小MATCH_PARENT(充满整个父View),那么子View你即使充满父容器,你的大小也是不确定的,父View自己都确定不了自己的大小,你MATCH_PARENT你的大小肯定也不能确定的,所以子View的mode=AT_MOST,size=父View的size,也就是你在布局虽然写的是MATCH_PARENT,但是由于你的父容器自己的大小不确定,导致子View的大小也不确定,只知道最大就是父View的大小。

    (2)、如果子View 的layout_xxxx是WRAP_CONTENT,父View的大小是不确定(只知道最大只能多大),子View又是WRAP_CONTENT,那么在子View的Content没算出大小之前,子View的大小最大就是父View的大小,所以子View MeasureSpec mode的就是AT_MOST,而size 暂定父View的 size。

    (3)、如果如果子View 的layout_xxxx是确定的值(200dp),同上,写多少就是多少,改变不了的。

    • 如果父View的MeasureSpec 是UNSPECIFIED(未指定),表示没有任何束缚和约束,不像AT_MOST表示最大只能多大,不也像EXACTLY表示父View确定的大小,子View可以得到任意想要的大小,不受约束

    (1)、如果子View 的layout_xxxx是MATCH_PARENT,因为父View的MeasureSpec是UNSPECIFIED,父View自己的大小并没有任何约束和要求,
    那么对于子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0

    (2)、同上...

    (3)、如果如果子View 的layout_xxxx是确定的值(200dp),同上,写多少就是多少,改变不了的(记住,只有设置的确切的值,那么无论怎么测量,大小都是不变的,都是你写的那个值)

    2、View的测量过程主要是在onMeasure()方法

    打开View的源码,找到measure方法,这个方法代码不少,但是测量工作都是在onMeasure()做的,measure方法是final的所以这个方法也不可重写,如果想自定义View的测量,你应该去重写onMeasure()方法

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
      ......
      onMeasure(widthMeasureSpec,heightMeasureSpec);
      .....
    }
    

    3、View的onMeasure 的默认实现

     

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
      setMeasuredDimension(
      getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            
      getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    View的onMeasure方法默认实现很简单,就是调用setMeasuredDimension(),setMeasuredDimension()可以简单理解就是给mMeasuredWidth和mMeasuredHeight设值,如果这两个值一旦设置了,那么意味着对于这个View的测量结束了,这个View的宽高已经有测量的结果出来了。如果我们想设定某个View的高宽,完全可以直接通过setMeasuredDimension(100,200)来设置死它的高宽(不建议),但是setMeasuredDimension方法必须在onMeasure方法中调用,不然会抛异常。

     

    4、ViewGroup的Measure过程

    ViewGroup 类并没有实现onMeasure,我们知道测量过程其实都是在onMeasure方法里面做的,我们来看下FrameLayout 的onMeasure 方法,具体分析看注释哦。

    //FrameLayout 的测量
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    ....
    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;
    for (int i = 0; i < count; i++) {    
       final View child = getChildAt(i);    
       if (mMeasureAllChildren || child.getVisibility() != GONE) {   
        // 遍历自己的子View,只要不是GONE的都会参与测量,measureChildWithMargins方法
        // 基本思想就是父View把自己的MeasureSpec 
        // 传给子View结合子View自己的LayoutParams 算出子View 的MeasureSpec,然后继续往下传,
        // 传递叶子节点,叶子节点没有子View,根据传下来的这个MeasureSpec测量自己就好了。
         measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);       
         final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 
         maxWidth = Math.max(maxWidth, child.getMeasuredWidth() +  lp.leftMargin + lp.rightMargin);        
         maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);  
         ....
         ....
       }
    }
    .....
    .....
    //所有的孩子测量之后,经过一系类的计算之后通过setMeasuredDimension设置自己的宽高,
    //对于FrameLayout 可能用最大的字View的大小,对于LinearLayout,可能是高度的累加,
    //具体测量的原理去看看源码。总的来说,父View是等所有的子View测量结束之后,再来测量自己。
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),        
    resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
    ....
    } 

     

    二、View的高度和宽度的获取

    View的onMeasure、onLayout过程和Activity的生命周期不同步,所以在onStart、onCreate、onResume中不能准确的获取View的宽、高。

    实现方法

    1、使用 View.measure 测量 View

    该方法测量的宽度和高度可能与视图绘制完成后的真实的宽度和高度不一致。

    
    int width = View.MeasureSpec.makeMeasureSpec(0,
            View.MeasureSpec.UNSPECIFIED);
    int height = View.MeasureSpec.makeMeasureSpec(0,
            View.MeasureSpec.UNSPECIFIED);
    view.measure(width, height);
    view.getMeasuredWidth(); // 获取宽度
    view.getMeasuredHeight(); // 获取高度
    

    2、使用 ViewTreeObserver. OnPreDrawListener 监听事件

    在视图将要绘制时调用该监听事件,会被调用多次,因此获取到视图的宽度和高度后要移除该监听事件。

    
    view.getViewTreeObserver().addOnPreDrawListener(
            new ViewTreeObserver.OnPreDrawListener() {
    
        @Override
        public boolean onPreDraw() {
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            view.getWidth(); // 获取宽度
            view.getHeight(); // 获取高度
            return true;
        }
    });
    

    3、使用 ViewTreeObserver. OnGlobalLayoutListener 监听事件

    在布局发生改变或者某个视图的可视状态发生改变时调用该事件,会被多次调用,因此需要在获取到视图的宽度和高度后执行 remove 方法移除该监听事件。

    
    view.getViewTreeObserver().addOnGlobalLayoutListener(
            new ViewTreeObserver.OnGlobalLayoutListener() {
    
        @Override
        public void onGlobalLayout() {
            if (Build.VERSION.SDK_INT >= 16) {
                view.getViewTreeObserver()
                        .removeOnGlobalLayoutListener(this);
            }
            else {
                view.getViewTreeObserver()
                        .removeGlobalOnLayoutListener(this);
            }
            view.getWidth(); // 获取宽度
            view.getHeight(); // 获取高度
        }
    });
    

    4、重写 View 的 onSizeChanged 方法

    在视图的大小发生改变时调用该方法,会被多次调用,因此获取到宽度和高度后需要考虑禁用掉代码。
    该实现方法需要继承 View,且多次被调用,不建议使用

    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
    
        view.getWidth(); // 获取宽度
        view.getHeight(); // 获取高度
    }
    

    5、重写 View 的 onLayout 方法

    该方法会被多次调用,获取到宽度和高度后需要考虑禁用掉代码。
    该实现方法需要继承 View,且多次被调用,不建议使用

    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
    
        view.getWidth(); // 获取宽度
        view.getHeight(); // 获取高度
    }
    

    6、使用 View.OnLayoutChangeListener 监听事件(API >= 11)

    在视图的 layout 改变时调用该事件,会被多次调用,因此需要在获取到视图的宽度和高度后执行 remove 方法移除该监听事件。

    
    view.addOnLayoutChangeListener(
            new View.OnLayoutChangeListener() {
    
        @Override
        public void onLayoutChange(View v, int l, int t, int r, int b,
                int oldL, int oldT, int oldR, int oldB) {
            view.removeOnLayoutChangeListener(this);
            view.getWidth(); // 获取宽度
            view.getHeight(); // 获取高度
         }
    });
    

    7、使用 View.post() 方法

    Runnable 对象中的方法会在 View 的 measure、layout 等事件完成后触发。
    UI 事件队列会按顺序处理事件,在 setContentView() 被调用后,事件队列中会包含一个要求重新 layout 的 message,所以任何 post 到队列中的 Runnable 对象都会在 Layout 发生变化后执行。
    该方法只会执行一次,且逻辑简单,建议使用

    
    view.post(new Runnable() {
    
        @Override
        public void run() {
            view.getWidth(); // 获取宽度
            view.getHeight(); // 获取高度
        }
    });
    展开全文
  • View绘制原理

    千次阅读 2016-07-28 13:14:10
    1.初识ViewRoot和DecorViewViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的,在ActivityThread中,当ActivityThread中,当Activity对象被创建完毕...

    1.初识ViewRoot和DecorView

    ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的,在ActivityThread中,当ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。

    View的绘制过程就是从ViewRoot的performTraversals方法开始的,它经过measure、layout、draw三个过程才能最终将一个View绘制出来:
    1.measure用来测量View的宽和高。
    2.layout用来确定View在父容器中放置的位置。
    3.draw用来将view绘制在屏幕上。

    大致的流程如下图所示:
    这里写图片描述

    如图所示,performTraversals会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout、draw这个三个流程。其中:

    1.perfromMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程。接着子元素会重复父元素的measure过程,如此反复就完成整个View树的遍历。

    2.performLayout的传递流程和performMeasure是一样的。

    3.performDraw的传递过程是在draw方法中通过dispathDraw来实现的,本质上并没有区别。

    Measure过程决定了View的宽高,Measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽高,在几乎所有的情况下它都等于View的最终宽高,这仅仅是在代码规范的前提之下。
    layout最终决定了View的四个顶点的坐标和实际View的宽/高,完成以后,可以通过getTop、getBottom、getLeft、getRight来拿到View的四个顶点坐标位置,并可以通过getWidth和getHeight来得到View的最终宽高

    draw过程决定了View的显示,只有draw方法完成以后View的内容才会最终显示在屏幕上。

    下面我们来认识一下DecorView,这个是Activity的顶级View。

    ![enter description here][2]

    大家应该还记得我们在Activity中加载布局使用的方法吧!setContentView,那个contentview就是我们这个DecroView中的子布局,从之前的博文View的事件分发中,我们也可以了解到View的点击事件都要先经过DecorView,然后再传递给我们的View。

    1.2 理解MeasureSpec

    MeasureSpec的中文意思是测量规格的意思,MeasureSpec代表一个32的int值,高2位代表SpecMode测量模式,低30位代表SpecSize测量规格大小。

    经常使用的三个函数:
    1.public static int makeMeasureSpec(int size,int mode)
    构造一个MeasureSpec

    2.public static int getMode(int measureSpec)
    获取MeasureSpec的测量模式

    3.public static int getSize(int measureSpec)
    获取MeasureSpec的测量大小

    SpecMode分为三类,每一类都没有他们对应的约束。

    1.UNSPECIFIED
    父容器不对View有任何限制,要多大就给多大,这种情况,一般我们自定义View用不到。

    2.Exactly
    这个表示准确值,这个对应着LayoutParams中的matchparent和准确值,这个时候View的最终大小就是SpecSize所指定的数。

    3.AT_MOST
    父容器指定了一个可用大小即SpecSize,View的大小不能大于该值,具体是什么要看View中自己的处理,所有自定义View时,我们需要自己在measure里面处理设置布局的大小,它对应layoutparams中的wrap_content

    2.1 MeasureSpec和LayoutParams的关系

    在上面系统中,使用MeasureSpec来进行View的测量,但是正常情况下我们使用View指定MeasureSpec,尽管如此,但是我们可以给View设置layoutparams,在View测量的时候,系统会将LayoutParams在父容器的约束下自动转化成对应的MeasureSpec,然后再根据MeasureSpec来确定View最终的宽高。

    MeasureSpec不是由LayoutParams唯一决定的,子View的宽高由自身的layoutparams和父容器的MeasureSpec。

    对于DecorView,其MeasureSpec由窗口的尺寸和自身的Layoutparams决定;
    对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同决定;

    遵循的规则:
    1.LayoutParams.MATCH_PARENT:精确模式,大小就是窗口的大小
    2.LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过窗口的大小
    3.固定大小(比如100dp):精确模式,大小为LayoutParams中指定的大小
    **4.当view采用固定的宽高时,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且大小遵循LayoutParams的大小
    5.当View的宽高时matchparent时,如果父容器是精确模式,那么View也是精确模式,并且View也是精确模式并且大小是父容器的剩余空间。如果父容器是最大模式,那么View也是最大模式但是大小不能超过剩余的空间。
    6.当View是wrap_content,那么不管父容器的模式,View一定是最大模式,但是不能超过父容器的剩余空间。**

    3. View的工作流程

    View的工作流程主要是指measure、layout、draw这三个流程,即测量、布局、绘制,其中measure确定View的测量宽高,layout确定View的最终宽高和上下左右的位置,draw将View绘制到屏幕上。

    3.1 measure操作

    measure过程要分情况来看:
    1.针对View的measure过程
    View的measure过程由其meaure方法完成,measure方法是一个final类型的方法,子类不可以重写此方法,因为View的measure内部会调用onMeasure方法,我们来看一下View的onMeasure的实现即可。

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

    里面就是将getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)获取到的宽和高赋给View。
    我们再来看一看getDefalutSize的实现。

    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;
        }
        protected int getSuggestedMinimumWidth() {
            return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
        }

    我们来分析一下getDefaultSize,如果当前的模式为测量模式为UnSpecified,那么View的宽高就是传递过来Size的大小,如果测量模式为AT_MOST或者EXACTLY,那么View的宽高就是SpecSize的大小。也就是说View的内部将matchparent和wrapcontent是一样处理的,没有区别看来,这就是我们自定义View时,要自己处理wrapcontent的原因。

    结论:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时自身的大小,否则在布局中使用wrap_content就相当于使用match_parent。matchparent使用的是父View剩余的控件。

    解决方法:重写View的onMeasure方法

       @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int measuredWidth = 你自己计算出来的宽;
            int measureHeight = 你自己计算出来的高;
            int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
            int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
            int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec);
    
           if (heightSpaceMode == MeasureSpec.AT_MOST && widthSpaceMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(measuredWidth, measureHeight);
            } else if (heightSpaceMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(widthSpaceSize, measureHeight);
            } else if (widthSpaceMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(measuredWidth, heightSpaceSize);
            }
        }

    还是so easy的,只需要判断measureSpec的mode是不是AT_MOST,如果是就我们自己计算好的值赋给View。

    2.ViewGroup的measure过程
    对于ViewGroup来说,除了完成自己的measure过程,还会遍历去调用所有子元素的measure方法,各个子元素在递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是他提供了一个叫measureChildren的方法。

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

    从上述代码来看,ViewGroup在measure时,会对每一个子元素进行measure,measureChild这个方法也很好理解。

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

    我们很清楚的看出measureChild的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec传递给View的measure方法进行测量。

    这样差不多View中measure过程就差不多了,总结一下:
    1.View的onmeasure在measure中,onmeasure中处理wrap_content和padding。
    2.ViewGroup的onmeasure在measure中,viewgroup除了要测量自身的宽高,还要通过measureChild测量子View的宽高,只有里面子View的宽高确定了,才好计算Viewgroup的宽高。
    3.注意MeasureSpec和layoutParams的对应关系。

    3.2 layout操作

    layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用Layout方法,在layout方法中onLayout方法又会被调用。这样一层一层的完成所有子View的布局过程。
    layout里面通过setFrame来确定View的四个顶点的位置,然后再layout中调用onLayout,这个方法用来确定子View的位置,起到一层层传递的作用。

    3.3 draw操作

    draw的过程也比较简单,它的作用是将View绘制到屏幕上面。View的绘制过程遵循下面:

    1.绘制背景 (drawBackground)
    2.绘制自己 (onDraw(canvas))
    3.绘制children ( dispatchDraw(canvas))
    4.绘制装饰 onDrawForeground(canvas);

        @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);
    
                // 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);
    
                // we're done...
                return;
            }
    
            /*
             * Here we do the full fledged routine...
             * (this is an uncommon case where speed matters less,
             * this is why we repeat some of the tests that have been
             * done above)
             */
    
            boolean drawTop = false;
            boolean drawBottom = false;
            boolean drawLeft = false;
            boolean drawRight = false;
    
            float topFadeStrength = 0.0f;
            float bottomFadeStrength = 0.0f;
            float leftFadeStrength = 0.0f;
            float rightFadeStrength = 0.0f;
    
            // Step 2, save the canvas' layers
            int paddingLeft = mPaddingLeft;
    
            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
            }
    
            int left = mScrollX + paddingLeft;
            int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
            int top = mScrollY + getFadeTop(offsetRequired);
            int bottom = top + getFadeHeight(offsetRequired);
    
            if (offsetRequired) {
                right += getRightPaddingOffset();
                bottom += getBottomPaddingOffset();
            }
    
            final ScrollabilityCache scrollabilityCache = mScrollCache;
            final float fadeHeight = scrollabilityCache.fadingEdgeLength;
            int length = (int) fadeHeight;
    
            // clip the fade length if top and bottom fades overlap
            // overlapping fades produce odd-looking artifacts
            if (verticalEdges && (top + length > bottom - length)) {
                length = (bottom - top) / 2;
            }
    
            // also clip horizontal fades if necessary
            if (horizontalEdges && (left + length > right - length)) {
                length = (right - left) / 2;
            }
    
            if (verticalEdges) {
                topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
                drawTop = topFadeStrength * fadeHeight > 1.0f;
                bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
                drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
            }
    
            if (horizontalEdges) {
                leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
                drawLeft = leftFadeStrength * fadeHeight > 1.0f;
                rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
                drawRight = rightFadeStrength * fadeHeight > 1.0f;
            }
    
            saveCount = canvas.getSaveCount();
    
            int solidColor = getSolidColor();
            if (solidColor == 0) {
                final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
    
                if (drawTop) {
                    canvas.saveLayer(left, top, right, top + length, null, flags);
                }
    
                if (drawBottom) {
                    canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
                }
    
                if (drawLeft) {
                    canvas.saveLayer(left, top, left + length, bottom, null, flags);
                }
    
                if (drawRight) {
                    canvas.saveLayer(right - length, top, right, bottom, null, flags);
                }
            } else {
                scrollabilityCache.setFadeColor(solidColor);
            }
    
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);
    
            // Step 4, draw the children
            dispatchDraw(canvas);
    
            // Step 5, draw the fade effect and restore layers
            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
    
            if (drawTop) {
                matrix.setScale(1, fadeHeight * topFadeStrength);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, right, top + length, p);
            }
    
            if (drawBottom) {
                matrix.setScale(1, fadeHeight * bottomFadeStrength);
                matrix.postRotate(180);
                matrix.postTranslate(left, bottom);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, bottom - length, right, bottom, p);
            }
    
            if (drawLeft) {
                matrix.setScale(1, fadeHeight * leftFadeStrength);
                matrix.postRotate(-90);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, left + length, bottom, p);
            }
    
            if (drawRight) {
                matrix.setScale(1, fadeHeight * rightFadeStrength);
                matrix.postRotate(90);
                matrix.postTranslate(right, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(right - length, top, right, bottom, p);
            }
    
            canvas.restoreToCount(saveCount);
    
            // 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);
        }

    View的绘制过程的传递是通过dispatchDraw实现的,dispatchdraw会遍历调用所有子元素的draw方法。如此draw事件就一层一层的传递下去。

    好,大结局。学习好上面的所有知识,我们就要打开自定义View的大门了啊,Follow Me!!!

    展开全文
  • Android View绘制流程

    万次阅读 多人点赞 2018-03-29 19:51:21
    本文主要是梳理 View 绘制的整体流程,帮助开发者对 View绘制有一个更深层次的理解。整体流程View 绘制中主要流程分为measure,layout, draw 三个阶段。measure :根据父 view 传递的 MeasureSpec 进行计算大小...
  • View绘制流程-入门全套

    千次阅读 2018-03-30 18:48:41
    1.Android-UI层级简介图其中PhoneWindow是最基本的窗口系统,是activity...2.view绘制流程简介Viewgroup也继承View.每个view负责绘制自己,viewgroup还负责通知view绘制。流程分为3步:测量(Measure),布局(Layout)...
  • View绘制是从上往下一层层迭代下来的:DecorView–>ViewGroup(—>ViewGroup)–>View,所以,在学习view绘制原理前,我们来先看看DecorView。 1.1 DecorView的视图结构 Android 中 Activity 是作为...
  • Android应用层View绘制流程与源码分析

    万次阅读 多人点赞 2015-05-31 16:30:18
    【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明...我们有分析到Activity中界面加载显示的基本流程原理,记不记得最终分析结果就是下面的关系:看见没有,如上图中id为content的内容就是整个View树的结构,所
  • Android中的View绘制原理

    千次阅读 2019-04-12 17:13:40
    在介绍VIew绘制原理之前,简单介绍一下Window,ViewRootImpl,DecorView之间的联系。 一个 Activity 包含一个Window,Window是一个抽象基类,是 Activity 和整个 View 系统交互的接口,只有一个子类实现类...
  • Android中View绘制流程以及invalidate()等相关方法分析

    万次阅读 多人点赞 2011-12-29 21:18:17
    转载请注明出处:http://blog.csdn.net/qinjuning 前言: 本文是我读《Android内核剖析》第13章----View工作原理总结而成的,在此膜拜下作者 。同时真挚地向渴望了解 Android 框架层的网友,推荐... 整个View树的绘
  • 从ViewRootImpl类分析View绘制的流程

    千次阅读 2015-11-25 17:51:38
    【转载请注明出处:从ViewRootImpl类分析View绘制的流程 CSDN 废墟的树】 从上两篇博客 《从setContentView方法分析Android加载布局流程》 和 《从LayoutInflater分析XML布局解析成View的树形结构的过程...
  • View绘制的三大流程,指的是measure(测量)、layout(布局)、draw(绘制) measure负责确定View的测量宽/高,也就是该View需要占用屏幕的大小,确定完View需要占用的屏幕大小后,就会通过layout确定View的最终宽/...
  • 从invalidate分析view绘制流程

    千次阅读 2019-01-07 15:58:26
    关于view绘制流程,现在网上一查,就会直接告诉你,view绘制流程是先onMeasure,然后onLayout,在最后onDraw,没错,绘制流程确实也是这样。所以我们今天要讨论的话题除了那三大方法内部到底做了什么,还要知道...
  • Android自定义View绘制流程小结

    千次阅读 2018-01-24 18:13:03
    总之一句话,当系统控件满足不了我们的需求时,就需要自定义View来实现,足以表达自定义有多么强大! 通过网上资料和结合自己实践,这篇文章主要用来理解绘制流程的一个具体过程的,绘制流程的起始都是在...
  • android View 绘制完成监听

    千次阅读 2019-08-23 17:49:29
    //view重绘时回调 view.getViewTreeObserver().addOnDrawListener(newOnDrawListener(){ @Override publicvoidonDraw(){ //TODOAuto-generatedmethodstub } }); //布局的状态发生变化或者可见性发生...
  • Android View绘制和显示原理简介

    千次阅读 多人点赞 2016-10-14 18:08:00
    现在越来越多的应用开始重视流畅度方面的测试,了解Android应用程序是如何... 首先,用一句话来概括一下Android应用程序显示的过程:Android应用程序调用SurfaceFlinger服务把经过测量、布局和绘制后的Surface渲染到显
  • View 绘制谈性能优化

    千次阅读 2017-02-26 16:02:49
    转载请注明出处:http://blog.csdn.net/wl9739/article/details/57416433在开发过程中,往往会听到 “性能...一般小项目或许用不上 View 性能优化,然而,当业务愈加庞大、界面愈加复杂的时候,没有一个良好的开发习惯
  • Android开发——View绘制流程

    千次阅读 2017-01-18 09:55:36
    网上讲解View绘制流程有很多优秀的文章。主要分为三个步骤:分别是measure、layout和draw。measure根据父布局的尺寸以及自己想要的尺寸得到最终自己的尺寸,layout用于确定子View的位置,draw负责绘制自己。View...
  • View的工作原理之绘制过程 目录 1. View的详细绘制过程 1.1 绘制过程到底要做一些什么? 1.2 画在哪? 1.3 如何画? 1.4 用什么画? 1.5 整个流程 2. View与Canvas的关系 3. 案例绘制一个长方形 3.1 效果图 ...
  • Android View绘制机制

    千次阅读 2015-01-12 09:26:06
    在此项目中我分析的是ViewPagerIndicator ,其中涉及到了View绘制机制,因此抽取出来,以便后期的其它Buddy分析类似的项目时可以直接引用,就不必再重复讲述这一块内容了。相同的,该开源项目单独建立了一个tech...
1 2 3 4 5 ... 20
收藏数 171,406
精华内容 68,562
关键字:

view的绘制