Android 自动换行自定义ViewGrop

本文介绍了一种自定义的 Android ViewGroup 布局——AutoLineLayout,它能够实现子 View 的横向排列,并在超出宽度时自动换行。此外,还支持设置固定的行高和子 View 在行内的竖直居中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

子View横向排列,超过宽自动换行

public class AutoLineLayout extends ViewGroup {
    private int rowHeight;//行高
    private boolean centerVerticalInRow;//是否在行竖直方向上居中
    private boolean hasRowHeight;
    private SparseIntArray rowHeightList = new SparseIntArray();//没有设置行高的话,通过计算保存每行的行高
    private SparseIntArray rowTopList = new SparseIntArray();//没有设置行高的话,每行顶部的高度

    public AutoLineLayout(Context context) {
        this(context, null);
    }

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

    public AutoLineLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.AutoLineLayout);
        rowHeight = typedArray.getDimensionPixelSize(R.styleable.AutoLineLayout_row_height, 0);
        hasRowHeight = rowHeight > 0;
        centerVerticalInRow = typedArray.getBoolean(R.styleable.AutoLineLayout_center_vertical_in_row, true);
        typedArray.recycle();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();//子View的可填充宽度
        int count = getChildCount();
        int row = 0;
        int widthSpace = width;
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
            int childWidthSpec;
            int childHeightSpec;
            if (lp.width > 0) {//计算子View宽,不应该超过当前可填充宽
                childWidthSpec = MeasureSpec.makeMeasureSpec(Math.min(width, lp.width), MeasureSpec.EXACTLY);
            } else {
                childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
            }
            if (hasRowHeight) {
                if (lp.height > 0) {//计算子View高,不应该超过行高
                    childHeightSpec = MeasureSpec.makeMeasureSpec(Math.min(rowHeight, lp.height), MeasureSpec.EXACTLY);
                } else {
                    childHeightSpec = MeasureSpec.makeMeasureSpec(rowHeight, MeasureSpec.AT_MOST);
                }
            } else {
                if (lp.height > 0) {//计算子View高
                    childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
                } else {
                    childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
                }
            }
            child.measure(childWidthSpec, childHeightSpec);
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            if (childWidth <= widthSpace) {//根据宽度计算当前子View在哪一行
                child.setTag(row);
                widthSpace -= childWidth;
            } else {
                row++;
                child.setTag(row);
                widthSpace = width - childWidth;
                rowTopList.put(row, rowHeightList.get(row - 1) + rowTopList.get(row - 1));
            }
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            if (!hasRowHeight) {//没有设置行高的话,将行高设置为当前行子View最大的高度
                rowHeightList.put(row, Math.max(rowHeightList.get(row), childHeight));
            }
        }
        int heightSpec;
        if (hasRowHeight) {
            heightSpec = MeasureSpec.makeMeasureSpec(rowHeight * (row + 1) + getPaddingTop() + getPaddingBottom(), MeasureSpec.EXACTLY);
        } else {
            int allChildHeight = rowTopList.get(row) + rowHeightList.get(row);
            heightSpec = MeasureSpec.makeMeasureSpec(allChildHeight + getPaddingTop() + getPaddingBottom(), MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            View preChild = getChildAt(i - 1);

            int childRow = (int) child.getTag();
            int currentRowHeight = hasRowHeight ? rowHeight : rowHeightList.get(childRow);

            ViewGroup.MarginLayoutParams childLP = (ViewGroup.MarginLayoutParams) child.getLayoutParams();

            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            int tempTop = centerVerticalInRow ? (currentRowHeight - childHeight) / 2 : childLP.topMargin;

            int left;
            int top;
            int right;
            int bottom;
            if (preChild != null && (int) preChild.getTag() == childRow) {
                ViewGroup.MarginLayoutParams preChildLP = (ViewGroup.MarginLayoutParams) preChild.getLayoutParams();
                left = preChild.getRight() + preChildLP.rightMargin + childLP.leftMargin;
            } else {
                left = getPaddingLeft() + childLP.leftMargin;
            }

            right = left + childWidth;

            int preRowHeight;//前几行的高度,用来计算当前View的TOP
            if (hasRowHeight) {
                preRowHeight = rowHeight * childRow;
            } else {
                preRowHeight = rowTopList.get(childRow);
            }
            top = getPaddingTop() + preRowHeight  + tempTop;
            bottom = top + childHeight;
            child.layout(left, top, right, bottom);
        }
    }

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


紫色区域是设置了每一行固定的高度且所有子View在行中竖直居中的视觉效果

黄色区域是未设置行高未居中的视觉效果

绿色区域是未设置行高,居中显示的视觉效果


代码:https://github.com/daijianjun/AutoLineLayout


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值