1.自定义View的基本方法
- onMeasure():测量视图大小
- onLayout():确定View位置,进行页面布局
- onDraw():绘制视图
2.View类的构造函数
View类是Android中各组件的基类,有4个构造函数
class CustomView : View {
//用于代码构造View()
constructor(context: Context?) : super(context)
//用于xml中构造View,使用默认style(AttributeSet对应自定义属性值集合)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
//用于xml中构造View,使用特定style
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
//用于xml中构造View,使用特定style,特定资源 (不常用)
constructor(
context: Context?,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
}
3.View视图结构
View视图结构.png- PhoneWindow:负责管理界面显示和事件响应,是Activity与View系统交互的接口
- DecorView:Android视图树的根节点,继承FrameLayout,作为整个视图容器来使用
- measure, layout和draw过程,都是从View树的根节点开始,自上而下遍历(即树形递归),最终确定整个View树的相关属性
4.Android坐标系
Android坐标系.pngView的位置是相对于父控件而言的
- getTop() 获取子View左上角到父View顶部的距离
- getLeft() 获取子View左上角到父View左侧的距离
- getBottom() 获取子View右下角到父View顶部的距离
- getRight() 获取子View右下角到父View左侧的距离
MotionEvent中 :
event.getX()/event.getY():触摸点相对于其所在View坐标系的坐标
event.getRawX()/event.getRawY():触摸点相对于屏幕坐标系的坐标
5.MeasureSpect(重点)
整个测量过程是从根节点开始的遍历过程,在这个过程中父View需要告诉子View具体的模式和宽高值(对子View是一种约束,子View需要在允许的范围内绘制),最终android用一个int类型来表示模式和值。int占4个字节,32位,高2位存测量模式(mode),低30位存测量大小(size)
测量模式 | 说明 |
---|---|
EXACTLY | 确切的size |
AT_MOST | 父View为子View指定一个最大size,子View不能大于该size |
UNSPECIFIED | 父View不约束子View(即子View可以取任意size),系统内部使用(如RecyclerView、ScrollView) |
MeasureSpec类就是⽤来处理这些事情的,子View的MeasureSpect值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpect值计算得到的:
/**
* 将父控件的测量规格和子View的布局参数计算得到一个最符合子View的测量规格
*
* @param spec 父控件的测量规格
* @param padding 父控件里已经占用的大小
* @param childDimension 子View布局LayoutParams里的尺寸
* @return 子View的测量规格
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父控件的测量模式
int specMode = MeasureSpec.getMode(spec);
//父控件的测量大小
int specSize = MeasureSpec.getSize(spec);
//获取父控件的size与padding的差值(也就是父控件剩余大小),若小于0则返回0
int size = Math.max(0, specSize - padding);
//定义返回值存储变量size
int resultSize = 0;
//定义返回值存储变量mode
int resultMode = 0;
//依据父控件的mode进行分支逻辑处理
switch (specMode) {
//父控件测量有精确尺寸
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//①如果child的布局参数有固定值(如android:layout_width="100dp"),那么child的size也可以确定下来(100dp),mode为EXACTLY,size为自己的大小
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//②如果child的布局参数是math_parent(也就是child想要占满父控件),而此时父控件是精确模式,有精确尺寸,都给你,child也确定下来了,mode为EXACTLY,size为父控件size
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//③如果child的布局参数是wrap_content(也就是child想要根据自己的逻辑决定自己的大小),那就自己决定,不过大小不能大于父控件,所以child的model为AT_MOST,size为父控件size
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父控件测量模式是AT_MOST,也就是父控件和还不知道自己的大小,但是不能超过size
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//④child能确定自己大小,自己确定
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//⑤child想要和父控件一样大,但是父控件自己不确定自己的大小,所以child的size的上限是父控件size
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//⑥child想要根据自己逻辑确定大小,size上限为父控件的size
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//UNSPECIFIED一般用于系统内部多次measure的情况下,如RecyclerView,ScrollView测量子View给的就是UNSPECIFIED,我们暂时不关注
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
小结:
MeasureSpect测量模式.png
网友评论