学习资源来源:https://www.jianshu.com/nb/9976005
measure、draw均为调度方法。具体的施工由onMeasure、onDraw实现。
- layout方法确定View本身的位置,而onLayout方法会确定所有子元素的位置;因此,对于单一View的layout过程:由于单一View是没有子View的,故onLayout()是一个空实现,具体布局实现在layout中。
- 对于ViewGroup的layout过程:由于确定位置与具体布局有关,所以onLayout()在ViewGroup为1个抽象方法,需重写实现。
- 自定义View需要支持padding和wrapcontent;而margin是由父容器控制的,默认已被支持。即外边距轮不到view来算,Andorid将其封装在LayoutParams内交由父容器统一处理。
测量
MeasureSpec的生成规则
子View的LayoutParams + 父View给出的测量规格(MeasureSpec) = 子View的测量规格。
即:子View的大小由父View的MeasureSpec和子View的LayoutParams 共同决定。
通过MeasureSpec的静态方法即可获取对应的Mode、Size以及生成新的MeasureSpec。
测量流程:
单一View的measure过程
View的onMeasure实现
getSuggestedMinimumWidth:设置了背景图,则为背景图的大小,否则为android:minWidth的属性值。
setMeasuredDimension保存测量后的尺寸。ViewGroup可以通过child.getMeasuredWidth获取子View的测量值;
由以上代码可以看出,View本身是不支持wrap_content属性的,因此如果自定义View,需要完成对wrap_content的支持。
支持wrap_content及padding示例 - MeasureSpec使用示例:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 设置宽度
*/
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
{
Log.e("xxx", "EXACTLY");
mWidth = specSize;
} else
{
// 由图片决定的宽
int desireByImg = getPaddingLeft() + getPaddingRight() + mImage.getWidth();
// 由字体决定的宽
int desireByTitle = getPaddingLeft() + getPaddingRight() + mTextBound.width();
if (specMode == MeasureSpec.AT_MOST)// wrap_content
{
int desire = Math.max(desireByImg, desireByTitle);
mWidth = Math.min(desire, specSize);
Log.e("xxx", "AT_MOST");
}
}
/***
* 设置高度
*/
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
if (specMode == MeasureSpec.EXACTLY)// match_parent , accurate
{
mHeight = specSize;
} else
{
int desire = getPaddingTop() + getPaddingBottom() + mImage.getHeight() + mTextBound.height();
if (specMode == MeasureSpec.AT_MOST)// wrap_content
{
mHeight = Math.min(desire, specSize);
}
}
setMeasuredDimension(mWidth, mHeight);
}
在单一View measure
过程中,getDefaultSize()
只是简单的测量了宽高值,在实际使用时有时需更精细的测量。所以有时候也需重写onMeasure()。
关于测量宽高:
- 在某些情况下,需要多次测量
(measure)
才能确定View
最终的宽/高; - 该情况下,
measure
过程后得到的宽 / 高可能不准确; - 此处建议:在
layout
过程中onLayout()
去获取最终的宽 / 高
ViewGroup的测量过程
遍历所有的子View的尺寸;
合并所有子View的尺寸,最终得到ViewGroup父视图的测量尺寸;
注:ViewGroup无法对onMeasure作统一实现,因为不同的ViewGroup子类有不同的测量规则。这也是单一View的measure过程和ViewGroup的measure过程最大的不同。
除了measureChildren,如下方式也是ViewGroup提供的测量相关的方法。
布局
视图的位置即四个顶点位置:Left、Top、Right、Bottom。
单一View的布局过程
通过setFrame设置了View本身的位置。
注:对于单一View的laytou过程
* a. 由于单一View是没有子View的,故onLayout()是一个空实现
* b. 由于在layout()中已经对自身View进行了位置计算,所以单一View的layout过程在layout()后就已完成了
ViewGroup的布局过程
1、计算自身ViewGroup的位置:layout;
2、遍历View & 确定自身子View在ViewGroup的位置(调用子View的layout):onLayout;
绘制
详见”自定义View温习笔记“
强行插播 - 关于RecycleView的ItemDecoration的
getItemOffsets
onDrawOver
一图胜千言,结束。
小知识点:
当包含一个View的Activity退出或者当前的View被remove时,View的onDetachedFromWindow方法会被调用。
对应的方法onAttachedToWindow是当包含此View的Activity启动时,此方法被调用。