android View绘制源码分析(上)

Posted 架构师必备

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android View绘制源码分析(上)相关的知识,希望对你有一定的参考价值。

在开发过程中我们经常要进行view的自定义。如果熟练掌握自定义技巧的话就能做出很多控件出来。这篇文章来讲讲view绘制背后发生的那些事。

一、view的基础知识

view的绘制概括

首先先说说view绘制的整体过程。

View绘制的源码分析 ,它的三大流程都是在ViewRootImpl中完成的,从ViewRootImpl中的performTraversals开始,有三个方法performMeasure, performLayout, prformDraw分别对measure,layout,draw三个方法。在onMeasure对所有子元素进行measure过程 ,这时measure就从父容器传递到子元素。子元素重复父元素的过程。

layout与draw类似,只是draw通过diapatchDraw来实现。measure完成后可以通过getMeasureWidth,getMeasureHeight分别获取View测量后的宽高。在实际情况下几乎所有情况它都等于最终宽高。layout过程决定view的四个顶点的坐标和实际view的宽高,完成之后可以通过getTop,getBottom,getLeft,getRight来拿 到view的四个顶点位置。并通过getWidth()和getHeight()来拿到最终宽高。draw决定了view的显示,只有完成才能显示在屏幕上。

MeasureSpec

在测量过程中系统会将View的LayoutParams根据容器所施加的规则转换成对应的MeasureSpec,然后再根据这个测量出view。 

Measure是一个32位的int,高2位代表SpecMode,低30位代表SpecSize。SpecMode表示测量模式,SpecSize指在某种测量模式下规格的大小。其代码如下:

public static class MeasureSpec {    private static final int MODE_SHIFT = 30;    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;    public static final int UNSPECIFIED = 0 << MODE_SHIFT;    public static final int EXACTLY     = 1 << MODE_SHIFT;    public static final int AT_MOST     = 2 << MODE_SHIFT;    public static int makeMeasureSpec(int size, int mode) {        if (sUseBrokenMakeMeasureSpec) {            return size + mode;        } else {            return (size & ~MODE_MASK) | (mode & MODE_MASK);        }    }        public static int getMode(int measureSpec) {        return (measureSpec & MODE_MASK) ;    }        public static int getSize(int measureSpec) {        return (measureSpec & ~MODE_MASK) ;    } }

其实MeasureSpec中源码很值得我们学习。他用一个32位的int来表示模式和大小,节省了空间,也更直观。MeasureSpec通过将specMode和specSize打包成一个int来避免过多的对象内存分配。以上是MeasureSpec的打包和解包过程。 

specMode有三种状态:UNSPECIFIED,EXACTLY(相当于matchparent和精确值这两种模式),ATMOST(wrap_content)。

LayoutParams

对于一般容器,它的MeasureSpec是由父容器的MeasureSpec和自身的LayoutParams共同决定的。上篇博客LayoutInflater源码解析 我们己经介绍了android view的结构,PhoneWindow包了一层DecorView,DecorView里才是title和我们的content view。所以行分析DecorView。

先来看下DecorView的产生源码:

childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ``` 再看下getRootMeasureSpec方法: ```java private static int getRootMeasureSpec(int windowSize, int rootDimension) {        int measureSpec;        switch (rootDimension) {        case ViewGroup.LayoutParams.MATCH_PARENT:            // Window can't resize. Force root view to be windowSize.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);            break;        case ViewGroup.LayoutParams.WRAP_CONTENT:            // Window can resize. Set max size for root view.            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);            break;        default:            // Window wants to be an exact size. Force root view to be that size.//自定义            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);            break;        }        return measureSpec;    }

这里很清楚,分别分MatchPraent和wrap_content和自定义来计算宽高。再来看下普通的view,在ViewGroup的measureChildWIthMargins中:

protected void measureChildWithMargins (View child,        int parentWidthMeasureSpec , int widthUsed,        int parentHeightMeasureSpec , int heightUsed) {    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams() ;    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec ,            mPaddingLeft + mPaddingRight + lp. leftMargin + lp.rightMargin                    + widthUsed, lp.width) ;    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec ,            mPaddingTop + mPaddingBottom + lp. topMargin + lp.bottomMargin                    + heightUsed, lp.height) ;    child.measure(childWidthMeasureSpec , childHeightMeasureSpec); }

再看下getChildMeasureSpec:

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) {    // Parent has imposed an exact size on us    case MeasureSpec.EXACTLY:        if (childDimension >= 0) {            resultSize = childDimension;            resultMode = MeasureSpec. EXACTLY;        } else if (childDimension == LayoutParams.MATCH_PARENT) {            // Child wants to be our size. So be it.            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;    // Parent has imposed a maximum size on us    case MeasureSpec.AT_MOST:        if (childDimension >= 0) {            // Child wants a specific size... so be it            resultSize = childDimension ;            resultMode = MeasureSpec. EXACTLY;        } else if (childDimension == LayoutParams.MATCH_PARENT) {            // Child wants to be our size, but our size is not fixed.            // Constrain child to not be bigger than us.            resultSize = size ;            resultMode = MeasureSpec. AT_MOST;        } 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;    // Parent asked to see how big we want to be    case MeasureSpec.UNSPECIFIED:        if (childDimension >= 0) {            // Child wants a specific size... let him have it            resultSize = childDimension ;            resultMode = MeasureSpec. EXACTLY;        } else if (childDimension == LayoutParams.MATCH_PARENT) {            // Child wants to be our size... find out how big it should            // be            resultSize = 0;            resultMode = MeasureSpec. UNSPECIFIED;        } else if (childDimension == LayoutParams.WRAP_CONTENT) {            // Child wants to determine its own size.... find out how            // big it should be            resultSize = 0;            resultMode = MeasureSpec. UNSPECIFIED;        }        break;    }    return MeasureSpec.makeMeasureSpec(resultSize, resultMode) ; }

以上表明,如果父是EXACTLY,parentSize,那么子如果是EXACTLY,

  • 具体的值size:那子的MeasureSpec就是EXACTLY,size; 

  • MATCH_PARENT:那子的MeasureSpec就是EXACTLY,parentSize; 

  • WRAPCONTENT:那子的MeasureSpec就是ATMOST,parentSize;

如果父是ATMOST,parentSize,那么子如果是EXACTLY,

  • 具体的值size:那子的MeasureSpec就是EXACTLY,size; 

  • MATCHPARENT:那子的MeasureSpec就是ATMOST,parentSize; 

  • WRAPCONTENT:那子的MeasureSpec就是ATMOST,parentSize; 

总结:对于普通View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。

二、三大过程源码分析

OnMeasure

measure。如果是view,measure绘制其自身。如果是VIewGroup,measure绘制自身外,还要绘制其子元素。先看View的measure方法,measure是一个final方法,不能重写:

if (cacheIndex < 0 |if (cacheIndex < 0 || sIgnoreMeasureCache ) {    // measure ourselves, this should set the measured dimension flag back    onMeasure(widthMeasureSpec , heightMeasureSpec);    mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; }| sIgnoreMeasureCache ) {    // measure ourselves, this should set the measured dimension flag back    onMeasure(widthMeasureSpec , heightMeasureSpec);    mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; }

调用了onMeasure(),来看下它的源码:

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

看下getSuggestedMinimumWidth(),它就是获取背景大小和mMinWidth的较大值:

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

那么mMinWidth是什么呢,mMinWidth就是设置的android:minWidth的属性,没设置就等于0。不信,看如下代码:

case R.styleable.View_minWidth:    mMinWidth = a.getDimensionPixelSize(attr , 0) ;    break;

getMinimumWidth()表示的是获取背景图大小,它位于Drawable下:

public int getMinimumHeight() {    final int intrinsicHeight = getIntrinsicHeight() ;    return intrinsicHeight > 0 ? intrinsicHeight : 0 ; }

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

它返回了specSize,它是测量后的大小。由上面的分析可知,view的宽高由specSize决定,而如果直接继承View的控件需要重写onMeasure方法并设置wrapcontent的自身大小。否则wrapcontent就相当 于Match_parent了。一般一重写方法如下:

protected void onMeasure( int widthMeasureSpec, int     heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);        if (widthSpecMode == MeasureSpec. AT_MOST                && heightSpecMode == MeasureSpec. AT_MOST) {            setMeasuredDimension(200, 200);        } else if (widthSpecMode == MeasureSpec. AT_MOST) {            setMeasuredDimension(200, heightSpecSize);        } else if (heightSpecMode == MeasureSpec. AT_MOST) {            setMeasuredDimension(widthSpecSize, 200);        }    }

上面的200是指定的一个默认宽高。

ViewGroup的measure过程,它没有重写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);        }    } }

分别绘制child,进入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); }

获取LayoutParams,通过getChildMeasureSpec来创建子无素的MeasureSpec,调用child.measure,因为ViewGroup有不同的特性,所以无法实现统一的onMeasure。


APP架构师
微信号:appjiagou(←长按复制)



请您养成良好的阅读习惯,看完文章,顺便点击下方广告 ,尊重小编的劳动成果,非常感谢!

以上是关于android View绘制源码分析(上)的主要内容,如果未能解决你的问题,请参考以下文章

android View绘制源码分析

Android视图View绘制流程与源码分析(全)

Android应用层View绘制流程与源码分析

源码分析Android中View的绘制流程

源码分析Android中View的绘制流程

深入理解 Android 之 View 的绘制流程