定义

什么是流式布局?就是当一行的末尾不能容纳新的子控件时,就另起一行。适用的场景包括关键字标签,搜索热词等。

实现

1.理解android View的3种测量模式

1)EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;
2)AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
3)UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。

2.继承ViewGroup,重写三个回调函数。

(1)/**
     * 为自定义布局设置一系列布局属性,比如layout_grayvity,layout_weight等
     * 在这里设置的布局属性将反馈在view.getLayoutParams()函数的返回结果中。
     * 在这个自定义的流式布局里只需要用到MarginLayoutParams
     * @param attrs
     * @return
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(),attrs);
    }
(2)/**
     * 计算所有childView的宽度和高度,然后根据ChildView的计算结果,设置自己的宽和高
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       super.onMeasure(widthMeasureSpec,heightMeasureSpec);     
    }
(3) /**
     * 在这个函数中设置子view的位置以及大小
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    }

具体代码

package com.example.chen.flowlayoutexample.widget;

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

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2015/8/14 0014.
 */
public class FlowLayout extends ViewGroup {
    public FlowLayout(Context context) {
        super(context);
    }

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

    /**
     * 为自定义布局设置一系列布局属性,比如layout_grayvity,layout_weight等
     * 在这里设置的布局属性将反馈在view.getLayoutParams()函数的返回结果中。
     * 在这个自定义的流式布局里只需要用到MarginLayoutParams
     * @param attrs
     * @return
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(),attrs);
    }

    /**
     * 计算所有childView的宽度和高度,然后根据ChildView的计算结果,设置自己的宽和高
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获得上级容器为其推荐的宽和高
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        //如果是wrap_content情况下,要重新计算宽,高
        int width = 0;
        int height = 0;

        //设置一个变量获取每一行的宽度,高度
        int lineWidth = 0;
        int lineHeight = 0;
        int count = getChildCount();

        //遍历每一个子元素
        for(int i =0; i < count; i++){
            View child = getChildAt(i);
            //测量每一个子view的宽和高
            measureChild(child,widthMeasureSpec,heightMeasureSpec);
            //得到child的布局参数(由generateLayoutParams生成)
            MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
            //计算当前子view实际占据的宽度和高度
            int childWidth = child.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
            int childHeight = child.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;

            //判断如果加入当前view超过目前给的最大宽度,则进行换行
            if(lineWidth + childWidth >sizeWidth){
                //将之前计算得的宽与当前行宽进行比较,取最大值
                width = Math.max(width,lineWidth);
                //当前行宽重新赋值为下一行的第一个子view的宽度
                lineWidth = childWidth;
                height += lineHeight;
                lineHeight = childHeight;
            }else{
                lineWidth += childWidth;
                lineHeight = Math.max(height,childHeight);
            }

            if(i == count - 1){
                width = Math.max(width,lineWidth);
                height += childHeight;
            }
        }

        //最后判断测量模式
        int measuredWidth = (modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width;
        int measuredHeight = (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height;
        //设置布局宽和高
        setMeasuredDimension(measuredWidth, measuredHeight);
    }

    //记录多有的子view
    private List<List<View>> mAllViews = new ArrayList<>();
    //记录每一行的最大高度
    private List<Integer> mLineHeight = new ArrayList<>();

    /**
     * 在这个函数中设置子view的位置以及大小
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mAllViews.clear();

        //获取布局的实际宽度
        int width = getWidth();

        int lineWdith = 0;
        int lineHeight = 0;

        //存储当前行的views
        List<View> lineViews = new ArrayList<>();

        for(int i =0; i < getChildCount(); i++){
            View child = getChildAt(i);
            MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();

            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            //判断需不需要换行
            if(childWidth + marginLayoutParams.leftMargin + marginLayoutParams.leftMargin + lineWdith > width){
                mLineHeight.add(lineHeight);
                mAllViews.add(lineViews);
                lineWdith = 0;
                lineViews = new ArrayList<>();
            }

            lineWdith += childWidth + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
            lineHeight = Math.max(lineHeight,childHeight + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin);
            lineViews.add(child);
        }

        //记录最后一行
        mLineHeight.add(lineHeight);
        mAllViews.add(lineViews);

        int left = 0;
        int top = 0;

        //得到总的行数
        int lineNums = mAllViews.size();

        for(int i = 0 ; i < lineNums; i++) {
            //取出每一行的view集合以及行高
            lineViews = mAllViews.get(i);
            lineHeight = mLineHeight.get(i);

            //遍历每一行的所有的view
            for (int j = 0; j < lineViews.size(); j++) {
                View child = lineViews.get(j);
                if (child.getVisibility() == View.GONE) {
                    continue;
                }
                MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

                int lc = left + lp.leftMargin;
                int tc = top + lp.topMargin;
                int rc = lc + child.getMeasuredWidth();
                int bc = tc + child.getMeasuredHeight();

                //进行子view的布局
                child.layout(lc, tc, rc, bc);

                left += lp.leftMargin + child.getMeasuredWidth() + lp.rightMargin;
            }
            left = 0;
            top += lineHeight;
        }
    }
}