自定义控件之流式布局

来源:互联网 发布:罗百吉 什么世界 知乎 编辑:程序博客网 时间:2024/04/18 13:34

这段时间偷懒了,全去dota去了。都没有心情敲代码了。写了个流式布局。练习下自定义viewgroup,再准备写个圆形菜单来练习练习。

下面看看效果:

流式布局:
这里写图片描述

一 概述:

流式布局就将其子控件,从左往右进行排列。如果这一行能放下当前的控件(需要考虑margin,和控件的宽度)那么久在当前放下控件,如果放不下控件,就放到第二行去。

viewgroup中我们必须实现onMeasure(),和onLayout()。两个方法,onMeasure()是测量布局的尺寸的。onLayout()方法是控制子控件位置的。然后执行完了之后还会执行onDraw()。如果你还需要 绘画其他东西,就可以自己画。

二 源码解读:

好了,我们可以看看onMeasure()方法。

如果你自定义的是View类型的控件,如果你不重写onMeasure()方法那么wrap_content属性将不会有任何作用和match_parent一样的效果。

现在我们自定义的是viewgroup ,因为viewgroup是个抽象方法,我们必须实现onMeasure(),onLayout()。

onMeasure()方法中有两个参数int widthMeasureSpec, int heightMeasureSpec,这两个参数将对应的宽高的size,和mode都放进这两个参数中了。

获取方式如下:

        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

这里的heightSize , widthSize 都是返回match_parent状态下的尺寸,或者精确的某个值。

widthMode,heightMode 如果是MeasureSpec.EXACTLY那么对应的是match_parent或者精确的值。
如果返回的是MeasureSpec.AT_MOST那么对应的wrap_content就是自适应。这个就需要我们手动进行计算了,并且还需要参考heightSize ,widthSize ,虽然我们是wrap_content,但是这两个值给我们的是match_parent对应的尺寸。所以我们自已适应的时候需要参考这两个值,不能超过他们,因为这是给我们最大的值了。最后还需要考虑到子控件的margin,和自己的padding属性。

贴出我的onMeasure

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        Log.e("xhc","onMeasure");        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {            setMeasuredDimension(widthSize, heightSize);            return;        }        //最后管padding        int childCount = getChildCount();        int measureWidth = 0, measureHeight = 0;        //一行中最高的一个控件        int heightMax = 0, widthMax = 0;        for (int i = 0; i < childCount; ++i) {            View view = getChildAt(i);            MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();            measureChild(view, widthMeasureSpec, heightMeasureSpec);            if ((measureWidth + params.rightMargin + params.leftMargin + view.getMeasuredWidth() + getPaddingRight()) <= widthSize) {                //这一行可以放下这个控件                measureWidth += (params.rightMargin + params.leftMargin + view.getMeasuredWidth());                if (heightMax < (view.getMeasuredHeight() + params.topMargin + params.bottomMargin)) {                    heightMax = (view.getMeasuredHeight() + params.topMargin + params.bottomMargin);                }            } else {                //换行                if ((measureHeight + heightMax) < heightSize) {                    //换行了 高度增加                    measureHeight += heightMax;                    Log.e("xhc","高度增加"+measureHeight);                    heightMax = 0 ;                }                if (widthMax < measureWidth) {                    //换成最宽的宽度;                    widthMax = measureWidth;                }                //行宽重新开始                measureWidth = 0  ;            }            if(i == (childCount - 1)){                    //这个是最后一行并且没到换行的地方需要把这一行的高度加进去                    measureHeight += heightMax;            }        }        if (widthMax != 0) {            measureWidth = widthMax;        }        if (measureHeight == 0) {            measureHeight = heightMax;        }        if ((measureWidth + getPaddingLeft()) < widthSize) {            measureWidth += getPaddingLeft();        }        if ((measureWidth + getPaddingRight()) < widthSize) {            measureWidth += getPaddingRight();        }        if ((measureHeight + getPaddingTop()) < heightSize) {            measureHeight += getPaddingTop();        }        if ((measureHeight + getPaddingBottom()) < heightSize) {            measureHeight += getPaddingBottom();        }        if (widthMode == MeasureSpec.EXACTLY) {            measureWidth = widthSize;        }        if (heightMode == MeasureSpec.EXACTLY) {            measureHeight = heightSize;        }        setMeasuredDimension(measureWidth, measureHeight);    }

在需要子控件的尺寸之前需要测量子控件

measureChild(view, widthMeasureSpec, heightMeasureSpec);

注意:如果要将子控件的LayoutParams 转成 MarginLayoutParams 需要在viewgroup中添加

    @Override    protected LayoutParams generateLayoutParams(LayoutParams p) {        return new MarginLayoutParams(p);    }

先判断这一行能否还能继续放下当前控件,因为最大的宽度就是widthSize,不能比他还大了。如果放不下了就移动到下一行去。并这一行的高度其实就是最高的控件的高度

 if (heightMax < (view.getMeasuredHeight() + params.topMargin + params.bottomMargin)) {     heightMax = (view.getMeasuredHeight() + params.topMargin + params.bottomMargin);}

当移动到下一行就将测量的高度加上上一行最高的高度。

   measureHeight += heightMax;

并且判断是否是最宽的一行。如果是的话那么就把当前布局的宽度设置成这个宽度。

if (widthMax < measureWidth) {        //换成最宽的宽度;        widthMax = measureWidth;}

记住在最后一行的时候需要将自己的行的高度添加上去

  if(i == (childCount - 1)){     //这个是最后一行并且没到换行的地方需要把这一行的高度加进去     measureHeight += heightMax;   }

然后将自己的padding计算上去。

        if ((measureWidth + getPaddingLeft()) < widthSize) {            measureWidth += getPaddingLeft();        }        if ((measureWidth + getPaddingRight()) < widthSize) {            measureWidth += getPaddingRight();        }        if ((measureHeight + getPaddingTop()) < heightSize) {            measureHeight += getPaddingTop();        }        if ((measureHeight + getPaddingBottom()) < heightSize) {            measureHeight += getPaddingBottom();        }

最后在判断自己的layout_width , layout_height 属性对应的是否是MeasureSpec.EXACTLY,如果是的话就直接使用widthSize,heightSize最大的尺寸了。

if (widthMode == MeasureSpec.EXACTLY) {        measureWidth = widthSize;}if (heightMode == MeasureSpec.EXACTLY) {        measureHeight = heightSize;}

最后设置尺寸

 setMeasuredDimension(measureWidth, measureHeight);

然后我们来看看onLayout函数:

 @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        Log.e("xhc","onLayout");        //注意父控件的padding,子空间的margin        int childCount = getChildCount();        int paddingLeft = getPaddingLeft();        int paddintRight = getPaddingRight();        int paddingTop = getPaddingTop();        int paddingBottom = getPaddingBottom();        int width = getMeasuredWidth();        int height = getMeasuredHeight();        int currentX = paddingLeft, currentY = paddingTop;        int heightMax = 0;        for (int i = 0; i < childCount; ++i) {            View child = getChildAt(i);            MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();            int childTotalWidth = params.leftMargin + params.rightMargin + child.getMeasuredWidth();            if ((paddintRight + currentX + childTotalWidth) <= getMeasuredWidth()) {                //这一行可以放下                int left = (currentX + params.leftMargin);                int top = (currentY + params.topMargin);                child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());                currentX += (params.leftMargin + child.getMeasuredWidth() + params.rightMargin);                if (heightMax < (top + child.getMeasuredHeight() + params.bottomMargin)) {                    heightMax = (top + child.getMeasuredHeight() + params.bottomMargin);                }            } else {                currentX = paddingLeft;                currentY += heightMax;            }        }    }

这里的基本逻辑和onMeasure中一致。判断这一行是否能放下,能放下就放下(阿弥陀佛)

if ((paddintRight + currentX + childTotalWidth) <= getMeasuredWidth()) {//这一行可以放下    int left = (currentX + params.leftMargin);    int top = (currentY + params.topMargin);    child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());}

不能放下就到下一行去。

else {      currentX = paddingLeft;      currentY += heightMax; }

好了最后贴出源码地址:

源码下载

加个好友共同学习(不是公众号):

这里写图片描述

因为小弟水平有限,如果有写的有问题,希望指出。

0 0
原创粉丝点击