Android标签布局LaybelLayout

来源:互联网 发布:网上开个淘宝店流程图 编辑:程序博客网 时间:2024/05/20 07:37

项目请参考 LaybelLayout
在开发当中,我们或多或少的可能会用到一种类似于标签一样的布局,如下图所示。
demo.jpg
在SDK自带的控件当中,我们很难实现这样一种效果。如果根据显示内容使用LinearLayout去动态生成的化,又显得很繁琐,而且很难移植到别的项目里去。于是就实现了这么一个布局Layout.

大致思路


  • 一,子控件的测量和摆放
    首先我们是直接继承了ViewGroup.众所周知,要自定义一个布局,必须要重写ViewGrouponLayout()方法,在这个方法里面,对子控件进行布局的摆放。但是在摆放前,咱们得先给这些子控件进行一个测量,知道它们自己需要多大空间。所以在onMeasure()方法里面,对这些子控件进行测量。因为要支持 margin 外边界吧,所以 ViewGroup 最原始的那个 LayoutParams 就不能使用啦,咱们得用 MarginLayoutParams。下面给出测量思路:
    1,先说说宽度问题,测出每个子控件的宽度,然后每个控件占的宽度是 自身宽度 + margin_left + margin_right.
    2,每一行的高度就等于 自身高度 + margin_top + margin_bottom + line_padding × 2,其中 ‘line_padding’ 是我定义的一个属性
    3,测量时候,如果发现有控件占领宽度加上LaybelLayoutpadding 大于了 LaybelLayout的宽度,则让它刚好等于 LaybelLayout宽度 - 左右padding
    4,测量完成之后,遍历子控件,并且将它们的摆放信息存到ChildLayoutMsg这个内部类里面,这样在待会onLayout()里面只需要将信息提取出来,进行摆放就可以了。
    5,记录摆放的信息,也就是记录每个子控件的 left, top, right, bottom.也是根据每一行的剩余宽度来判断,如果控件占的宽度加上父控件的padding大于了剩余宽度,则需要摆放到下一行

  • 二,LaybelLayout本身的宽高配置
    因为要实现wrap_content属性,这就需要得到本控件的最小宽高。需要知道最小宽度,则需要只知道LaybelLayout的父控件给LaybelLayout分配了多大空间。!= = 这就需要用MeasureSpec把本控件的宽高的mode取出,不知道mode干嘛用的请自行google
    如果mode是EXACTLY,也就是LaybelLayout的父控件给定了宽度,这种情况就用默认的super.onMeasure(widthMeasureSpec, heightMeasureSpec)进行设置宽高就行了。现在主要应对不确定宽度的情况,也就是判断宽和高的mode不等于EXACTLY,把最小宽高通过setMeasuredDimension()设置进去。这里还有个问题,当最小宽高超过了LaybelLayout的父控件给的最大限度时,咋办呢,是不是又要去判断下?其实有个api帮我们实现了这个功能,我们只需要将最小宽高放进去,就会返回一个合适的宽高回来,就是使用resolveSize()了。
    下面来看看resolveSize()都干了什么

public static int resolveSize(int size, int measureSpec) {        return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;}

发现它调用了resolveSizeAndState(),于是跟进去看看

public static int resolveSizeAndState(int size, int measureSpec,     int childMeasuredState) {        final int specMode = MeasureSpec.getMode(measureSpec);        final int specSize = MeasureSpec.getSize(measureSpec);        final int result;        switch (specMode) {              case MeasureSpec.AT_MOST:                    if (specSize < size) {                          result = specSize | MEASURED_STATE_TOO_SMALL;                    } else {                          result = size;                    }                    break;              case MeasureSpec.EXACTLY:                    result = specSize;                    break;              case MeasureSpec.UNSPECIFIED:              default:                    result = size;      }        return result | (childMeasuredState & MEASURED_STATE_MASK);}

第三个参数先不用管,这里用宽度来说明,传入了最小宽度和测量宽度的Spec。记住,这个最小宽度是与LaybelLayout的测量宽度有关的,把每一行的宽度进行比较,取最大的那个宽度作为本控件的最小宽度。虽然在本控件里面,最小宽度的计算方式已经处理了这个问题,但是任然用这个函数来包裹一层。可以看到,如果是父控件给定了确定宽度EXACTLY,则完全按照测量的宽度来作为本控件的宽度;如果父控件对本控件没有加以限制UNSPECIFIED,表示我这个控件需要多大,就得多大,大小完全自己决定,于是返回我传入的size(最小宽度);如果父控件给定了个范围AT_MOST,则判断下,测量值和传入的size哪个小,就返回哪个,这样的好处就是,绝对不会超出父控件的范围。

总结

上面讲得确实有点啰嗦,但是为了力求准确,也只好这样了。如有错误,请各位看客指出,在下感激不尽。具体源码和Demo,请前往 github

0 0
原创粉丝点击