FlowLayout 自定义流式布局

上效果图

image.png
package com.zt.flowlayout.weget;

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

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

/**
 * 自定义 流式布局
 */
public class FlowLayout extends ViewGroup {
    //横向分割 宽度
    private int mHorizontalSpacing = dp2px(16);
    //纵向分割 宽度
    private int mVerticalSpacing = dp2px(8);

    private List> allLineViews = new ArrayList<>();
    private List lineHeights = new ArrayList<>();


    /**
     * 通过new 创建对象
     *
     * @param context
     */
    public FlowLayout(Context context) {
        super(context);
    }

    /**
     * 通过反射
     *
     * @param context
     * @param attrs
     */
    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    /**
     * 防止内存抖动 , 每次清空而不是 重新创建
     */
    private void clearMeasureParams() {
        allLineViews.clear();
        lineHeights.clear();
    }

    /**
     * 框架的宽高
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        clearMeasureParams();

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        //获取父控件的宽度高度
        int selfWidth = MeasureSpec.getSize(widthMeasureSpec);  //ViewGroup解析的父亲给我的宽度
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的父亲给我的高度

        //保存当前行所有view
        List lineViews = new ArrayList<>();
        //当前行高度
        int lineHeight = 0;
        //当前行宽度
        int lineWidthUsed = 0;


        int parentNeededWidth = 0;  // measure过程中,子View要求的父ViewGroup的宽
        int parentNeededHeight = 0; // measure过程中,子View要求的父ViewGroup的高


        //获取所有孩子计算 递归计算孩子所需宽高
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            LayoutParams lp = childView.getLayoutParams();
            int childMeasureSpecWidth = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, lp.width);
            int childMeasureSpecHeight = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, lp.height);
            childView.measure(childMeasureSpecWidth, childMeasureSpecHeight);

            //当前view 的具体宽 高
            int childMeasuredWidth = childView.getMeasuredWidth();
            int childMeasuredHeight = childView.getMeasuredHeight();


            //判断是否需要换行
            if (lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth) {
                //保存当前行 所有控件
                allLineViews.add(lineViews);
                //保存当前行高
                lineHeights.add(lineHeight);
                //记录 当前父控件所需宽高
                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed);
                parentNeededHeight += lineHeight + mVerticalSpacing;


                //当前行 相关保存数据 重置
                lineViews = new ArrayList<>();
                lineHeight = 0;
                lineWidthUsed = 0;
            }

            //保存当前行所有控件
            lineViews.add(childView);
            //计算出最大高度//如每个控件高度不一样
            lineHeight = Math.max(lineHeight, childMeasuredHeight);
            //当前行宽度
            lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;

            if (i == childCount - 1) {//防止最后一个控件 是需要换行
                //保存当前行 所有控件
                allLineViews.add(lineViews);
                //保存当前行高
                lineHeights.add(lineHeight);
                //记录 当前父控件所需宽高
                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed);
                parentNeededHeight += lineHeight;

            }
        }
        //再度量自己,保存
        //根据子View的度量结果,来重新度量自己ViewGroup
        // 作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;
        setMeasuredDimension(realWidth, realHeight);
    }

    /**
     * 确定 每个子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) {
        //确定第一个控件的xy位置 就是 paddingTop 和 paddingLeft w
        int curL = getPaddingLeft();
        int curT = getPaddingTop();

        int lineCount = allLineViews.size();
        for (int i = 0; i < lineCount; i++) {
            //获取所有行数 的控件
            List views = allLineViews.get(i);
            //获取当前行高
            int height = lineHeights.get(i);
            //当前行数有几个控件
            int nowLienView = views.size();
            for (int j = 0; j < nowLienView; j++) {
                //确定第一个控件位置
                View view = views.get(j);
                //调用过 onMeasure 这个就有值,
                //右边位置 需要当前宽度 加上左边位置
                int curR = view.getMeasuredWidth() + curL;
                //下边位置 需要当前高度 加上上边位置
                int curB = view.getMeasuredHeight() + curT;
                view.layout(curL, curT, curR, curB);
                //第二个或者之后的 x 轴需要改变也就是 curL 需要加上当前控件的宽 和 横向分割宽度
                curL = curR + mHorizontalSpacing;
            }
            //第二行 left 需要重置,top 需要加上 上一行的高度 和 纵向分割宽度
            curL = getPaddingLeft();
            curT = curT + height + mVerticalSpacing;
        }

    }


    public int dp2px(float dpValue) {
        float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}


你可能感兴趣的:(FlowLayout 自定义流式布局)