官方指南:https://developer.android.com/guide/topics/ui/custom-components.html
onMeasure()
用于测量视图的宽高
onLayout()
用于控制子视图的位置
在这个方法里面测量视图的宽高然后确定视图的宽高值。这里需要注意的是,测量的是子视图的宽高,并不是自己的。
完整的方法是这样的
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
传入了两个参数,这两个参数就是父布局对当前这个View的大小的测量结果。里面包含宽高的数值和测量模式,可以用View.MeasureSpec
类去获取
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
size不用说,就是大小,单位是px。测量模式总共有三种,如下表格说明
测量模式 | 描述 |
---|---|
MeasureSpec.EXACTLY | 精确的尺寸,父布局已经给出一个明确的大小,比如设置宽高为match_parent或者50dp,都表示精确的 |
MeasureSpec.AT_MOST | 自适应,父布局会给出一个最大值,子视图自己适应大小,但不能超过最大值,比如设置宽高为wrap_content |
MeasureSpec.UNSPECIFIED | 没有明确的大小,父布局没有给出任何数值,子视图自由发挥!应该是在列表适配器之类的布局中使用 |
好了,知道了这个参数的含义,接下来就可以来测量自视图的宽高。一般的,如果你需要测量子视图,那么你肯定是继承了ViewGroup或者它的子类。
在ViewGroup中提供了几个方法方便我们做简单的测量操作:
或者你也可以用子视图的measure
方法设置测量值。
下面写一个最简单的例子,继承了ViewGroup
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0, count = getChildCount(); i < count; i++){
final View child = getChildAt(i);
if (child.getVisibility() != GONE){
//测量子视图
measureChild(child, widthMeasureSpec,heightMeasureSpec);
}
}
//在onMeasure中这个方法必须被调用,用于设置最终的测量宽高
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
这个方法在onMeasure之后被调用,在这里设置子视图的位置。给所有子视图都调用一遍layout()
方法就算完成任务了。
一个最简单的例子如下,就是将布局从最上角开始堆起来,完全不考虑其它任何因素
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0, count = getChildCount(); i < count; i++){
final View child = getChildAt(i);
if (child.getVisibility() != GONE){
child.layout(0,0,child.getMeasuredWidth(),child.getMeasuredHeight());
}
}
}
把上面两个方法合在一起就诞生了一个效果类似FrameLayout的视图(细节上还有很大差距!)。
public class CustomFrameLayout extends ViewGroup {
public CustomFrameLayout(Context context) {
super(context);
}
public CustomFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0, count = getChildCount(); i < count; i++){
final View child = getChildAt(i);
if (child.getVisibility() != GONE){
measureChild(child, widthMeasureSpec,heightMeasureSpec);
}
}
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0, count = getChildCount(); i < count; i++){
final View child = getChildAt(i);
if (child.getVisibility() != GONE){
child.layout(0,0, child.getMeasuredWidth(),child.getMeasuredHeight());
}
}
}
}
当然,实际上官方的FrameLayout
远比我写的例子复杂,因为还有很多情况需要考虑,比如边距问题、前景的大小、子视图的gravity、国际化时需要考虑的左右互换等等。所以当你还不能完全掌握自定义视图的时候不要轻易的去直接继承ViewGroup
,可以找一个适合的子类去继承,做一点小的修改,这样会轻松很多!