Bimmer7 Android教程 进阶系列之:深入了解View的绘制过程

我们在写Android App的时候,经常要获取或者改变各种layout或者view的宽度和高度,或者想实现一些customized的UI控件,这个时候经常会遇到各种各样没有任何想法的问题,这个时候就需要对Android中view绘制流程的内部工作原理有一个了解,我们这里不会讲太深,仅仅深入到能帮我们解决日常遇到的问题就够了。我总结了下面几点重要的知识点。

(一)Android中View的添加是树状结构。
Android添加View是按照一个树的结构添加的,这棵树的Root是一个叫DectorView的class,并且它是一个FrameLayout的子类。这也就是为什么如果你的最外面的layout如果是framelayout的话,可以在layout的xml文件中省略framelayout,用<merge> tag来代替。我们自己写layout一般都用xml来写,而且我们写的xml的结构也是树的结构,所以我们一般的layout就是下面这样的结构。

常见的setContentView()就是生成用户控制的根部的那个View,然后下面的都是通过LayoutInflater.inflate()来加载布局,生成View,并通过addView()一层一层的从上到下添加到树上。各个view之间的消息传递也是按照这个树形结构来传递的,或者向上,或者向下。

(二)View、ViewGroup和Layout的关系
ViewGroup继承View,Layout继承ViewGroup。基本的UI类都是继承View类的,一个View在屏幕上可以占据一个矩形区域。ViewGroup是View的容器,负责对其中的多个View的管理,虽然ViewGroup继承View,但ViewGroup和View之间是组合的设计模式。Widget是一个包,包含了android内置的UI类,比如ListView, ImageView等,里面的类都继承View。下面这张图把这些关系说的很清楚。

(三)View的绘制流程
每一个View的绘制过程都必须先后经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw()

1. onMeasure()
每一个View都会在OnMeasure()这个方法里设置View的height和width。我们先看View里的源代码:

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

其中setMeasuredDimension()是决定这个View大小的代码,不管你之前如何设置这个view的height和width,如果你此处手动给了值,那么这个view最终的大小就以这个地方给的值为准。我们来看一个简单的例子:

public class MyView extends View {
        ......
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(200, 200);
	}

}
这样的话就把View默认的测量流程覆盖掉了,不管在布局文件中定义MyView这个视图的大小是多少,最终在界面上显示的大小都将会是200*200。

需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。但getWidth()或者getHeight()需要在onLayout()之后才能得到,我们下面马上讲到。

但如果想利用getWidth()或者getHeight()得到View的大小的时候,可以在当前View override onMeasure(), 并且在onMeasure()中调用完super.onMeasure()之后调用getWidth, 也可以在onLayout()或者onDraw()里调用都可以。

讲到这里我们可以说一下getWidth()和getMeasuredWidth()的区别。以上面的代码为例,如果在MyView里没有覆盖调用onMeasure()并人为setMeasuredDimension(),那么这两个方法返回的结果应该是一样的。但如果像我们上面给的例子那样,getWidth返回的结果将是200,即setMeasuredDimension()中设置的值,而getMeasuredWidth()返回的将是我们人为改变前的值,即没有覆盖调用onMeasure()时应该得到的值。

2. onLayout()
measure过程结束后,视图的大小就已经测量好了,接下来就是layout的过程了。正如其名字所描述的一样,这个方法是用于给视图进行布局的,也就是确定视图的位置。

public void layout(int left, int top, int right, int bottom)

这个方法接收四个参数,分别代表着左、上、右、下的坐标,当然这个坐标是相对于当前视图的父视图而言的,如果某个view覆盖重写这个方法,就可以人为固定其位置。但一般某个view在屏幕上的具体位置都是其parent决定的,所以一般情况是,在父类的onLayout()里,调用子类的layout()函数,来固定子类的位置。

在onLayout()过程结束后,我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了。那么getWidth()方法和getMeasureWidth()方法到底有什么区别呢?首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。比如下面的代码:

@Override
protected void onLayout(boolean changed, int l, , int r, int b) {
	if (getChildCount() > 0) {
		View childView = getChildAt(0);
		childView.layout(0, 0, 200, 200);
	}
}

这样getWidth()方法得到的值就是200 - 0 = 200,不会再和getMeasuredWidth()的值相同了。当然这种做法充分不尊重measure()过程计算出的结果,通常情况下是不推荐这么写的。通常情况下,这个方法都是写成下面这种,让两者保持一致:

@Override
protected void onLayout(boolean changed, int l, int t,
        int r, int b) {

    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        child.layout(childLeft, childTop,
                childLeft + child.getMeasuredWidth(),
                childTop + child.getMeasuredHeight());
        }
}

3. onDraw()
measure和layout的过程都结束后,接下来就进入到draw的过程了.在这里才真正地开始对视图进行绘制。每个视图根据想要展示的内容来自行绘制,比如TextView、ImageView等类的源码,你会发现它们都有重写onDraw()这个方法,并且在里面执行了相当不少的绘制逻辑。绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。Canvas这个类的用法非常丰富,基本可以把它当成一块画布,在上面绘制任意的东西,具体我们这里就不讲了。


Reference: http://blog.csdn.net/guolin_blog/article/details/16330267

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值