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

来源:互联网 发布:哈尔滨程序员工资 编辑:程序博客网 时间:2024/05/21 01:47

  在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