学习资源来源: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启动时,此方法被调用。
本文详细探讨了自定义View的三大流程:测量、布局和绘制。在测量阶段,讲解了MeasureSpec的生成规则和测量过程,强调单一View与ViewGroup的差异。布局部分阐述了如何确定View及其子元素的位置。最后,讨论了绘制过程,并提到了RecycleView的ItemDecoration。文章还提及自定义View时对wrap_content和padding的支持,以及在特定情况下可能需要的多次测量。
2586

被折叠的 条评论
为什么被折叠?



