Android之View的绘制流程解析

转载请标明出处:【顾林海的博客】

个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持!
在这里插入图片描述

##前言
自定义View在Android中占据着非常重要的地位,因此了解View的绘制流程对自定义View来说尤其重要,View的绘制流程总的来说包含测量、布局和绘制三个流程,本篇会对这三个流程进行详细的讲解,力求对View的绘制流程有清晰的认识。

##视图绘制

Activity代表着一个用于与用户进行交互的窗口,通常启动一个Activity时,会通过setContentView方法来加载需要显示的布局文件,这个布局会被添加到内容视图中,就像下图一样:

这里写图片描述

布局被添加到ContentView中,ContentView是一个FrameLayout,图中PhoneWindow是Activity与View进行交互的接口,创建一个Activity时都会创建一个PhoneWindow,PhoneWindow会创建一个DecorView,这个DecorView就是根视图,在DecorView中通常会包含一个TitleView用于显示ActionBar,以及ContentView,ContentView就是内容视图,平时创建的Layout会被添加到这个ContentView。
启动Activity时会对布局进行绘制,绘制从ViewRoot(或是ViewRootImpl)的performTraversals方法开始,代码简化如下:

private void performTraversals(){
    ...
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    performLayout();
    ...
    performDraw();
    ...
}

performTraversals方法中performMeasure方法执行的是测量流程,performLayout方法执行的是布局流程,performDraw方法执行的是绘制流程。

##MeasureSpec

MeasureSpec是View内部的一个静态内部类,表示的是一个32位的整型值,高2位表示的是测量模式SpecMode,低30位表示的是测量大小SpecSize,可以通过MeasureSpec获取View的测量模式和测量大小,从而更合理的设置View的大小。

public static class MeasureSpec {
   
    ...
    
    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;
    
    ...

}

MeasureSpec提供了三种测量模式,分别是UNSPECIFIED、EXACTLY和AT_MOST,三种测量模式的含义如下:

  • UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
  • EXACTLY:精确测量模式,当前视图的layout_width和layout_height指定为具体数值或是match_parent时生效,这种模式下View的测量值就是SpecSize值。
  • AT_MOST:最大值模式,当前视图的layout_width和layout_height指定为wrap_content时生效,此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸。

##绘制流程

在上面ViewRoot(或是ViewRootImpl)的performTraversals方法中了解到了在根视图中绘制分为三个步骤,分别是测量、布局和绘制,在具体的测量阶段会将测量操作分发给View来执行,如下代码

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

在ViewGroup中会将测量操作交由measureChildren,如下图:

这里写图片描述

在measureChildren方法中会通过遍历调用子View的measure方法进行测量,View的measure方法如下:

这里写图片描述

在measure方法中测量操作最终通过回调onMeasure方法实现的,由于measure方法是final类型的所以子类就不能重写这个方法,那如何在自定义View时重新测量大小呢?这时可以重写onMeasure方法,最终通过调用View的setMeasureDimension方法设置View的尺寸。如果自定义View时没有重写onMeasure方法,这时View的大小默认会通过getDefaultSize方法来获取。

这里写图片描述

其中getSuggestedMinimumWidth和getSuggestedMinimumHeight这两个方法分别获取的是当前View的最小宽高。

View的测量完毕后的下一个流程是布局,在ViewRootImpl中将布局操作交由View来实现,如下图(ViewRootImpl):

这里写图片描述

如果是ViewGroup,会重写View的layout方法来实现所有View控件的布局流程:

这里写图片描述

ViewGroup中的layout方法会调用它的基类也就是View的layout进行View的布局。由于layout方法使用final修饰,因此自定义View时可以通过重写onLayout方法来实现View的布局,默认在ViewGroup和View中的onLayout方法是空实现的,并且在ViewGroup中onLayout方法是一个抽象方法,子类继承ViewGroup时,必须实现这个方法。

这里写图片描述

这这里举个LinearLayout的布局的例子,LinearLayout大家肯定熟悉,它是一个线性布局,内部元素按水平或垂直排列,并且LinearLayout是继承自ViewGroup,因此LinearLayout的布局肯定是实现onLayout方法来实现的:

这里写图片描述

以上就是View绘制流程中的布局流程,最后来分析绘制流程,查看ViewRootImpl中的绘制操作:

这里写图片描述

最终调用到每个View的draw方法:

这里写图片描述

其中onDraw方法是用于绘制内容,在View中onDraw方法是空实现的:

protected void onDraw(Canvas canvas) {
}

需要子类实现,并通过Canvas进行绘制。

View的绘制流程已经讲解完毕,希望对大家有所帮助。

©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页