Android如何自定义视图之测量和布局原理(一)

  在android开发过程中,自定义视图的使用基本上可以说是非常频繁,如何开发一个高效的自定义视图,显得非常重要。由于android本身就是mvc的架构,假如视图的逻辑耦合到controller里,就会显得非主流,并且对View的控制显得不是得心应手,各种各样的适配问题也会接踵而来。这个时候View的作用就显现出来,View的逻辑还是要放到V这一层去控制。

那么如何实现高效有用的自定义View?

1、首先剥离View的功能,View只实现视图显示的功能,不能把业务逻辑掺杂进来,否则这个View又是四不像

2、View重写onMeasure,ViewGroup重写onMeasure、onLayout方法,实现视图的大小和位置的重新定义

3、对外提供相应的方法,实现对View的动态控制

第一点,抽离View的业务逻辑:就是分析需求,寻找View的功能,划清边界,不能“越权”执行代码,把View自己的功能内聚到自己的类里。

第二点,对于视图有需求变化宽高的情况,在onMeasure里实现视图宽高的测量,以及子视图的测量。在onLayout里变换视图的布局位置等。

第三点,对外暴漏通俗易懂的方法,让使用者使用方便,实现的过程中,尽量保证高的拓展性,对新需求能保证很快的拓展进来。

一起来看下onMeasure的具体原理:

先来看下onMeasure(int widthMeasureSpec, int heightMeasureSpec),参数的含义代表什么呢?我们来看下View的默认实现。

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
setMeasuredDimension(int measureWidth,int measureHeight)是必须在onMeasure()测量完毕之后调用的,那么我们来看下getDefaultSize(int size, int measureSpec)的实现。

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }
能看到switch..case条件判断的三种模式,通过查询api说明MeasureSpec是这样的:
一个MeasureSpec封装了从父容器传递给子容器的布局需求,每一个MeasureSpec代表了一个宽度,或者高度的说明。
一个MeasureSpec是一个大小跟模式的组合值。一共有三种模式:
1、 UPSPECIFIED:父容器对于子容器没有任何限制,子容器想要多大就多大.
2、 EXACTLY:父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间.
3、 AT_MOST:子容器可以是声明大小内的任意大小.

那么父类传过来的size是怎么组合到MeasureSpec中呢?一起看下MeasureSpec这个类:注释已经去掉,

    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
MODE_MASK这个变量是将16进制3向左移位30,那么

MODE_MASK结果是这样的11000000000000000000000000000000;

~MODE_MASK是MODE_MASK的取反00111111111111111111111111111111;

UNSPECIFIED就是32位的00000000000000000000000000000000;

EXACTLY移位结果就是01000000000000000000000000000000;

AT_MOST移位结果就是10000000000000000000000000000000。

因为只有3中结果,2位就可以搞定,所以用3来作为mask。

getMode就获取了形参的最高两位,getSize就获取了形参低30位,也就是测量的实际dimension。


然后来看下onLayout:

onLayout是ViewGroup的抽象方法,方法的声明是这样的

    protected void onLayout(boolean changed, int l, int t, int r, int b);
参数:

changed代表View相对于父布局位置或者大小是否发生变化

l代表View相对于父布局左边的间距

t代表View相对于父布局顶部的间距

r代表View相对于父布局右侧的间距

b代表View相对于父布局底部的间距

当onLayout被系统调用时,需要去给每个子view分配一个大小和size。来看个onLayout的demo:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = this.getChildCount();
        for (int i = 0; i < count; i++) {
            View child = this.getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            child.layout(lp.x + lp.leftMargin, lp.y + lp.topMargin,
                    lp.x + lp.leftMargin + child.getMeasuredWidth(), lp.y + lp.topMargin + child.getMeasuredHeight());
        }
    }
这个重载的方法是绝对按照子view的leftMargin和topMargin去约束子View的位置。


好了,简单的一起看了下onMeasure和onLayout的实现,以后一起跟大家对View进行更深入的解读,一起学习,如有问题欢迎指正,谢谢大家。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值