View的工作原理《Android开发艺术探索》笔记

参考链接

要点提炼|开发艺术之View,基本上图都是从那里找到的,这个博主也是个大佬,建议关注。

DecorView与ViewRoot

一句话概括DecorView是最外层的ViewGroup,也可以理解为Window界面的顶级View。在事件到来时,也是先发到DecorView在传到具体的View。如图所示,他会包含一个LinearLayout,其中content就是用来装我们setContentView加载近的布局文件。
在这里插入图片描述

View的工作流程

measure->layout->draw

1.measure:测量view的宽高;获得了宽高,可以通过getWidth和getHeight获取。

2.layout:计算View应该放置在父View的位置确定了View最终的宽高和四个顶点坐标。可以通过getWidth和getHeight获得最终的宽高。以及getBottom和getTop、getLeft与getRight获取四个顶点。

3.draw:将View绘制。**

具体流程

1.ViewRoot是连接WindowManger与DecorView的。ViewRoot对应ViewRootImpl类。在Activity对象创建后,会将DecorView放入Window,同时创建ViewRootImpl对象。
在这里插入图片描述

2.View的绘制是由ViewRoot的performtravelsals(中文为遍历的意思)方法开始的。会依次进行performmeaure,performLayout,performDraw方法。

3.首先会调用顶层View的TperformMeasure,接下来会调用measure方法,紧接着调用onMeasure方法,对全部子元素进行测量,此时就转向了子元素的measure方法,重复这个过程,一个完整的View树就测量完毕。performLayout和performDraw同理。

在这里插入图片描述

measureSpec

view的measure过程与measureSpec密切相关。

1.measureSpec是一个32位的,其前两位为标识位SpecMode,标识选择哪种模式,其后30位为specSize(某种测量模式下的规格大小)。

2.measureSpec共有三种模式
1).AT_MOST(最大模式):由父View给出一个不能超过的Specsize,对应layoutparams的wrap_content
2).Excatly(确定模式):由父View给出一个确定的Specsize,对应layoutparams的match_parent或给定数值
3)UNSPECIFIED模式:父View不决定其大小,一般用于系统内部。
.
3.在父View向子View传递measure函数时,会首先计算子View的widthmeasureSpecheightmeasureSpec,并传递。因此可知子View的MeasureSpec与父View的MeasureSpec与自身的layoutparam有关。
在这里插入图片描述
4.子View的MeasureSpec与父View的MeasureSpec与layoutparam的关系如下表:
在这里插入图片描述
1)如果layoutparams传入的是具体的数值则无论父View是什么模式,子View都是Excatly模式,且size就是子View的长度
2)如果layoutparams传入的是matchparent,则根据父View是什么模式,子View就是什么模式,然后size是父View的长度
3)如果layoutparams传入的是wrap_content,则无论父View是什么模式,子View都是AT_MOST模式,size是父View的长度。

5.普通View的measureSpec与父View的measureSpec与LayoutParams有关,而DecorView与窗口大小与自身的LayoutParams有关。
在这里插入图片描述

View的工作流程

measure

View的measure

View没有子View了,直接测量自身即可。会首先调用measure方法(该方法是final类型,不可重写),紧接着调用onMeasure方法,在onMeasure方法调用setMeasureDimension该方法会设置View的宽高。在setMeasureDimension中使用getDefaultSize方法,该方法会根据传入的measureSpec返回size值,如果measureSpec是AT_MOST或Excatly都会直接返回size。
在这里插入图片描述
而这里出现了一个重要问题,就是属性wrap_content不生效。可以根据之前的表格知道,子View为wrap_content时,无论父View的MeasureSpec是什么模式,都会将子View的MeasureSpec模式变成EXACTLY,同时MeasureSpec的size是父View的size那么此时getDefalutSize就会返回父View剩余的width和height,这样就和子View为match_parent相同了。

因此我们要重写onMeasure方法,并为其设置一个宽高用于(LayoutParams)宽高为wrap_content时使用。下面为典型代码。

@Override
    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(mWidth,mHeight);
        }else if(widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth,heightSpecSize);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize,mHeight);
        }
    }
ViewGroup的measure

ViewGroup除了完成自身的测量,会遍历的去调用所有子元素的的measure方法,各个子元素在递归的去做这件事。(如果子元素也是ViewGroup就会递归的继续判断,如果是View就会直接测量)。

ViewGroup是个抽象类,需要具体的子类(比如LinearLayout)来实现onMeasure

整体流程如下,ViewGroup先调用measureChildren方法,在该方法中会遍历所有的子View,并调用measurechild方法,同时传入child,以及父MeasureSpec。measurechild方法中会根据父MeasureSpec,以及自身的Layoutparams计算自身的MeasureSpec,最后会调用子View的measure方法,并传入两个自己的MeasureSpec。

measureChildren(int widthMeasureSpec,int heightMeasureSpec){
	for(child in children)
		measurechild(child,widthMeasureSpec,heightMeausreSpec);
}
measurechild(View child,int parentwidthMeasureSpec,int parentheightMeausreSpec){
	LayoutParams Lp = child.getLayoutParams();
	int childWidthMeasurSpec= getMeasureSpec(parentwidthMeasureSpec,...);
	int childHeightMeasureSpec = getMeasureSpec(parentheightMeausreSpec,..)
	child.measure(childWidthMeasurSpec,childHeightMeasureSpec );
}

下图是一个ViewGroup到一个子View的测量过程。
在这里插入图片描述

layout方法

layout方法是计算自身的位置(确定四个顶点的位置以及View的宽高)。onLayout方法的作用是ViewGroup确定子元素的位置

对于ViewGroup来说,会调用layout确定自身的位置,之后调用onlayout方法去遍历这些子元素并调用它们的layout方法,这些子元素会继续持续这个过程。但是ViewGroup中的onlayout方法是没有实现的,因为不同的ViewGroup需要不同的实现。

对于View来说,会调用layout方法,其中setframe方法会设置四个顶点的坐标(mLeft、mRight、mTop、mBottom),对于View来说它的onlayout是空实现。
在这里插入图片描述

draw方法

draw过程的入口是draw()函数,之后会进行如下几个操作。

  1. 绘制背景:drawbackground(canvas): 绘制本身View的背景。
  2. 绘制自己:onDraw(canvas) :由于不同的View会不同,因此自定义View需要重写该方法
  3. 绘制children:dispatchDraw(canvas) :遍历子View,最终调用子View的draw函数重复绘制过程,在View中这里是空实现。
  4. 绘制装饰:onDrawScrollBars(canvas)(如 滚动指示器、滚动条、和前景等)

可以补充一点的是只有ViewGroup才有dispatchDraw方法,在View中是一个空实现,同理,只有在View中才有onDrawScrollBars方法。

其次补充一点,View有一个boolean变量WILL_NOT_DRAW用于标识不用在屏幕上绘制它。在ViewGroup中它是默认为false,在View中是true。
在这里插入图片描述

整体总结(复习必看)

1.DecorView是Window的顶级View,无论是事件分发还是对View进行工作流程,都是由DecorView在分给底下的View。DecorView其中是一个LinearLayout,上面是title,下面是content,布局文件就是放在这个content里的。

2.ViewRoot是连接DecorView与WindowManger的枢纽,View的measure、layout、draw都是由ViewRootImpl对象完成的。ViewRootImpl对象在Acitivity创建后,DecorView放入Window后会创建。

3.View的绘制工作是 measure->layout->draw。

measure:测量View的宽高;
layout:计算View应该放置在父View的位置
draw:将View绘制在屏幕上。

View绘制工作是以调用ViewRootImpl对象的peformTravelsals方法开始的。首先调用ViewRootImpl的performmeasure方法,进行View树的测量。进入顶级View的measure方法,之后进入onMeasure方法,顶级View会在该方法中遍历所有的子元素,并调用其Measure方法。持续上述过程直到所有的View都完成测量。之后会调用ViewRootImpl的performlayout、performdraw。
在这里插入图片描述
4.MeasureSpec:是一个测量标准,它是一个32位的int型变量。他的前两位是模式标记位,后30位是size。他的模式包括三类:AT_MOST、EXCATLY、UN_specfic。

其中AT_MOST是最大化模式,对应LayoutParams的wrap_content,因为这里不知道size该取什么,只知道不能大于父View所能容纳的最大值(考虑了padding在内)。

EXCATLY是确定模式对应LayoutParams的match_parent和确定数值。确定size该取什么,取父View所能容纳的最大值,或确定的数值。

UN_SPECFIC模式:父View不影响它,size想取多少都可以,一般用于系统内部。

子View的MeasureSpec与其自身的LayoutParams有关也与父View的MeasureSpec有关。其关系如下表。
在这里插入图片描述
5. Measure过程:
View的measure过程如下图,首先以measure方法开始,之后调用onMeasure方法,在onMeasure方法中会调用setMeasureDimension去设置宽高;setMeasureDimension中会调用getDefaultSize方法获取宽高,当MeasureSpec为EXCATLY以及AT_MOST时都会返回size。至此View的measure结束。
在这里插入图片描述
ViewGroup的measure过程,其没有OnMeasure方法,会调用MeasureChildren对其子元素进行测量,具体是通过调用measurechild方法,在measurechild方法获得当前子元素的WidthMeasureSpec与HeightMeasureSpec,并传入子元素的mesure方法。子元素会重复上述操作,直至结束。
在这里插入图片描述
6.为什么自定义View的wrap_content不起作用?

因为当子View的LayoutParams为wrap_content时,不管父View是什么其MeasureSpec的模式都只会是AT_MOST,size为父View的size。这样在onMeasure测量时,getDefault方法就会返回这个size,这样就和match_parent相同了。

解决方法就是要重新onMeasure方法,并设置一个自定义的宽高,当LayoutParams为wrap_content时(MeasureSpec为AT_MOST),就使用setMeasureDimension传入我们自定义的宽高。

@Override
    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(mWidth,mHeight);
        }else if(widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth,heightSpecSize);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize,mHeight);
        }
    }

7.layout过程

ViewGroup的layout首先layout方法用于确定自身的位置,之后使用onLayout确定子元素的位置,在onLayout中会遍历所有子元素,并调用子元素的layout方法。

View的layout通过layout方法确定自身的位置,使用setFrame可以设置四个顶点的位置(top,bottom,left,right)。View的onlayout方法是一个空实现。

8.draw过程

draw过程一共包括以下几个方法:(1)drawBackGround:用于绘制背景(2)onDraw:用于绘制自身内容(3)dispatchDraw:用于ViewRoot去绘制子元素(4)onDrawScrollBars:用于绘制装饰
注:第四个总记不住好好记忆一下

ViewGroup的draw过程:draw()->drawBackGround()->onDraw()->dispatchDraw();其中dispatchDraw会遍历所有子元素,并调用子元素的draw方法。

View 的draw过程:draw()->drawBackGround()->onDraw()->;View的dispatchDraw是个空实现。

其次补充一点,View可以通过设置WILL_NOT_DRAW这个boolean变量来让View不绘制,在ViewGroip中该变量默认为false,View中默认为true。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值