继承ViewGroup自己实现一个流式布局(FlowLayout)

2019年第一篇博客,祝愿自己能越来越好 。

 

 

今天给大家带来的是自定义View实现流式布局,如上图的热搜词,他有的可能会很长,有的会很短,而一行容不下了会自动换行。

用现有的布局很难实现这样的效果,所以我们需要自定义一个布局。

 

思路也很简单,先得到屏幕宽度,再得到每个子View(也就是每个长短不一的item)的宽度,子view的宽度加起来大于屏幕宽度,就换到下一行排列。

 

下面贴出完整代码,注释非常齐全。

 

/**
 * 继承ViewGroup实现一个流式布局
 * Created by Fushize on 2018/12/28.
 */

public class FlowLayout extends ViewGroup {
    public FlowLayout(Context context) {
        this(context, null);
    }

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

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

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //如果写死了宽高或者match_parent
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int sizeHeigh = MeasureSpec.getSize(heightMeasureSpec);
        int modeHeigh = MeasureSpec.getMode(heightMeasureSpec);

        //如果高宽为warp_content,存储宽高
        int width = 0;
        int height = 0;

        //记录每一行的宽高
        int lineWidth = 0;
        int lineHeight = 0;

        int cCount = getChildCount();
        for (int i = 0; i < cCount; i++) {

            View childAt = getChildAt(i);

            //测量每一个子View的宽高
            measureChild(childAt, widthMeasureSpec, heightMeasureSpec);

            //得到LayoutParams  这边的MarginLayoutParams已经在generateLayoutParams中定义
            MarginLayoutParams lp = (MarginLayoutParams) childAt.getLayoutParams();

            //得到子View的真实宽度 =  本身宽度 + 左边距 + 右边距
            int childWidth = childAt.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

            //得到子View的真实高度 =  本身宽度 + 上边距 + 下边距
            int childHeight = childAt.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;


            //一行的宽度+子view宽度,大于总容器的宽度  则换行
            if (lineWidth + childWidth > sizeWidth) {
                //换行

                //比较width和lineWidth(行宽)谁更大
                width = Math.max(width, lineWidth);

                //换行后,行宽重置为子View宽度
                lineWidth = childWidth;

                //记录行高
                height += lineHeight;

                //重置行高
                lineHeight = childHeight;

            } else {
                //不换行

                //不换行的情况下,子view的宽度叠加起来,就等于行宽
                lineWidth += childWidth;

                //得到当前行的最大高度
                lineHeight = Math.max(lineHeight, childHeight);
            }

            //如果是最后一个控件
            if (i == cCount - 1) {
                width = Math.max(lineWidth, width);
                height += lineHeight;
            }

        }

        // 设置测量尺寸
        setMeasuredDimension(
                //如果测量模式等于EXACTLY 表明控件为match_parent or 写死宽高,则直接使用测量的宽高即可
                //否则表明控件为warp_content , 则使用我们计算好的宽高
                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width,
                modeHeigh == MeasureSpec.EXACTLY ? sizeHeigh : height);
    }

    private List<List<View>> mAllViews = new ArrayList<>(); // 一整行的View集合
    private List<Integer> mLineHeigh = new ArrayList<>();   //每一行的行高

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        //onLayout会多次调用,所以进来先清空这两个集合
        mAllViews.clear();
        mLineHeigh.clear();

        //当前ViewGroup的宽度 因为已经测量完毕,可以直接取到
        int width = getWidth();

        //当前行的宽度
        int lineWidth = 0;

        //当前行的高度
        int lineHeight = 0;


        List<View> lineViews = new ArrayList<>();

        //子view的个数
        int cCount = getChildCount();

        for (int i = 0; i < cCount; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int childWidth = child.getMeasuredWidth();
            int childHeigh = child.getMeasuredHeight();

            //当前子view宽度叠加的值 大于 ViewGroup的宽度 , 就换行
            if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width) {
                //记录LineHeight
                mLineHeigh.add(lineHeight);
                //记录当前行的Views
                mAllViews.add(lineViews);

                //重置行宽和行高
                lineWidth = 0;
                lineHeight = childHeigh + lp.topMargin + lp.bottomMargin;

                //重置View的集合
                lineViews = new ArrayList<>();
            }

            //不换行的情况
            //子View的宽度 + 左右边距 = lineWidth
            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;

            //对比行高和子view的高,谁高谁就赋值给lineHeight
            lineHeight = Math.max(lineHeight, childHeigh + lp.topMargin + lp.bottomMargin);

            //不换行的情况下,循环添加子view,直到换行后,被清空
            lineViews.add(child);
        }

        //处理最后一行
        mLineHeigh.add(lineHeight);
        mAllViews.add(lineViews);

        //设置子View的位置
        int left = 0;
        int top = 0;

        //一共的行数
        int lineNunm = mAllViews.size();

        for (int i = 0; i < lineNunm; i++) {
            //当前行的所有View
            lineViews = mAllViews.get(i);

            //当前行所有的高度
            lineHeight = mLineHeigh.get(i);

            for (int j = 0; j < lineViews.size(); j++) {
                //得到当前行的所有View
                View child = lineViews.get(j);

                //可能有的View没有显示的情况,如果没有显示则continue,跳过本次循环,重新开始下一趟循环。
                if (child.getVisibility() == 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的值
                left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

            }

            //到达下一行时, left需要清空 top需要累加
            left = 0;
            top += lineHeight;
        }

    }

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

 

你可以把它想象成LinearLayout来使用即可。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值