View的绘制流程

View的绘制流程

一、视图层级结构关系

1. View的层级关系
在正式分析View的绘制流程之前,我们先来了解一下View的层级视图关系。
先看一下View层级关系图:
View的绘制流程_第1张图片

Activity在创建的同时会创建一个Window,在这个Window中我们所看到的DecorView就是最顶层的View,而且它其实是一个FrameLayout。在Android的源码中我们可以看到DecorView是继承FrameLayout实现的。

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
	// ......
}

在这个层级视图中我们还可以看到DecorView中包含了一个LinearLayout,这个LinearLayout又包含了两个FrameLayout:

  1. 上面部分的是TitleBar,可以通过在AndroidManifest.xml文件中application中设置无TitleBar的theme;也可以在Activity中单独设置某个Activity的theme。

    <application
            android:name=".xxApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:screenOrientation="portrait"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
    
  2. 下面的FrameLayout部分(android.R.id.content)就是我们通常编写的layout内容,从代码上直观来说的话就是我们setContentView (layoutId)添加的布局。

        @Override
        public void setContentView(int resId) {
            ensureSubDecor();
            ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            LayoutInflater.from(mContext).inflate(resId, contentParent);
            mOriginalWindowCallback.onContentChanged();
        }
    

    从源码中我们可以看到setContentView()这个方法其实就是将我们的布局加载到了名为:android.R.id.content的ViewGroup中。

    2. DecorView、WindowManager和ViewRoot三者之间的关系

    上文已经我们已经知道了DecorView是最顶层view,承载着我们编写的layout。

    WindowManager是一个继承ViewManager的系统服务,主要用来管理窗口的状态、属性、View的增加删除更新、消息处理。

    package android.view;
    
    public interface ViewManager
    {
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }
    
    @SystemService(Context.WINDOW_SERVICE)
    public interface WindowManager extends ViewManager {
        //......
        
    }
    

    ViewRoot实现了ViewRootImpl类,是连接WindowManager和DecorView的纽带,View的绘制是有ViewRoot来完成的。

     root = new ViewRootImpl(view.getContext(),display);
     root.setView(view,wparams, panelParentView);
    

    当Activity对象创建完毕后,DecorView会被添加到Window中并创建ViewRootImpl对象,然后将ViewRootImpl对象和DecorView关联在一起。

    这里有涉及到Window的概念,在Android的源码中Window是一个抽象类PhoneWindow是它的唯一实现(源码中对Window的注释自己说的),它是窗口外观和行为策略的抽象基类。它提供了标准的UI策略,例如背景,标题区域,默认密钥处理等。

    到这里我们就可以在完善一下View的层级结构图:
    View的绘制流程_第2张图片


二、View绘制的流程

总的来说View的绘制流程可以用三个方法进行概括:

  1. measure:测量View的宽高
  2. layout:确定View的位置
  3. draw:绘制View的内容

View的绘制都是通过ViewRoot来负责的。每个应用程序窗口DecorView都有一个与之关联的ViewRoot对象,他们之间通过WindowManager建立关联。

View的绘制流程从ViewRoot的performTraversals开始,performTraversals会依次调用performMeasureperformLayoutperformDraw。这三个方法会依次完成整个View树的measure、layout和Draw。

我们以performMeasure()方法为例,该方法会调用measure方法,measure方法又会去调用onMeasure方法;onMeasure方法则会对所有子元素进行measure,其子元素又会重复这个过程。

performLayout和performDraw这两个方法的过程和performMeasure是一样的。

1. View的大小测量——measure

在View的大小测量中有一个关键的类MeasureSpec。MeasureSpec是一个32位的二进制数据,主要由两部分组成:

低30位,表示测量大小size

高2位(即,第31、32位),表示测量模式mode

其中Mode分为三类:

  1. UNSPECIFIED:父容器没有对View进行任何约束,View可以是任意大小,一般用于系统内部(如ListView、ScrollView)。

  2. EXACTLY:View的大小是确定的(如:match_parent或具体的数值10dp),它的最终大小就是SpecSize指定的大小。

  3. AT_MOST:View的尺寸最多不能大于父容器的大小,对应于wrap_content

一个View大小的确定是通过其本身的LayoutPrarms和父布局的MeasureSpec共同决定的。

ViewonMeasure方法决定了View的大小,其主要是依靠setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)来完成的。onMeasure的默认实现如下:

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

在源码中我们还看到了getDefaultSize(),在这个方法中根据specMode重新确定了View的大小。

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

可以看到在View的源码中,当 SpecModeMeasureSpec.UNSPECIFIED时View的大小使用的是getSuggestedMinimumWidth()getSuggestedMinimumHeight()即为:视图应使用的建议最小宽度或高度。当 SpecModeMeasureSpec.AT_MOST或MeasureSpec.EXACTLY时都是直接使用specSize。如果specModeEXACTLY自然是没有问题的,但是当View的specModeAT_MOST时,该View的宽或高会等于父容器的大小。

因此我们在自定义View时,可以根据情况重写onMeasure()方法(如果自定义View的大小和父控件的测量规则一致,就不需要重写):

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //不调用父类的方法  使用我们自定义的规则 确定控价的大小
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        int width = 100;
        int height = 100;
        setMeasuredDimension(
                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()
        );
    }

以上代码没有实际意义仅作示例使用,不同的自定义View具有不同的规则,需要根据自己的情况来写。

measure的过程我们用流程图的形式可以展示的更加清楚:
View的绘制流程_第3张图片

2.View的位置确定 —— layout

从整个绘制流程上来说,layout的流程和measure是类似的。都是从performTraversals()方法开始的,调用内部的performLayout()然后在performLayout()方法中调用layout(),然后对子View进行遍历确定它们d的位置。

3.View的绘制 —— draw

measure()layout()一样,View的绘制也是从ViewRootImpl中的performTraversals()方法中开始的,然后遍历绘制所有的子View。

整个绘制的过程,共分为六个步骤(其中第2和5步可以忽略),因此我们只需要关注其中四个步骤:

  1. 对View的背景进行绘制 —— drawBackground(canvas)
  2. 对View的内容进行绘制 —— onDraw(canvas),每个View各不相同需要自己重写绘制的方法;
  3. 对View的所有子View进行绘制——dispatchDraw(canvas),如果没有子View就不需要绘制;
  4. 对View的滚动条进行绘制 —— onDrawScrollBars(),在源码中我们可以看到其实任何View都有水平和垂直滚动条,只是没有显示而已。

4.invalidate、postInvalidate和requestLayout的作用

  1. invalidate:执行draw的过程,重新绘制。

    当View的appearance(外观、外形)发生改变时,比如状态(enable、focus)、背景改变、隐藏显示改变,这些都属于appearance,都会引起invalidate操作。

    当我们需要改变View的界面内容时,可以通过调用invalidate。

    View调用invalidate重新绘制自身,ViewGroup调用则会重绘整个View树。

  2. postInvalidate:在非UI线程中通过调用postInvalidate完成重新绘制,在UI线程中可以直接调用invalidate进行重新绘制。

  3. requestLayout:View的边界发生变化(宽高发生变化),可以调用requestLayout重新进行布局。

    View调用requestLayout时,会向上递归到顶层View中,然后调用顶层View的requestLayout方法,对整个界面进行重新测量和布局。

    可能会调用onMeasure和onLayout,不会调用onDraw。


如果你想更详细和深入的了解View的绘制原理,可以关注一下这篇博客——Android应用层View绘制流程与源码分析。

你可能感兴趣的:(android学习笔记,android,measure,layout,draw)