自定义控件之流式布局

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010339039/article/details/50452912

这段时间偷懒了,全去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;
 }

好了最后贴出源码地址:

源码下载

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

这里写图片描述

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

猜你喜欢

转载自blog.csdn.net/u010339039/article/details/50452912