View(视图)

View的简介

  在Android系统中,视图是最重要的显示控件之一,也是其他显示控件的基类。在视图的子类中,一类是视图控件组ViewGroup,用于组合和排列其他控件,如LinearLayout;另一类是单个视图控件,负责显示特定的视觉样式,如TextView。视图是应用与用户之间交流的窗口。View的结构图如下所示:

  视图的核心是在页面中展示特定的图像和响应用户的事件。
  视图展示的系统架构,由视图根节点(ViewRoot)开始,视图根节点负责连接窗口管理器(WindowManager)与装饰视图(DecorView),窗口管理器用于响应用户事件,而装饰视图用于展示特定图像。
  视图展示的程序入口,由performTaversals()方法开始,在方法中依次调用performMeasure()、performLayout()、performDraw(),即执行测量流程、执行布局流程、执行绘制流程,完成页面的整个绘制流程,将装饰视图中的内容呈现于手机屏幕之上。
  在视图由创建到显示的过程中,第一步,测试视图的高度与宽度;第二步,布局视图在父容器中的位置;第三步,绘制视图在屏幕上的显示。

  自定义View是有几种固定类型的,有的直接继承自View,有的直接继承自ViewGroup,有的则继承自现有的控件。

View的位置参数

  View的位置主要是由它的四个顶点来决定的,分别对应于View的四个属性:top、left、right、bottom。其中top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标。需要注意的是,这些坐标都是相对于View的父容器来说的,因此它是一种相对坐标。
  所以,View的宽高和坐标的关系如下:

width = right - left;
height = bottom - top;

  View的四个参数,对应的是源码中的mLeft、mRight、mTop、mBottom这四个成员变量。获取和设置这四个参数的方式如下:

protected int mLeft;
protected int mRight;
protected int mTop;
protected int mBottom;

public final int getLeft() {
    return mLeft;
}

public final void setLeft(int left) {
    //---;
}

public final int getRight() {
    return mRight;
}

public final void setRight(int right) {
    //---;
}

public final int getTop() {
    return mTop;
}

public final void setTop(int top) {
    //---;
}

public final int getBottom() {
    return mBottom;
}

public final void setBottom(int bottom) {
    //---;
}

  从Android 3.0 开始,View增加了额外的几个参数:x、y、translationX、translationY,其中x和y是View左上角的坐标,而translationX和translationY是View左上角相对于父容器的偏移量。这几个参数也是相对于父容器的坐标,并且translationX和translationY的默认值是0,和View的四个基本位置参数一样,View也为其提供了get/set()方法,这几个参数的换算关系如下:

x = left + translationX;
y = top + translationY;

  需要注意的是,View在平移过程中,top和left表示的是原始左上角的位置信息,其值在移动过程中并不会发生变化,移动中变化的值是x、y、translationX和translationY这四个参数。

ViewRoot和DecorView

  ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程measure、layout、draw均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。
  View的绘制流程是从ViewRoot的performTraversals()方法开始的,它经过measure、layout和draw三个过程最终将一个View绘制出来。其中,measure用来测量View的宽和高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。
  performTravsals()会依次调用performMeasure、performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三个流程,其中在performMeasure中会调用measure方法,在measure方法中又会调用onMeasure方法,在onMeasure方法中会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程。接着,子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。同理,performLayout和performDraw的传递流程和performMeasure是类似的,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的。
  measure过程决定了View的宽/高,Measure完成以后,可以通过getMeasureWidth和getMeasuredHeight方法来获取到View测量后的宽/高。Layout过程决定了View的四个顶点的坐标和实际的View的宽/高,完成以后,可以通过getTop、getBottom、getLeft、getRight来拿到View的四个顶点的位置,并可以通过getWidth和getHeight方法来拿到View的最终宽/高。Draw过程则决定了View的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。
  DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下两个部分,上面是标题栏,下面是内容蓝。在Activity中我们通过setContentView所设置的布局文件其实就是被加到内容栏之中的,而内容栏的id是content,可以理解为Activity指定布局的方法不叫setview而叫setContentView,因为我们的布局的确加到了id为content的FrameLayout中。
  可以通过以下代码获取content:

ViewGroup content = findViewById(R.android.id.content);

  可以通过以下代码获取到我们设置的View:

content.getChildAt(0);

  同时,通过源码我们可以知道,DecorView其实是一个FrameLayout,View层的事件都先经过DecorView,然后才传递给我们的View。

理解MeasureSpec

  MeasureSpec参与了View的measure过程。确切地说,MeasureSpec在很大程度上决定了一个View的尺寸规格,之所以说是很大程度上是因为这个过程中还受父容器的影响 ,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽/高。这里测量的宽/高,不一定等于View的最终宽/高。

MeasureSpec

  MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。
  MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供了打包和解包方法。SpecMode和SpecSize都是一个int值,一组SpecMode和SpecSize可以打包为一个MeasureSpec,而一个MeasureSpec可以通过解包的形式来得出其原始的SpecMode和SpecSize。这里提到的MeasureSpec是指MeasureSpec所代表的int值,而并非MeasureSpec本身。
  SpecMode有三类,每一类都表示特殊的含义:

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

MeasureSpec和LayoutParams的对应关系

  系统内部是通过MeasureSpec来进行View的测量,但是正常情况下我们使用View指定MeasureSpec。在View测量的时候,系统会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽/高。需要注意的是,MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。另外,对于顶级View(即DecorView)和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。
  对于DecorView来说,在ViewRootImpl中的measureHierarchy方法中有如下代码,它展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desiredWindowHeight是屏幕的尺寸。

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

View的常用属性

  • id:为此视图提供标识符名称,用于findViewById()方法;
  • width:设置视图的宽度;
  • height:设置视图的高度;
  • padding:设置所有四条边缘的填充;
  • paddingLeft:设置左边缘的填充;
  • paddingStart:设置起始边缘的填充;
  • paddingRight:设置右边缘的填充;
  • paddingEnd:设置结束边缘的填充;
  • paddingTop:设置顶边缘的填充;
  • paddingBottom:设置底边缘的填充;
  • paddingVertical:相当于同时定义了paddingTop和paddingBottom(在android8.0可以使用,在8.0以下的机型中不兼容,还是要用分开的那两个属性);
  • paddingHorizontal:相当于同时定义了paddingLeft和paddingRight(在android8.0可以使用,在8.0以下的机型中不兼容,还是要用分开的那两个属性);
  • margin:设置视图的四边的额外空间;
  • marginLeft:设置视图左边的额外空间;
  • marginRight:设置视图右边的额外空间;
  • marginTop:设置视图顶边的额外空间;
  • marginBottom:设置视图底边的额外空间;
  • layout_marginVertical:相当于同时定义了layout_marginTop和layout_marginBottom(在android8.0可以使用,在8.0以下的机型中不兼容,还是要用分开的那两个属性);
  • layout_marginHorizontal:相当于同时定义了layout_marginLeft和layout_marginRight(在android8.0可以使用,在8.0以下的机型中不兼容,还是要用分开的那两个属性);
  • background:设置视图的背景色;
  • foreground:设置视图的前景色;
  • visibility:设置视图的可见性;
  • focusable:设置视图的焦点;
  • alpha:(范围在0-1之间的float类型值)文本内容,在java代码中设置为setAlpha()(范围在0-1之间的float值);
  • clickable:是否对点击事件做出反应;
  • longClickable:是否对长按事件做出反应;
  • minHeight:定义视图的最小高度;
  • minWidth:定义视图的最小宽度;
  • onClick:单击视图时要调用的上下文方法的名称;
  • rotation:视图的旋转;
  • rotationX:围绕x轴旋转视图,以度为单位;
  • rotationY:围绕y轴旋转视图,以度为单位;
  • scaleX:x方向上视图的比例;
  • scaleY:y方向上视图的比例;
  • translationX:初始水平滚动偏移;
  • translationY:初始垂直滚动偏移;
  • translationZ:在视图的z轴变化;
  • transformPivotX:x枢轴点的位置,视图将围绕该位置旋转和缩放;
  • transformPivotY:y枢轴点的位置,视图将围绕该位置旋转和缩放;
  • tag:为视图提供包含String的标记,以便稍后检索或搜索,使用View.getTag()或者View.findViewWithTag();

具体属性讲解

  • visibility:可见性;该属性有三个值,如下:
属性值 代码 解释
visible android:visibility="visible"
mView.setVisibility(View.VISIBLE)
可见
invisible android:visibility="invisible"
mView.setVisibility(View.VISIBLE)
不可见,但是占据屏幕中位置
gone android:visibility="gone"
mView.setVisibility(View.VISIBLE)
不可见,并且不占据屏幕中位置