流式布局初探

hackernew
发布于 2021-2-25 09:56
浏览
0收藏

 

背景


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

 

我想说,会写流式布局了,表示你对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>

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.

 

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;
    }
}

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.

 

如果想要MFlowLayout可以设置padding,子View可以设置margin,就需要使用measureChildWithMargins(child, widthMeasureSpec, widthUsed,

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

分类
已于2021-2-25 09:56:12修改
收藏
回复
举报
回复
    相关推荐