Android View(四)——View的工作原理

一.初识ViewRoot 和 DecorView

  • DecorView
    DecorView是整个Window界面的最顶层View。DecorView只有一个子元素为LinearLayout。代表整个Window界面,包含通知栏,标题栏,内容显示栏三块区域。
  • ViewRoot
    ViewRoot是连接WIndowManager和DecorVIew的纽带。View的三大流程(measure layout draw)都是通过ViewRoot完成的。在ActivityThread中,Activity创建后,DecorView会被添加到Window中,同时创建ViewRootImpl,然后将DecorView与ViewRootImpl建立关联(通过ViewRoot的setView方法)。
     

一张图看明白ViewRoot 和 DecorView

在这里插入图片描述

二.View的工作流程概述

View 的绘制流程是从 ViewRoot 的 performTraversals 方法开始的,它经过 measure、layout 和 draw 三个过程才能最终将一个 View 绘制出来

  • measure方法用于测量View的宽高
  • layout方法用于确定View在父容器中的位置
  • draw方法负责把View绘制在屏幕上

大致流程图如下:

在这里插入图片描述
理解:
      
performTraversals 会依次调用 performMeasure、performLayout 和 performDraw 三个方法,这三个方法分别完成顶级 View 的 measure、layout 和 draw 这三大流程,其中在 performMeasure 中会调用 measure 方法,在 measure 方法中又会调用 onMeasure 方法,在 onMeasure 方法中则会对所有的子元素进行 measure 过程,这个时候 measure 流程就从父容器传递到子元素中了,这样就完成了一次 measure 过程。接着子元素会重复父容器的 measure 过程,如此反复就完成了整个 View 树的遍历。
      
同理,performLayout 和 performDraw 的传递流程和 performMeasure 是类似的,唯一不同的是,performDraw 的传递过程是在 draw 方法中通过 dispatchDraw 来实现的,不过这并没有本质区别。

View绘制过程中的一些方法介绍

方法 作用 被赋值的时间
getMeasuredWidth/getMeasureHeight 获取View测量后的宽/高 Measure完成以后
getTop/getBottom/getLeft/getRight VIew四个顶点的坐标 layout完成以后
getWidth/getHeight 拿到View的最终宽/高 layout完成之后

三.理解MeasureSpec

1.MeasureSpec是干什么的?

MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说很大程度上是因为这个过程还受父容器的影响。在测量过程中,系统会将View的LayoutParams根据父容器所施加的转换规则转换为对应的MeasureSpec,然后再根据这个MeasureSprc来测量出View的宽/高。

2.MeasureSpec的定义

MeasureSpec代表一个32位的int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(在某种测量模式下的规格大小)。

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

        //将 size和mode打包成一个MeasureSpec
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
   
            if (sUseBrokenMakeMeasureSpec) {
   
                return size + mode;
            } else {
   
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
       //MeasureSpec解包出mode
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
   
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
        //MeasureSpec解包出size
        public static int getSize(int measureSpec) {
   
            return (measureSpec & ~MODE_MASK);
        }       

MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的内存分配,为了方便操作,其提供了打包和解包方法。
      
SpecMode有三类:

类别 说明 使用时机
UNSPECIFIED 父容器不对View有任何限制,要多大给到大 这种情况一般用于系统内部,表示一直测量出状态
EXACTLY 父容器已经检测出View的精确的大小,这时候View的最终大小就是SpecSize确定的值 对应LayoutParams中的match_parent和具体数值这两种模式
AT_MOST 父容器指定一个可用大小即SpecSize,View的大小不能大于这个值 对应LayoutParent中的wrap_content

      

3.MeasureSpec和LayoutParams的对应关系

在View测量的时候,系统会将LayoutParams在父容器的约束下转换为对应的MeasureSpec,然后在根据这个MeasureSpec来确定View测量后的宽\高。
      
另外,对于顶级View(DecorView)和普通View来说,MeaSureSpec的转换过程略有不同。

3.1 顶级View(DecorView)

对于DecorView,其MeasureSpec由窗口的尺寸和自身的LayoutParams来共同决定。
      
源码分析:

/*
ViewRootImpl.measureHierarchy其中的一段代码
	这段代码展示了DecorView的MeasureSpec创建过程
	desiredWindowWidth,desiredWindowHeight是屏幕尺寸
*/
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

/*
ViewRootImpl.getRootMeasureSpec源码
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
   
        int measureSpec;
        switch (rootDimension) {
   

        case ViewGroup.LayoutParams.MATCH_PARENT: //精确模式,大小就是窗口的大小
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT: //最大模式,大小不定,但不能超酷窗口大小
           
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default: //精确模式,大小为LayoutParams中指定的大小
           
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
3.2 普通View

View的measure过程由ViewGroup传递而来
      
源码分析:

/*
ViewGroup.measureChildWithMargins方法
改方法会对子元素进行measure,在调用子元素的measure方法之前会先通过getChildMeasureSpec
方法得到子元素的MeasureSpec
*/
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
   
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
		/*
		获得子元素的MeasureSpec,可以看出子元素MeasureSpec的确定不止和父元素的MeasureSpec,
		自身的LayoutParams有关,还和View的margin和padding有关
		
		*/
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值