android view绘制郭林,Android view的绘制流程

Android的UI管理系统层级关系

aa049db3dbcf

无标题.png

PhoneWindow是Adroid系统中最基本的窗口系统,每个Activity会创建一个PhoneWindow是Activity和view的系统交互接口。DecorView本质上是一个framlayout。

View的绘制流程

View的绘制时从ViewRoot的performTraversals方法开始的,经过measure、layout和draw三个过程最终将一个view绘制出来。

performTraversals会一次调用performMeasure、performLayout、performDraw三个方法,分别完成顶级View的measure、layout、draw三大流程。

其中在performMeasure会调用measure方法,measure方法又会调用onMeasure方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中,这样就完成了一次measure过程。

接着子元素会重复父容器的measure过程,如此反复完成整个View树的遍历。

同理,performLayout和performDraw的传递流程与其类似,唯一不同的是performDraw在draw方法中通过dispatchDraw来实现。

注意

measure决定了View的宽/高,measure完成后,可以通过下面方法来获得View测量后的View的款和高

getMeasuredWidth();

getMeasuredHeight();

layout决定了View的四个顶点的坐标和实际的View的宽/高完。

//通过此方法数去View的四个顶点

getTop();

getLeft();

getRight();

getBottom();

//layout后获取最终的宽高

getWidth();

getHeight();

//view的getWidth()方法

public final int getWidth() {

return mRight - mLeft;

}

draw过程决定了View的显示,只有draw方法完成后View的内容才能呈现在屏幕上。

View的工作流程

Measure过程

1.如果只是一个原始的View,那么measure方法就完成其测量过程、

2.如果是一个viewgroup,除了要完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程。

1view的measure过程

View的measure过程由其measure方法来完成,measure方法是一个final类型的方法,子类不能重写。在measure方法中会去调用View的onMeasure方法。

(view的大小最终是在layout阶段确定的)

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

// 获取值的模式

MeasureSpec.getMode(widthMeasureSpec);

// 获取值的大小

MeasureSpec.getSize(widthMeasureSpec);

getDefaultSize()

//setMeasuredDimension方法测量view是多宽多高的

// 测量出最终view的值(传入多少就是多少)

// super.onMeasure(widthMeasureSpec, heightMeasureSpec);

// onMeasure方法调用了setMeasuredDimension方法

setMeasuredDimension(200, 200);

// 通过这个方法来测量view是多宽多高的

// 这个方法中的参数就代表了测量之后的宽和高--对成员方法的赋值--

}

**View的onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

}

setMeasuredDimension设置view的宽高测量值,来看getDefaultSize方法

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;

}

可以看出在EXACTLY和AT_MOST情况下,返回的其实就是measureSpec中的specSize

如果view没有设置背景,那么view的宽度为mMinWindth,而mMinWindth对应于Android:minWidth这个属性的值。如果这个属性不指定,mMinWindth的值为0.

如果View指定了背景则View的宽度为Android:minWidth和背景的最小宽度这两者中的最大值

max(mMinWidth,mBackGroud.getMinimumWidth())

ViewGroup的measure过程

除了完成自己的measure过程外,还会遍历去调用所有子元素的measure方法,各个子元素再去递归执行这个过程。Viewgroup是一个抽象类,没有重写View的onMeasure方法,但是提供了measureChildren的方法

viewgroup的测量过程的onMeasure方法需要各个子类去具体实现

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {

final int size = mChildrenCount;

final View[] children = mChildren;

for (int i = 0; i < size; ++i) {

final View child = children[i];

if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {

measureChild(child, widthMeasureSpec, heightMeasureSpec);

}

}

}

measurechild的思想就是取出子元素的LayoutParams,然后通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的Measure方法来进行测量

linearlayout的measure过程

系统会遍历元素对每个子元素与执行measureChildBeforeLyout,这个方法来调用view子元素的measure过程,这样各个子元素开始因此进入measure过程

每测量一个子元素,mTotalLength就会增加,增加的部分主要包括了子元素的高度以及元素在竖直方向的margin。

当子元素测量完毕后,LinearLayout会测量自己的大小

针对竖直的LinearLayout,它在水平方向的测量过程遵循View的测量过程

竖直方向:若采用的是matchparent或具体数值,测量与View过程。

若采用wrap_content,那么就是子元素的总和,但人不能超过它的父容器的剩余空间(当然还要考虑其padding值)

注意

在某些极端情况下,系统可能需要多次measure才能确定最终的测量宽高,在这种情况下,在onMeasure方法中拿到的测量宽高时不准确的。因此较好的习惯是在onlayout方法中取获取测量宽和高。

在activity的onCreate、onStart、onResume中均无法正确的得到某个View的宽高信息。因为View的measure与activity的生命周期不是同步的。如果View没有测量完则,获取到的View是0.

获取测量宽高的时机。

(1)Activity/View的onWindowFocusChanged

这个方法的含义是View已经初始化完毕,宽高已经准备好了。

onWindowFocusChanged会被调用多次,当activity获得焦点并失去焦点均会被调用,如果onResume和onPause方法被频繁调用,那么onWindowFocusChanged也会被调用。

@Override

public void onWindowFocusChanged(boolean hasFocus) {

super.onWindowFocusChanged(hasFocus);

if (hasFocus){

view.getMeasuredWidth();

view.getMeasuredHeight();

}

}

(2)view.post(runnable)

通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View此时已经初始化好了

@Override

protected void onStart() {

super.onStart();

view.post(new Runnable(){

@Override

public void run() {

view.getMeasuredWidth();

view.getMeasuredHeight();

}

});

}

(3)ViewTreeObserver

使用ViewTreeObserver的众多接口可以完成此功能,如addOnGlobalLayoutListener,当view树的状态发生改变或者View树内部的可见性发生改变时onGlobalLayout会被调用。

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

view.getViewTreeObserver().removeOnGlobalLayoutListener(this);

view.getMeasuredWidth();

view.getMeasuredHeight();

}

});

(4)手动对view进行measure来得到View的宽和高

根据View的layoutparams来分

matchparent

无法得出具体结果,根据View的measure过程,构造此种Measure需要知道parentSize,即父容器的剩余空间,而这个时候无法知道父容器的剩余空间,因此理论上无法测量出。

具体的数值

int width = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);

int heght = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);

view.measure(width,heght);

wrapcontent

view的尺寸最大是1 << 30) - 1==2^30-1

int width = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);

int height = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);

view.measure(width,heght);

理解MeasureSpec

在测量过程中系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后更具这个measureSpec来测量出View的宽和高

widthMeasureSpec :宽测量规格 :父容器对view的宽度的期望

heightMeasureSpec:高的测量规格:父容器对view高度期望

他两的值实际上是由两部分组成(32位):前30位表示大小+后两位来表示mode(模式) **

SpecMode和SpecSize打包成一个MeasureSpec,而SpecMode可以通过解包的形式来得出原始的SpecMode和SpecSize。

SpecMode有三个类

1)UNSPECIFIED** 未指定(爹不管模式), 对子View的最大值没有要求 ,一般用于系统内部。

2)EXACTLY:精确的 父容器已经检测出View所需要的精确大小,这个时候最终大小就是SpecSize所指定的值。对应于LayoutParams中的match_parent 和具体的数值。

3)AT_MOST :至多 父容器指定了一个可用大小即SpecSize,View的值不能大于这个值,具体指要看不同View的具体实现。它对应于LayoutParams中的wrap_content。

在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec来确定View测量后的宽/高

1.对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定。

2.对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定,onMeasure中就可以确定View测量的宽/高。

在对子元素进行measure,在调用子元素的measure方法之前会先通过getChildMeasureSpec()方法来获得子元素的MeasureSpec。

aa049db3dbcf

普通view的MeasureSpec的构建规则.png

Layout过程

Layout的作用是ViewGroup用来确定子元素的位置

**layout方法确定view本身的位置,而onLayout方法确定所有子元素的位置。

当 ViewGroup的位置被确定后它在onLayout中会遍历所有的子元素并调用其Layout方法,在layout方法中onLayout方法又会被调用。

View的layout方法

layout方法的大致流程:

首先通过setFram方法来设定View的四个顶点的位置,即初始化mLeft,mRight,mTop,mBottom四个值,这四个值已确定,那么View在父容器的位置就确定了。接着会调用onLayout方法。

View和ViewGroup中的onlayout方法。

这个方法的用途是确定子元素的位置。

和onMeasure方法一样onlayout实现同具体的布局有关,

view和Viewgroup中均没有真真的实现onLayout方法,

**LinearLayout的layoutVertical

遍历所有子元素并调用setChildFrame方法来为子元素指定对应的位置,childTop的值越拉越大,后面的子元素会越靠下方。

setChildFrame仅仅是调用子元素的layout方法而已。

这样在layout方法中完成自己的定位以后,就通过onLayout方法去调用子元素的layout方法,这样就会一层一层的传递下去。

draw过程

它的作用是将View绘制到屏幕上面。

View的绘制过程遵循如下几步

(1)绘制背景drawBackground.(canvas)

(2)绘制自己(ondraw)

(3)绘制children(dispatchDraw)

(4)绘制装饰(onDrawScrollBars)

view的绘制过程是通过dispatchDraw来实现的,dispatchDraw会遍历所有子元素的draw方法,如此draw事件就传递下去了

参考

《Android高级进阶》

《Android开发艺术探索》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值