Android 自定义View基础 onMeasure & onLayout

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tyzlmjj/article/details/72860045

官方指南:https://developer.android.com/guide/topics/ui/custom-components.html

方法简介

  • onMeasure()
    用于测量视图的宽高

  • onLayout()
    用于控制子视图的位置

onMeasure

在这个方法里面测量视图的宽高然后确定视图的宽高值。这里需要注意的是,测量的是子视图的宽高,并不是自己的。

完整的方法是这样的

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中提供了几个方法方便我们做简单的测量操作:

  • measureChild()
    按给出的父布局宽高测量数据去测量自视图
  • measureChildWithMargins()
    按给出的父布局宽高测量数据和已经占用的宽高值去测量自视图,并考虑子视图的外边距。(使用这个测量方法的话还需要实现MarginLayoutParams)

或者你也可以用子视图的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));
}

onLayout

这个方法在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,可以找一个适合的子类去继承,做一点小的修改,这样会轻松很多!

展开阅读全文

没有更多推荐了,返回首页