Android View的绘制流程详解

本文详细解析了Android中View的绘制流程,包括measure、layout及draw三个核心步骤。介绍了每个阶段的功能、实现细节及其应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

View的绘制流程
        整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为:根据之前设置的状态,判断是否需要重新计算视图大小(measure过程)、是否重新需要计算视图的位置(layout过程)、以及是否需要重绘(draw过程)
一:mesarue()过程:
        主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。

              打开View的源码,找到measure方法,这个方法代码不少,但是测量工作都是在onMeasure()做的,measure方法是final的所以这个方法也不可重写。
        1)如果想自定义View的测量,你应该去重写onMeasure()方法。onMeasure方法默认实现很简单,就是调用setMeasuredDimension(),setMeasuredDimension()可以简单理解就是去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth),如果这两个值一旦设置了,那么意味着对于这个View的测量结束了,这个View的宽高已经有测量的结果出来了。如果我们想设定某个View的高宽,完全可以直接通过setMeasuredDimension(100,200)来设置死它的高宽(不建议),但是setMeasuredDimension方法必须在onMeasure方法中调用,不然会抛异常。

//View类中measure默认实现
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  ......
  onMeasure(widthMeasureSpec,heightMeasureSpec);
  .....
}

//View类中onMeasure默认实现
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    
  setMeasuredDimension(
  getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),            
  getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

        2)抽象类ViewGroup类继承自View,并没有实现onMeasure,但是具体的布局如RelativeLayout等类实现了onmeasure。
        如果想自定义ViewGroup,需要重写该onMeasure()方法。大致分为两步:a)对其子视图进行遍历的measure()过程 b)所有的孩子测量之后,经过一系类的计算之后通过setMeasuredDimension设置自己的宽高
        如FrameLayout:

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



//伪代码描述该onMeasure流程
    private void onMeasure(int height , int width){  
     //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight)  
     //1、该方法必须在onMeasure调用,否者报异常。  
     setMeasuredDimension(h , l) ;  
       
     //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程  
     int childCount = getChildCount() ;  
       
     for(int i=0 ;i<childCount ;i++){  
      //2.1、获得每个子View对象引用  
      View child = getChildAt(i) ;  
        
      //整个measure()过程就是个递归过程  
      //该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法都  
      measureChildWithMargins(child , h, i) ;   
        
      //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下:  
      //child.measure(h, l)  
     }  
    }  
      
    //该方法具体实现在ViewGroup.java里 。  
    protected  void measureChildWithMargins(View v, int height , int width){  
     v.measure(h,l)     
    }  

二、 layout布局过程
          performTraversals 方法执行完mView.measure 计算出mMeasuredXXX后就开始执行layout 函数来确定View具体放在哪个位置,我们计算出来的View目前只知道view矩阵的大小,具体这个矩阵放在哪里,这就是layout 的工作了
          layout的主要作用 :根据子视图的大小以及布局参数将View树放到合适的位置上。


1)对于View对象(非ViewGroup)

查看View.java源码,View类中的layout,onLayout函数默认实现


public final void layout(int l, int t, int r, int b) {
       .....
      //设置View位于父视图的坐标轴
       boolean changed = setFrame(l, t, r, b); 
       //判断View的位置是否发生过变化,看有必要进行重新layout吗
       if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
           if (ViewDebug.TRACE_HIERARCHY) {
               ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
           }
           //调用onLayout(changed, l, t, r, b); 函数
           onLayout(changed, l, t, r, b);
           mPrivateFlags &= ~LAYOUT_REQUIRED;
       }
       mPrivateFlags &= ~FORCE_LAYOUT;
       .....
   }

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

layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)
  接下来回调onLayout()方法,对于View来说,onLayout只是一个空实现,一般情况下我们自定义View不需要重载该函数。


2)对于ViewGroup对象:
    先查看ViewGroup.java源码中layout,onLayout实现
 @Override
    public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

         layout函数中:LayoutTransition是用于处理ViewGroup增加和删除子视图的动画效果,也就是说如果当前ViewGroup未添加LayoutTransition动画,或者LayoutTransition动画此刻并未运行,那么调用super.layout(l, t, r, b),即父类View中的layout,继而调用到ViewGroup中的onLayout,否则将mLayoutSuppressed设置为true,等待动画完成时再调用requestLayout()。
    
         对于ViewGroup 来说,唯一的差别就是ViewGroup中onLayout多了关键字abstract的修饰,要求其子类必须重载onLayout函数,目的就是安排其children在父视图的具体位置,那么如何安排子View的具体位置呢?
 int childCount = getChildCount() ; 
  for(int i=0 ;i<childCount ;i++){
       View child = getChildAt(i) ;
       //整个layout()过程就是个递归过程
       child.layout(l, t, r, b) ;
    }

三:draw()绘图过程:

    performTraversals 方法的下一步就是mView.draw(canvas); 因为View的draw 方法一般不去重写,官网文档也建议不要去重写draw 方法
    View.java中draw函数的源码如下:

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

绘制过程:
          1)绘制该View的背景
          2)为显示渐变框做一些准备操作(大多数情况下,不需要改渐变框)          
          3)调用onDraw()方法绘制视图本身   (每个View都可以重载该方法来具体实现自定义绘制,ViewGroup不需要实现该方法)
          4)调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)
          ViewGroup类已经为我们实现绘制子View的默认过程(内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法),这个实现基本能满足大部分需求,所以ViewGroup类的子类(LinearLayout,FrameLayout)也基本没有去重写dispatchDraw方法,我们在实现自定义控件,除非比较特别,不然一般也不需要去重写它
          5)绘制滚动条

四:应用
        这三种情况,最终会直接或间接调用到三个函数,分别为invalidate(),requsetLaytout()以及requestFocus()
        invalidate():请求重绘View树,即draw()过程
        requestLayout()方法 :会导致调用measure()过程 和 layout()过程 。
        requestFocus():请求View树的draw()过程
 




参考文章:http://www.jianshu.com/p/5a71014e7b1b
http://blog.youkuaiyun.com/qinjuning/article/details/7110211/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值