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

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

作者:cmad 时间:2016-05-13 分类:Android 评论:0条 浏览:506

ViewRoot和DecorView

  ViewRoot对应于ViewRootImpl实现类,它是连接WindowManager和DecorView的纽带,View的三大流程(measure、layout、draw)均是通过ViewRoot来完成的。
  View的绘制流程是从ViewRoot的performTraversals方法开始的,performTraversals会一次调用performMeasureperformLayoutperformDraw三个方法,这三个方法分别调用顶级View的measure、layout、draw方法完成View及其子View的绘制过程。
  DecorView为顶级View,一般情况下都会包含一个竖直的LinearLayout,在这个LinearLayout里有上下两部分(具体情况和Android版本及主题有关),上面是标题,下面是内容栏。在Activity中我们通过setContentView所设置的布局其实就是添加到内容栏之中的,而内容栏的id是content,我们可以通过`findViewById(android.R.id.content)获得内容栏的ViewGroup。

MeasureSpec

  MeasureSpec代表的是一个32位的int值,高2为代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。
  MeasureSpec类把SpecMode和SpecSize打包成一个int值并提供了解包的方法getMode/getSize
  SpecMode有三类:

  • UNSPECIFIED
    父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内容,表示一种测量状态。
  • EXACTLY
    父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式。
  • AT_MOST
    父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。

View的工作流程

  View的工作流程主要是指measure、layout、draw即测量、布局、绘制。其中measure确定View的测量高宽,layout确定View的最终宽高和四个顶点的位置即view的位置,draw则是将view绘制到屏幕上。

measure

  measure过程分为两种,一种是View的measure,一种是ViewGroup的measure。如果只是一个原始的View,那么measure方法就完成了测量过程,如果是ViewGroup,除了完成自己的测量外还会遍历所有子View的measure,各个子元素再递归去执行这个流程。

1.View的measure过程

  View的measure过程由其measure方法来完成,measure是一个final类型方法,不能对它进行重写,但是在measure中调用了onMeasure方法,我们可以重写onMeasure方法来完成我们的自定义测量。来看一下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;
}

  这个方法逻辑很简单,就是根据SpecMode返回测量大小,如果不看UNSPECIFIED的话,其实就是返回SpecSize的值。
  再来看看getSuggestedMinimumHeight方法:

protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

  逻辑也很简单,判断是否有背景,如果没有背景就返回View的mMinWidth/mMinHeight 即xml里对应的android:minWidth/minHeight的值,如果有背景的话,返回背景宽高与mMinWidth/mMinHeight大的那个。
  所以View的默认测量结果其实就是父容器传入的可用的宽高的大小,那岂不是xml里设置的wrap_content岂不是没用?是的如果继承View不重写onMeasure方法的话xml设置wrap_content确实没用跟match_parent效果一样,我们常用的TextView等基础控件都是重写了onMeasure进行了处理的。

2.ViewGroup的measure过程

  对于ViewGroup来说,除了完成本身的measure过程还会去遍历所有子元素的measure方法。和View不同的是ViewGroup是一个抽象类,它没有重写onMeasure方法,需要它的子类去实现。但是他提供了一个measureChildren的方法来对子元素进行测量。来看看这个方法的源码:

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,看下代码:

protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec)
{
final LayoutParams lp = child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

  measureChild思路也很简单,取出了childview的LayoutParams,再通过getChildMeasureSpec来创建子元素的MeasureSpec,然后再调用子元素的measure进行测量。

layout

  layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,他在onLayout中会遍历所有的子元素并调用其layout方法。
  在View的onLayout方法是一个空方法,没有做任何的实现,因为View没有下一级不需要去确定下级元素的位置。而ViewGroup的onLayout是一个抽象方法,需要它的子类去做具体实现。
  layout方法有四个参数left、top、right、bottom即View的上下左右的位置,也就确定了View的位置和宽高。

draw

  draw的作用是将view绘制到屏幕上,大致如下过程:

  • (1) 绘制背景background.draw(canvas)。
  • (2) 绘制自己(onDraw 继承View自定义view需要重写改方法)。
  • (3) 绘制children(dispatchDraw)
  • (4) 绘制装饰(onDrawScrollBars)

自定义View

  自定义view一般分为4类:

  • 1.继承VIew重写onDraw方法
    主要实现一些不规则界面效果
  • 2.继承ViewGroup派生特殊的onLayout
    主要用于实现自定义布局
  • 3.继承特定的VIew(如Textview)
    一般用于扩展已有基础控件。
  • 4.继承特定的ViewGroup(如LinearLayout)
    主要用于控件组合的情况。

相关推荐
更多

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>