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) |
不可见,并且不占据屏幕中位置 |
- :