AndroidUI系列—源码分析View的绘制

欢迎关注作者的github https://github.com/BudSpore
本系列文章都有点长,首先AndroidUI显示的大致流程,并结合项目对视图,流畅度进行性能优化,最后整合一个大项目,认真读完,您一定有所收获。

AndroidUI系列—源码分析View的绘制_第1张图片
Paste_Image.png

Android视图显示整体流程是先绘制(CPU的工作)再渲染(GPU的工作),本篇文章先讲CPU做的部分事情,即View绘制,该文章源码都在Java层。
文章目录

  • 案例实战

  • 绘制的起点——测量

  • 绘制第二步——布局

  • 绘制第三步——绘制
  • 下面是Android屏幕组成架构

    Android屏幕架构


    AndroidUI系列—源码分析View的绘制_第2张图片
    Paste_Image.png
    下面对图中以及Android视图里的概念进行详细的介绍
    DecorView

    DecorView是一个应用窗口的根容器,它是整个页面的根View,也是一个FrameLayout布局,DecorView有2个子View,第一个子View就是屏幕这个部分,看图:

    Paste_Image.png

    第二个子View是一个垂直LinearLayout,包含两个子元素,一个是TitleView(ActionBar的容器),它就是我们看到的标题栏,另一个是ContentView(窗口内容的容器)。
    ContentView是一个FrameLayout(android.R.id.content),
    我们在Activity的生命周期的起点——onCreate回调方法里(如下图)使用setContentView就是成了它的子View。每个Activity都与一个PhoneWindow相关联,用户界面则由PhoneWindow所承载。注意,setContentView方法最后会调用到绘制的起点函数,后面将详细讲解哦~

    AndroidUI系列—源码分析View的绘制_第3张图片
    Paste_Image.png
    Canvas(给View用的画布)

    Canvas位于Java层,ViewGroup会把自己的Canvas拆分给子View。View会在onDraw方法里将图形数据绘制在它获得的Canvas上,在View类里通过建立这个Canvas类来构建绘画的基础,把Canvas想象成一个画布,笔(Paint)把View画在画布上,然后再把所有数据传递给Surface(下篇文章会讲到),Canvas是View树和Surface的连接点,android中的UI元素通过调用Canvas类来构建的。
    Canvas类处理“draw“的调用,当绘制(draw)内容时需要4个基本组件,保持像素的Bitmap,处理绘制调用 的canvas(写入Bitmap,bitmap用于存储surface内容),绘制 的内容(如,Rect,Path,text,Bitmap)和一个笔paint(描述颜色和样式)。

    Window

    应用通过 Window Manager创建一个window,Window Manager 为每一个window创建一个surface,并把该surface传递给应用以便应用在上面绘制,一个应用有很多Window。
    Window是屏幕上用于绘制各种UI元素(比如Button,TextView)及响应用户输入事件(键盘事件)的一个矩形区域,它独立绘制,不与其他界面产生影。在Android系统中,每个Window是独占一个Surface实例的显示区域,每个窗口的Surface由WindowManagerService分配。
    每个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,对于Activity来说,ViewRootImpl是连接WindowManager和DecorView的纽带,绘制的入口是由ViewRootImpl的performTraversals方法来发起Measure,Layout,Draw等流程的,当然performTraversals函数调用之前也有很多方法要做,这里不仔细分析。

    ViewRoot

    从上一段得知,WindowManagerGlobal里面有一个mRoots,也就是ViewRoot,它主要用来管理窗口的根 view,并跟 WindowManagerService 交互。

    WindowManager

    管理窗口的一些状态属性(view 的增加,删除,窗口位置,更新等等),它管理window和WindowManagerService交互,由ViewRoot完成。主要的方法有:addview(),updateViewLayout();removeView()。
    WindowManager是一个接口,WindowManagerImpl 是它的实现类。
    WindowManager 里面维护了三个变量:mViews(根view),mRoots(viewroot),mParams(一些相关变量),由 WindowManagerGlobal 维护它们。

    WindowManagerService

    对系统中的所有窗口进行管理,它和WindowManager区别是WindowManager管理的是单独的窗口,而WindowManagerService需要管理所有的窗口。
    Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时创建 ViewRootImpl 对象并将ViewRootImpl和 DecorView 建立关联。

    PhoneWindow

    它是Activity和整个View系统交互的接口,是Window的具体实现。


    AndroidUI系列—源码分析View的绘制_第4张图片
    Paste_Image.png
    MeasureSpac(规格测量)

    下面的测量过程用到了MeasureSpac,MeasureSpec是一个int类型的值,由高2位的规格模式和低30位的具体尺寸,由父View的MeasureSpec和子View的LayoutParams(LayoutParams就是我们在xml写的时候设置的layout_width和layout_height 转化而来的)通过计算得出一个针对子View的测量规格。

    好了,枯燥的概念讲完了,下面结合一个实例来详细讲解Android视图绘制的过程。

    案例实战


    看下面的activity_main的XML代码

    
    
        
        
    
    

    效果如图所示

    AndroidUI系列—源码分析View的绘制_第5张图片
    Paste_Image.png

    分析上面的视图:
    AndroidUI系列—源码分析View的绘制_第6张图片
    view1.png

    View树的整体结构:
    AndroidUI系列—源码分析View的绘制_第7张图片
    Paste_Image.png

    为了方便,我把header设置为Gone,去掉ActionBar的测量过程
    View的绘制分为measure、layout、draw 过程,绘制的View节点从根节点——DecorView开始。 绘制的起点就是上面讲的performTraversals函数:
    函数做的执行过程主要是根据之前设置的状态,判断是否重新计算视图大小(measure)、是否重新放置视图的位置(layout)、以及是否重绘 (draw),其核心也就是通过判断来选择顺序执行这三个方法中的哪个.

    AndroidUI系列—源码分析View的绘制_第8张图片
    Paste_Image.png

    绘制的起点

    绘制的起点——测量

    private void performTraversals() {
            ......
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            //mWidth和mHeight代表屏幕的宽高,lp是WindowManager.LayoutParams,它的lp.width和lp.height的默认值是MATCH_PARENT,
            ......
            // Ask host how big it wants to be
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
          
            ......
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
            ......
            performDraw();       
            ......
            =—=  这个函数太长了
        }
    
        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 = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
                    break;
                case ViewGroup.LayoutParams.WRAP_CONTENT:
                    // Window can resize. Set max size for root view.
                    measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.AT_MOST);
                    break;
                default:
                    // Window wants to be an exact size. Force root view to be that size.
                    measureSpec = View.MeasureSpec.makeMeasureSpec(rootDimension, View.MeasureSpec.EXACTLY);
                    break;
            }
            return measureSpec;
        }
    

    performTraversals传入参数后,getRootMeasureSpec函数里的switch case(张老师反复强调switch---case是一条语句)走的是MATCH_PARENT,使用MeasureSpec.makeMeasureSpec方法组装一个MeasureSpec,MeasureSpec的specMode等于EXACTLY,specSize等于windowSize,屏幕的宽高,也就是为何根视图总是全屏的原因。

    AndroidUI系列—源码分析View的绘制_第9张图片
    Paste_Image.png
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
            //Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
           // try {
                mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
          //  } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
           // }
        }
    
    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
           ...
                    // measure ourselves, this should set the measured dimension flag back
                    onMeasure(widthMeasureSpec, heightMeasureSpec);           
           ...       
        }
    

    因为DecorView 是一个FrameLayout 那么接下来会进入FrameLayout 的measure方法

     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把自己的MeasureSpec 
                    // 传给子View结合子View自己的LayoutParams 算出子View 的MeasureSpec,然后继续往下传,
                    // 传递叶子节点,叶子节点没有子View,根据传下来的这个MeasureSpec测量自己就好了。
    //注意DecorView的子节点(View)有2个。
                    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));
            ....
        }
    

    DecorView的第一个子节点是ViewRoot,它继承自LinearLayout,LinearLayout继承自ViewGroup,(跟你在XML里写的LinearLayout布局没关系)所以后面的measure函数调用的是LinearLayout类里的
    再次附上View树


    AndroidUI系列—源码分析View的绘制_第10张图片
    Paste_Image.png
    AndroidUI系列—源码分析View的绘制_第11张图片
    Paste_Image.png
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
      
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            //根据父View的测量规格和父View自己的Padding,
            // 还有子View的Margin和已经用掉的空间大小(widthUsed),就能算出子View的MeasureSpec
            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);
                    //通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,然后父容器传递给子容器的
                    // 然后让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,如果子View是ViewGroup 那还会递归往下测量。
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
        }
    

    大家注意下这个:

    Paste_Image.png

    Paste_Image.png

    父节点得到自己的MeasureSpac后,调用 child.measure 方法(因为它是 FrameLayout 只有一个直接子类),传入自己的MeasureSpac,开始测量子节点的MeasureSpac。
    如果child 是 viewgroup ,测量流程基本上如下:得到父 view 的MeasureSpac,算出自己的MeasureSpac,调用 measureChildren,多次调用 measureChild,进而调用 child.measure
    如果child 是 view,测量流程基本上如下:得到父 view 的 MeasureSpec,算出自己的MeasureSpac,设置自己的宽高(setMeasureDimension),并结束测量。
    这样就讲清楚了是如何得到MeasureSpac的了。

    而怎么得到子View的MeasureSpac,请看下面的图(省略了UPSPECIFIED模式: 父容器对于子容器没有任何限制,子容器想要多大就多大):

    AndroidUI系列—源码分析View的绘制_第12张图片
    Paste_Image.png
    // spec参数   表示父View的MeasureSpec 
    // padding参数    父View的Padding+子View的Margin,父View的大小减去这些边距,才能精确算出
    //               子View的MeasureSpec的size
    // childDimension参数  表示该子View内部LayoutParams属性的值(lp.width或者lp.height)
    //                    可以是wrap_content、match_parent、一个精确指(an exactly size),  
        public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            int specMode = MeasureSpec.getMode(spec);  //获得父View的mode  
            int specSize = MeasureSpec.getSize(spec);  //获得父View的大小  
    
            //父View的大小-自己的Padding-子View的margin,得到值才是子View的大小。
            int size = Math.max(0, specSize - padding);
    
            int resultSize = 0;    //初始化值,最后通过这个两个值生成子View的MeasureSpec
            int resultMode = 0;    //初始化值,最后通过这个两个值生成子View的MeasureSpec
    
            switch (specMode) {
                // Parent has imposed an exact size on us  
                //1、父View是EXACTLY的 !  
                case MeasureSpec.EXACTLY:
                    //1.1、子View的width或height是个精确值 (an exactly size)  
    /*
    子view是确定值,那么控件最后展示就是那个具体的值,与父View无关
    */
                    if (childDimension >= 0) {
                        resultSize = childDimension;         //size为精确值  
                        resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
                    }
                    //1.2、子View的width或height为 MATCH_PARENT/FILL_PARENT   
                    //如果一个View的MeasureSpec 是EXACTLY,那么它的size 是多大,最后展示到屏幕就一定是那么大
                    else if (childDimension == LayoutParams.MATCH_PARENT) {
                        // Child wants to be our size. So be it.  
                        resultSize = size;                   //size为父视图大小  
                        resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  
                    }
                    //1.3、子View的width或height为 WRAP_CONTENT  
                    /* 
                     我们还不知道具体子View的大小是多少,
                    要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 调用的时候
                    才去真正测量子View 自己content的大小
                    */
                    else if (childDimension == LayoutParams.WRAP_CONTENT) {
                        // Child wants to determine its own size. It can't be  
                        // bigger than us.  
                        resultSize = size;                   //size为父视图大小  
                        resultMode = MeasureSpec.AT_MOST;    //mode为AT_MOST 。  
                    }
                    break;
    
                // Parent has imposed a maximum size on us  
                //2、父View是AT_MOST的 !      
                case MeasureSpec.AT_MOST:
                    //2.1、子View的width或height是个精确值 (an exactly size)  
                    if (childDimension >= 0) {
                        // Child wants a specific size... so be it  
                        resultSize = childDimension;        //size为精确值  
                        resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY 。  
                    }
                    //2.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
                    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;                  //size为父视图大小  
                        resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
                    }
                    //2.3、子View的width或height为 WRAP_CONTENT  
                    else if (childDimension == LayoutParams.WRAP_CONTENT) {
                        // Child wants to determine its own size. It can't be  
                        // bigger than us.  
                        resultSize = size;                  //size为父视图大小  
                        resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  
                    }
                    break;
    
                // Parent asked to see how big we want to be  
                //3、父View是UNSPECIFIED的 !  
                case MeasureSpec.UNSPECIFIED:
                    //3.1、子View的width或height是个精确值 (an exactly size)  
                    if (childDimension >= 0) {
                        // Child wants a specific size... let him have it  
                        resultSize = childDimension;        //size为精确值  
                        resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY  
                    }
                    //3.2、子View的width或height为 MATCH_PARENT/FILL_PARENT  
                    else if (childDimension == LayoutParams.MATCH_PARENT) {
                        // Child wants to be our size... find out how big it should  
                        // be  
                        resultSize = 0;                        //size为0! ,其值未定  
                        resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  
                    }
                    //3.3、子View的width或height为 WRAP_CONTENT  
                    else if (childDimension == LayoutParams.WRAP_CONTENT) {
                        // Child wants to determine its own size.... find out how  
                        // big it should be  
                        resultSize = 0;                        //size为0! ,其值未定  
                        resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  
                    }
                    break;
            }
            //根据上面逻辑条件获取的mode和size构建MeasureSpec对象。  
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }
    

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec)会调用到LinearLayout的onMeasure函数,我们来看源码

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mOrientation == VERTICAL) {
                measureVertical(widthMeasureSpec, heightMeasureSpec);//垂直测量
            } else {
                measureHorizontal(widthMeasureSpec, heightMeasureSpec);//水平测量
            }
        }
    
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
             ...
            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();
    
                ....
                     //这个函数会调用measureChildWithMargins,然后调用child.measure,因为我去掉了ActionBar,
                   //所以ViewRoot的子View是content(FrameLayout布局),
                    //child.measure进入FrameLayout的onMeasure函数
                    measureChildBeforeLayout(  
                           child, i, widthMeasureSpec, 0, heightMeasureSpec,
                           totalWeight == 0 ? mTotalLength : 0);
                       ...
    //所有的孩子测量之后,经过一系类的计算之后通过setMeasuredDimension设置自己的宽高,
    //对于LinearLayout自己的测量,可能是高度的累加
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    heightSizeAndState);
        }
    

    ViewRoot 是系统的View,它的LayoutParams默认都是match_parent,根据我们文章最开始MeasureSpec 的计算规则,ViewRoot 的MeasureSpec mode应该等于EXACTLY(DecorView MeasureSpec 的mode是EXACTLY,ViewRoot的layoutparams 是match_parent),size 也等于DecorView的size
    对于content的lp.width lp.height都是系统设定的match_parent

    AndroidUI系列—源码分析View的绘制_第13张图片
    Paste_Image.png
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    .....
    for (int i = 0; i < count; i++) {
            ...关键代码
                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
               ...   
            }
    }
    
    protected void measureChildWithMargins(View child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
    ..... 
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
     mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, 
    lp.height); 
    ....
    }
    
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    ...
                int specSize = MeasureSpec.getSize(spec); //获得父View的大小  
                int size = Math.max(0, specSize - padding); 
    ...
                resultSize = size;//父View的大小
                resultMode = MeasureSpec.EXACTLY;
    ...
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
    

    由于ViewRoot 的mPaddingBottom=100px(这个可能和状态栏的高度有关,我们测量的最后会发现id/statusBarBackground的View的高度刚好等于100px,ViewRoot 是系统的View的它的Padding 我们没法改变,所以通过 Math.max(0, specSize - padding)计算出来Content(android.R.id.content) 的MeasureSpec 的高度用屏幕高度减去100px (假设结果是2460),它的宽高的mode 根据算出来也是EXACTLY(ViewRoot 是EXACTLY和android.R.id.content 是match_parent)。所以Content(android.R.id.content)的MeasureSpec高度少了100px。


    AndroidUI系列—源码分析View的绘制_第14张图片
    Paste_Image.png

    接下来测量content的子节点,即XML里的LinearLayout
    同理,父View(content)的MeasureSpec+子View(linear)的LayoutParams就得出了子View的宽高,这里注意的是XML里设置了margintop=200px,所以通过 Math.max(0, specSize - padding)让linear的高度用减少200px

    AndroidUI系列—源码分析View的绘制_第15张图片
    Paste_Image.png

    1、id/linear的heightMeasureSpec 的mode=AT_MOST,因为id/linear 的LayoutParams 的layout_height="wrap_content"
    2、id/linear的heightMeasureSpec 的size 少了200px, 由上面的代码
    padding=mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed;
    int size = Math.max(0, specSize - padding);
    由于id/linear 的 android:layout_marginTop="50dp" 使得lp.topMargin=200px (本设备的density=4,px=4*pd),在计算后id/linear的heightMeasureSpec 的size 少了200px。(布局代码前面已给出,可自行查看id/linear 控件xml中设置的属性)
    同理,对于TextView来说,linear的paddingButtom=70dp,70×4=280px,对于TextView来说,paddingTop=280,看图:


    AndroidUI系列—源码分析View的绘制_第16张图片
    Paste_Image.png

    然后遍历到WebView,高度是一个确定值,所以最后的高度就是其对应值

    Paste_Image.png

    附整体的MeasureSpec传递过程

    AndroidUI系列—源码分析View的绘制_第17张图片
    Paste_Image.png

    当遍历到叶子节点的时候,就测量叶子节点自身,TextView继承自View,就调用View类的onMeasure,WebView继承自AbsoluteLayout,就调用AbsoluteLayout的onMeasure
    id/linear 的子View的高度都计算完毕了,接下来id/linear就通过所有子View的测量结果计算自己的高宽,id/linear是LinearLayout,它的高度是子View的高度总和+自身padding.
    最终算出id/linear出来后,id/content 就要根据它唯一的子View id/linear 的测量结果和自己的之前算出的MeasureSpec一起来测量自己的结果,
    在FrameLayout的for循环退出后,测量自己

    Paste_Image.png

    而对于ViewRoot来讲,它是LinearLayout,for循环也结束了
    接下来测量ViewRoot,刚才讲到,它的测量就是子view的累加和和自己的padding,过程在onMeasure函数里
    然后再测量id/statusBarBackground,虽然不知道id/statusBarBackground 是什么,但是调试的过程中,测出的它的高度=100px, 和 id/content 的paddingTop 刚好相等。在最后DecorView退出for循环,测量自己 的高宽最终整个测量过程结束。所有的View的大小测量完毕。所有的getMeasureWidth 和 getMeasureWidth 都已经有值了。

    DecorView添加到窗口的过程:(感兴趣可以了解一下,这不是重点)


    AndroidUI系列—源码分析View的绘制_第18张图片
    Paste_Image.png

    绘制第二步——布局

    布局的基本思想也是由根View开始,递归地完成整个视图树的布局工作。
    接下来进入的是:

    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    

    别忘了View树


    AndroidUI系列—源码分析View的绘制_第19张图片
    Paste_Image.png
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                int desiredWindowHeight) {
           ...
            final View host = mView;
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
           ...
        }
    
    public final int getMeasuredWidth() {
            return mMeasuredWidth & MEASURED_SIZE_MASK;
        }
    
    public final int getMeasuredHeight() {
            return mMeasuredHeight & MEASURED_SIZE_MASK;
        }
    

    我们把对decorView的layout()方法的调用作为布局整个控件树的起点,实际上调用的是View类的layout()方法,源码如下:

    /**
    * l为本View左边缘与父View左边缘的距离 
      t为本View上边缘与父View上边缘的距离 
      r为本View右边缘与父View左边缘的距离 
      b为本View下边缘与父View上边缘的距离
    */
    public void layout(int l, int t, int r, int b) {
            if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
                onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
            ...
            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);
                   ...
               }
        }
    

    AndroidUI系列—源码分析View的绘制_第20张图片
    Paste_Image.png

    setFrame(l, t, r, b) 为mLeft 、mTop、mRight、mBottom赋值,然后基本就能确定View自己在父视图的位置了,这几个值构成的 矩形区域就是该View显示的位置,当初始化完毕后,ViewGroup的布局流程也就完成了,这里的具体位置都是相对与父视图的位置,然后在setFrame()方法中会判断View的位置是否发生了改变,若发生了改变,则需要对子View调用onLayout()方法进行重新布局,由于普通View( 非ViewGroup)不含子View,View类的onLayout()方法为空。ViewGroup的onLayout函数是抽象方法,每个子类实现它的方式都不相同。

    View类的onLayout函数

    Paste_Image.png

    这是一个空实现,主要作用是在我们的自定义View中重写该方法,实现自定义的布局逻辑。
    我们以decorView,也就是FrameLayout的onLayout()方法为例,它继承自ViewGroup,要重写onLayout函数,那我们就根据它来分析ViewGroup的布局过程:

    @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            //把父容器的位置参数传递进去
            layoutChildren(left, top, right, bottom, false /* no force left gravity */);
        }
    
    AndroidUI系列—源码分析View的绘制_第21张图片
    Paste_Image.png
    /**根据上面函数
    *  传入的四个值分别是0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()
    *DecorView的左上位置为0,然后宽高为它的测量宽高
    */
    void layoutChildren(int left, int top, int right, int bottom,
                                      boolean forceLeftGravity) {
            final int count = getChildCount();
            /*
             parentLeft 表示当前View为其子View显示区域指定的一个左边界,
             即子View显示区域的左边缘到父View的左边缘的距离,它由父容器的padding和Foreground决定
             */
            final int parentLeft = getPaddingLeftWithForeground();
             /*
             parentRight 表示当前View为其子View显示区域指定的一个右边界,
             即子View显示区域的右边缘到父View的右边缘的距离
             */
            final int parentRight = right - left - getPaddingRightWithForeground();
    
            final int parentTop = getPaddingTopWithForeground();
            final int parentBottom = bottom - top - getPaddingBottomWithForeground();
             //以上四个值确立子View的显示区域
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
                    final int width = child.getMeasuredWidth();
                    final int height = child.getMeasuredHeight();
    
                    int childLeft;//子View的左边缘距父View左边缘的距离
                    int childTop;//子View的上边缘距父View上边缘的距离
    
                    int gravity = lp.gravity;
                    if (gravity == -1) {
                        gravity = DEFAULT_CHILD_GRAVITY;
                    }
    
                    final int layoutDirection = getLayoutDirection();
                    final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                    final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
                   //当子View设置了水平方向的layout_gravity属性时,根据不同的属性设置不同的childLeft //childLeft表示子View的 左上角坐标X值
                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                     /* 水平居中,由于子View要在水平中间的位置显示,因此,要先计算出以下:
                     * (parentRight - parentLeft -width)/2 此时得出的是父容器减去子View宽度后的
                     * 剩余空间的一半,那么再加上parentLeft后,就是子View初始左上角横坐标(此时正好位于中间位置),
                     * 假如子View还受到margin约束,由于leftMargin使子View右偏而rightMargin使子View左偏,所以最后
                     * 是 +leftMargin -rightMargin .
                     */
    
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                            lp.leftMargin - lp.rightMargin;
                            break;
                          //水平居右,子View左上角横坐标等于 parentRight 减去子View的测量宽度 减去 margin
                        case Gravity.RIGHT:
                            if (!forceLeftGravity) {
                                childLeft = parentRight - width - lp.rightMargin;
                                break;
                            }
                         //如果没设置水平方向的layout_gravity,那么它默认是水平居左 
                         //水平居左,子View的左上角横坐标等于 parentLeft 加上子View的magin值
                        case Gravity.LEFT:
                        default:
                            childLeft = parentLeft + lp.leftMargin;
                    }
                           //当子View设置了竖直方向的layout_gravity时,根据不同的属性设置同的childTop 
                           //childTop表示子View的 左上角坐标的Y值 
                           //分析方法同上   
                    switch (verticalGravity) {
                        case Gravity.TOP:
                            childTop = parentTop + lp.topMargin;
                            break;
                        case Gravity.CENTER_VERTICAL:
                            childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                            lp.topMargin - lp.bottomMargin;
                            break;
                        case Gravity.BOTTOM:
                            childTop = parentBottom - height - lp.bottomMargin;
                            break;
                        default:
                            childTop = parentTop + lp.topMargin;
                    }
                    ////对子元素进行布局,左上角坐标为(childLeft,childTop),右下角坐标为(childLeft+width,childTop+height)
                    //若子View是容器View,则会递归地对其子View进行布局。  
                    child.layout(childLeft, childTop, childLeft + width, childTop + height);
                }
            }
        }
    

    上面的源码涉及到android:layout_gravity属性,记住,你的orientation设置为horizontal,横向排列则只能竖向居中,当你设置为vertical竖向排列则只能横向居中。(LinearLayout如果设置 android:orientation="vertical",那么android:layout_gravity的设置只在水平方向生效,android:orientation="horizontal",那么android:layout_gravity属性只在垂直方向生效)
    比如我举一个例子

    
    
        
    
    
    

    显示效果如图

    AndroidUI系列—源码分析View的绘制_第22张图片
    Paste_Image.png

    回到我们的主话题中来,以上就是首先先获取父容器的padding值,然后遍历其每一个子View,根据子View的layout_gravity属性、子View的测量宽高、父容器的padding值、来确定子View的布局参数,然后调用child.layout方法,把布局流程从父容器传递到子元素。如果子View是一个ViewGroup,那么就会重复以上步骤,如果是一个View,那么会直接调用View的layout方法,根据以上分析,在该方法内部会设置view的四个布局参数,接着调用onLayout的空方法。

    在for循环过程中,会布局到LinearLayout,child.layout函数会调用到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);
            }
        }
    
    AndroidUI系列—源码分析View的绘制_第23张图片
    Paste_Image.png
    void layoutVertical(int left, int top, int right, int bottom) {
            final int paddingLeft = mPaddingLeft;
    
            int childTop;
            int childLeft;
            
            // Where right end of child should go
    //获得子视图可以用的宽度,顺便也把子视图左边沿的位置计算出来。
            final int width = right - left;
            int childRight = width - mPaddingRight;
            
            // Space available for child
            int childSpace = width - paddingLeft - mPaddingRight;
            
            final int count = getVirtualChildCount();
    //根据父视图中的gravity属性,决定子视图的起始位置。
            final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
            final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    
            switch (majorGravity) {
               case Gravity.BOTTOM:
                   // mTotalLength contains the padding already
                   childTop = mPaddingTop + bottom - top - mTotalLength;
                   break;
    
                   // mTotalLength contains the padding already
               case Gravity.CENTER_VERTICAL:
                   childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
                   break;
    
               case Gravity.TOP:
               default:
                   childTop = mPaddingTop;
                   break;
            }
            //遍历所有子视图,为它们分配位置
            for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    childTop += measureNullChild(i);
                } else if (child.getVisibility() != GONE) {
                    final int childWidth = child.getMeasuredWidth();
                    final int childHeight = child.getMeasuredHeight();
                    
                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();
                    
                    int gravity = lp.gravity;
                    if (gravity < 0) {
                        gravity = minorGravity;
                    }
                    final int layoutDirection = getLayoutDirection();
                    final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                    + lp.leftMargin - lp.rightMargin;
                            break;
    
                        case Gravity.RIGHT:
                            childLeft = childRight - childWidth - lp.rightMargin;
                            break;
    
                        case Gravity.LEFT:
                        default:
                            childLeft = paddingLeft + lp.leftMargin;
                            break;
                    }
    
                    if (hasDividerBeforeChildAt(i)) {
                        childTop += mDividerHeight;
                    }
    
                    childTop += lp.topMargin;
                     //还会调用child.layout()为子视图设置布局位置.
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight);
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
    
                    i += getChildrenSkipCount(child, i);
                }
            }
        }
    
    private void setChildFrame(View child, int left, int top, int width, int height) {        
            child.layout(left, top, left + width, top + height);
        }
    

    布局到TextView,同样会调用到TextView类的onLayout方法

    @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            if (mDeferScroll >= 0) {
                int curs = mDeferScroll;
                mDeferScroll = -1;
                bringPointIntoView(Math.min(curs, mText.length()));
            }
        }
    

    同理WebView也布局完毕,上面的layoutChildren函数里的for循环结束,所有View就布局完毕。

    绘制第三步——绘制


    到了View绘制的最后一步


    AndroidUI系列—源码分析View的绘制_第24张图片
    Paste_Image.png
    performDraw();
    
    private void performDraw() {
        ...
               final boolean fullRedrawNeeded = mFullRedrawNeeded;
    //fullRedrawNeeded参数判断是否需要重新绘制全部视图,
     //如果是第一次绘制视图,那么绘制所有的视图,
                mFullRedrawNeeded = false;
                 //绘画逻辑都在这个函数里实现
                draw(fullRedrawNeeded);
    ....
       if (mSurfaceHolder != null && mSurface.isValid()) {
                    mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
                    SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                    if (callbacks != null) {
                        for (SurfaceHolder.Callback c : callbacks) {
                            if (c instanceof SurfaceHolder.Callback2) {
                                ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
                                        mSurfaceHolder);
                            }
                        }
                    }
                }
    ....
        }
    
    /*
    
    */
    private void draw(boolean fullRedrawNeeded) {
            Surface surface = mSurface;
              ...
             final Rect dirty = mDirty;//表示重绘区域
            if (mSurfaceHolder != null) {
                // The app owns the surface, we won't draw.
                dirty.setEmpty();
                if (animating) {
                    if (mScroller != null) {
                        mScroller.abortAnimation();
                    }
                    disposeResizeBuffer();
                }
                return;
            }
    
          ...
    //如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制 
    //第一次绘制流程,需要绘制所有视图
            if (fullRedrawNeeded) {
                mAttachInfo.mIgnoreDirtyState = true;
                dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
            }
    ...
                    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) 
                       {
                        return;
                       }
          ...
        }
    
     private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                boolean scalingRequired, Rect dirty) {
    
            // Draw with software renderer.
            final Canvas canvas;
         ...
                //还记得文章开始讲的Surface的工作流程吗
               /*- create a bitmap
              - attach a canvas to it
              - do the rendering into that canvas
              - lockCanvas
              - draw your bitmap into the backbuffer
              - unlockAndPost
              */
                canvas = mSurface.lockCanvas(dirty);
    ...
                if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                }
    
          ...
                    canvas.translate(-xoff, -yoff);
                   ...
                    canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                    attachInfo.mSetIgnoreDirtyState = false;
                    //decorView就是起点,也就是View.draw()方法
                    mView.draw(canvas);
    ...
                    surface.unlockCanvasAndPost(canvas);
              ...
        }
    

    终于到了绘画的逻辑实现函数了

    public void draw(Canvas canvas) {
    /*我们首先来看一开始的标记位dirtyOpaque,该标记位的作用是判断当前View是否是透明的,
    如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,
    比如绘制背景、绘制内容等。这样很容易理解,
    因为一个View既然是透明的,那就没必要绘制它了
    */
           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
          ...
                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)
             */
           ...
        }
    

    我对上面的步骤翻译一下
    1.对View的背景进行绘制
    2.保存当前的图层信息(可跳过)
    3.绘制View自身的内容
    4.对View的子View进行绘制(如果有子View)
    5.绘制View的褪色的边缘,类似于阴影效果(可跳过)
    6.绘制View的滚动条

    第一步是绘画背景

    private void drawBackground(Canvas canvas) {
            final Drawable background = mBackground;//mBackground是该View的背景参数,比如背景颜色
            if (background == null) {
                return;
            }
            //mRight - mLeft, mBottom - mTop layout确定的四个点来设置背景的绘制区域 
            setBackgroundBounds();
    
            // Attempt to use a display list if requested.
            if (canvas.isHardwareAccelerated() && mAttachInfo != null
                    && mAttachInfo.mHardwareRenderer != null) {
                mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
    
                final RenderNode renderNode = mBackgroundRenderNode;
                if (renderNode != null && renderNode.isValid()) {
                    setBackgroundRenderNodeProperties(renderNode);
                    ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                    return;
                }
            }
    //获取当前View的mScrollX和mScrollY值
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            if ((scrollX | scrollY) == 0) {
                background.draw(canvas);
            } else {
    //如果scrollX和scrollY有值,则对canvas的坐标进行偏移,再绘制背景
                canvas.translate(scrollX, scrollY);
    //调用Drawable的draw() 把背景图片画到画布上
                background.draw(canvas);
                canvas.translate(-scrollX, -scrollY);
            }
        }
    

    跳过第2步

    第3步,绘画自己View内容

    onDraw(canvas) 方法是view用来draw 自己的,具体如何绘制,颜色线条什么样式就需要子View自己去实现,View类的onDraw(canvas) 是空实现,ViewGroup 也没有实现,每个View的内容是各不相同的,所以需要由子类去实现具体逻辑。
    对于decorView来说,由于它是容器View,所以它本身并没有什么要绘制的,关键在下面.
    第4步,绘画当前View的子View

    对于View类来讲,它就是叶子节点,没有子节点,所以它的dispatchDraw(canvas)空
    所以调用的是ViewGroup类的dispatchDraw(canvas)方法

    protected void dispatchDraw(Canvas canvas) {
        ...
            for (int i = 0; i < childrenCount; i++) {
                while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                    final View transientChild = mTransientViews.get(transientIndex);
                    if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                            transientChild.getAnimation() != null) {
                         //关键代码,依次调用drawChild()方法来绘制子View
                        more |= drawChild(canvas, transientChild, drawingTime);
                    }
                    transientIndex++;
                    if (transientIndex >= transientCount) {
                        transientIndex = -1;
                    }
                }
                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);
                }
            }
        ...
        }
    
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            return child.draw(canvas, this, drawingTime);
        }
    

    drawChild()的核心过程就是为子视图分配合适的cavas剪切区,剪切区的大小正是由layout过程决定的,而剪切区的位置取决于滚动值以及子视图当前的动画。设置完剪切区后就会调用子视图的draw()函数进行具体的绘制了。
    首先对canvas进行了一系列变换,以变换到将要被绘制的View的坐标系下。完成对canvas的变换后,便会调用View.draw(Canvas)方法进行实际的绘制工作,此时传入的canvas为经过变换的,在将被绘制View的坐标系下的canvas。

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    ...
    /*首先判断是否已经有缓存,即之前是否已经绘制过一次了,
    如果没有,则会调用draw(canvas)方法,开始正常的绘制,即上面所说的六个步骤,
    否则利用缓存来显示。
    */
    if (!drawingWithDrawingCache) {
                if (drawingWithRenderNode) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                } else {
                    // Fast path for layouts with no backgrounds
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                        dispatchDraw(canvas);
                    } else {
                        draw(canvas);
                    }
                }
            } else if (cache != null) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                if (layerType == LAYER_TYPE_NONE) {
                    // no layer paint, use temporary paint to draw bitmap
                    Paint cachePaint = parent.mCachePaint;
                    if (cachePaint == null) {
                        cachePaint = new Paint();
                        cachePaint.setDither(false);
                        parent.mCachePaint = cachePaint;
                    }
                    cachePaint.setAlpha((int) (alpha * 255));
                    canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
                } else {
                    // use layer paint to draw the bitmap, merging the two alphas, but also restore
                    int layerPaintAlpha = mLayerPaint.getAlpha();
                    mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                    canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                    mLayerPaint.setAlpha(layerPaintAlpha);
                }
            }
    }
    

    这一步就是ViewGroup绘制过程,它对子View进行了绘制,而子View又会调用自身的draw方法来绘制自身,这样不断遍历子View及子View的不断对自身的绘制,从而使得View树完成绘制。
    跳过第5步

    第六步,绘画View的滚动条

    public void onDrawForeground(Canvas canvas) {
            onDrawScrollIndicators(canvas);
            onDrawScrollBars(canvas);
    
            final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
            if (foreground != null) {
                if (mForegroundInfo.mBoundsChanged) {
                    mForegroundInfo.mBoundsChanged = false;
                    final Rect selfBounds = mForegroundInfo.mSelfBounds;
                    final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
    
                    if (mForegroundInfo.mInsidePadding) {
                        selfBounds.set(0, 0, getWidth(), getHeight());
                    } else {
                        selfBounds.set(getPaddingLeft(), getPaddingTop(),
                                getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
                    }
    
                    final int ld = getLayoutDirection();
                    Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                            foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
                    foreground.setBounds(overlayBounds);
                }
    
                foreground.draw(canvas);
            }
        }
    

    基本思想就是先设定绘制区域,然后利用canvas进行绘制,至此,View的绘制过程就完成了,
    感谢UIT学长对文章的审阅,我对文章的细节再次进行了删减优化,特别感谢老姐对文章的纠错,作者将不断完善该篇文章,希望读者能提出宝贵意见。
    下一篇文章我将会讲解AndroidUI渲染。

    你可能感兴趣的:(AndroidUI系列—源码分析View的绘制)