流式布局初探

背景

最近项目中用到了流式布局,最初就决定自己写一个,发现一时竟然没有思路。虽然自定义控件的博客看了不少,也写过简单的自定义控件,但是真正自己独立写出一个流式布局,还是有些考验的。查找了几篇博客,思路大同小异,理清思路,自己开干写了一下。中间改了几个问题,觉得可以正常使用后,这才有了这篇博客。

我想说,会写流式布局了,表示你对ViewGroup的测量(onMeasure)和布局(onLayout)有了一个较为深入的理解。流式布局主要涉及ViewGroup对子View的测量和摆放(布局)。

效果图流失布局效果图

参考布局

参考布局,尺寸和颜色根据自己需求修改:


<com.istarshine.views.MFlowLayout
    android:id="@+id/flow_search_history"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="@dimen/common_margin"
    android:layout_marginTop="2dp"
    android:layout_marginRight="18dp">


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="6dp"
        android:layout_marginTop="6dp"
        android:background="@drawable/shape_solid_ff"
        android:paddingLeft="@dimen/common_margin"
        android:paddingTop="4dp"
        android:paddingRight="@dimen/common_margin"
        android:paddingBottom="4dp"
        android:text="鞋子"
        android:textColor="@color/common_text_22"
        android:textSize="@dimen/text_size_14" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="6dp"
        android:layout_marginTop="6dp"
        android:background="@drawable/shape_solid_ff"
        android:paddingLeft="@dimen/common_margin"
        android:paddingTop="4dp"
        android:paddingRight="@dimen/common_margin"
        android:paddingBottom="4dp"
        android:text="吹风机"
        android:textColor="@color/common_text_22"
        android:textSize="@dimen/text_size_14" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="6dp"
        android:layout_marginTop="6dp"
        android:background="@drawable/shape_solid_ff"
        android:paddingLeft="@dimen/common_margin"
        android:paddingTop="4dp"
        android:paddingRight="@dimen/common_margin"
        android:paddingBottom="4dp"
        android:text="袜子 男 纯棉"
        android:textColor="@color/common_text_22"
        android:textSize="@dimen/text_size_14" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="6dp"
        android:layout_marginTop="6dp"
        android:background="@drawable/shape_solid_ff"
        android:paddingLeft="@dimen/common_margin"
        android:paddingTop="4dp"
        android:paddingRight="@dimen/common_margin"
        android:paddingBottom="4dp"
        android:text="豆浆机 九阳"
        android:textColor="@color/common_text_22"
        android:textSize="@dimen/text_size_14" />


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="6dp"
        android:layout_marginTop="6dp"
        android:background="@drawable/shape_solid_ff"
        android:paddingLeft="@dimen/common_margin"
        android:paddingTop="4dp"
        android:paddingRight="@dimen/common_margin"
        android:paddingBottom="4dp"
        android:text="三只松鼠"
        android:textColor="@color/common_text_22"
        android:textSize="@dimen/text_size_14" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="6dp"
        android:layout_marginTop="6dp"
        android:background="@drawable/shape_solid_ff"
        android:paddingLeft="@dimen/common_margin"
        android:paddingTop="4dp"
        android:paddingRight="@dimen/common_margin"
        android:paddingBottom="4dp"
        android:text="三只松鼠 三只松鼠  三只松鼠  三只松鼠  三只松鼠  三只松鼠  三只松鼠"
        android:textColor="@color/common_text_22"
        android:textSize="@dimen/text_size_14" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="6dp"
        android:layout_marginTop="6dp"
        android:background="@drawable/shape_solid_ff"
        android:paddingLeft="@dimen/common_margin"
        android:paddingTop="4dp"
        android:paddingRight="@dimen/common_margin"
        android:paddingBottom="4dp"
        android:text="刮皮刀 水果刀"
        android:textColor="@color/common_text_22"
        android:textSize="@dimen/text_size_14" />


</com.istarshine.views.MFlowLayout>

MFlowLayout 代码

onMeasure() 中测量子View,按流式布局算出MFlowLayout自己(ViewGroup)的宽高;在 onLayout()中按流式布局准确摆放子View。见如下代码:

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 * 流式布局
 */
public class MFlowLayout extends ViewGroup {

    public MFlowLayout(Context context) {
        super(context);
    }

    public MFlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //测量所有的子元素(调用子元素的measure()),
        // 只有测量过的元素调用child.getMeasuredHeight/Width()才能获取到值,否则为0
//        measureChildren(widthMeasureSpec, heightMeasureSpec);

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

        int spaceWidth = widthSize - getPaddingLeft() - getPaddingRight();

        int resultWidth = 0;
        int resultHeight = 0;
        int lineWidth = 0;
        int lineHeight = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE) {
                continue;
            }
            //测量每个子元素的宽高
            int widthUsed = getPaddingLeft() + getPaddingRight();
            int heightUsed = getPaddingTop() + getPaddingBottom();
            measureChildWithMargins(child, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);
//            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            //测量后的宽高
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            //因为子View可能设置margin,这里要加上margin的距离
            MarginLayoutParams childMlp = (MarginLayoutParams) child.getLayoutParams();
            int childWidthWithMargin = childWidth + childMlp.leftMargin + childMlp.rightMargin;
            int childHeightWithMargin = childHeight + childMlp.topMargin + childMlp.bottomMargin;

            //一行放不下了,就换行
            if (lineWidth + childWidthWithMargin > spaceWidth) {
                //换行,计算宽高
                resultWidth = Math.max(resultWidth, lineWidth);
                resultHeight += lineHeight;
                //换行结束,重新给lineWidth和lineHeight赋值
                lineWidth = childWidthWithMargin;
                lineHeight = childHeightWithMargin;
            } else {
                //不换行,宽度直接相加
                lineWidth += childWidthWithMargin;
                //高度取二者最大值
                lineHeight = Math.max(lineHeight, childHeightWithMargin);

            }

            //最后一个肯定是最后一行
            if (i == getChildCount() - 1) {
                resultWidth = Math.max(resultWidth, lineWidth);
                resultHeight += lineHeight;
            }

        }

        //因为上面resultWidth参与了宽度比较,所以计算padding必须放在这里
        resultWidth += getPaddingLeft() + getPaddingRight();
        resultHeight += getPaddingTop() + getPaddingBottom();

        //设置FlowLayout的宽高
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : resultWidth,
                heightMode == MeasureSpec.EXACTLY ? heightSize : resultHeight);


    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int spaceWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingLeft = getPaddingLeft();
        int childLeft = 0;
        int childTop = 0;

        int lineHeight = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);

            if (child.getVisibility() == View.GONE) {
                continue;
            }

            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            MarginLayoutParams childMlp = (MarginLayoutParams) child.getLayoutParams();
            int childWidthWithMargin = childWidth + childMlp.leftMargin + childMlp.rightMargin;
            int childHeightWithMargin = childHeight + childMlp.topMargin + childMlp.bottomMargin;

            if (childLeft + childWidthWithMargin > spaceWidth) {
                childTop += lineHeight;

                //换行处理
                childLeft = 0;
                lineHeight = childHeightWithMargin;
            } else {
                lineHeight = Math.max(lineHeight, childHeightWithMargin);

            }
            int left = childLeft + paddingLeft + childMlp.leftMargin;
            int top = childTop + paddingTop + childMlp.topMargin;
            int right = left + childWidth;
            int bottom = top + childHeight;
            child.layout(left, top, right, bottom);

            childLeft += childWidthWithMargin;

        }

    }


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

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    public void setLayoutParams(LayoutParams params) {
        super.setLayoutParams(params);
    }

    @Override
    protected boolean checkLayoutParams(LayoutParams p) {
        return p instanceof MarginLayoutParams;
    }
}

代码并不难,但需要一些计算逻辑,和一些注意点。
解释一下为什么这么测量子View,而不是直接使用注释部分(measureChild(child, widthMeasureSpec, heightMeasureSpec);):

            //测量每个子元素的宽高
            int widthUsed = getPaddingLeft() + getPaddingRight();
            int heightUsed = getPaddingTop() + getPaddingBottom();
            measureChildWithMargins(child, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);
//            measureChild(child, widthMeasureSpec, heightMeasureSpec);

如果想要MFlowLayout可以设置padding,子View可以设置margin,就需要使用measureChildWithMargins(child, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed),这样就会在测量的时候把MFlowLayout设置的padding(wideUsed,heightUsed)和子View设置的margin计算在内。而子View可以设置margin,则需要MarginLayoutParams,具体见上面代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值