首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Android 自定義 ViewGroup 實現流式佈局:動態添加按鈕、自動換行、平均分配寬度

     編輯:程式設計疑難解答

需求

  • 每行顯示 3 個 Button。
  • 按鈕寬高固定,超出行寬自動換行。
  • 支援運行時動態 addView() 新增按鈕。

核心思路:繼承 ViewGroup 覆寫 onMeasure 與 onLayout

完整實作範例

public class FlowLayout extends ViewGroup {
    private final int horizontalSpacing = 16; // dp 轉 px
    private final int verticalSpacing = 16;
    private int maxPerRow = 3;

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

    private void init() {
        horizontalSpacing = dp2px(horizontalSpacing);
        verticalSpacing = dp2px(verticalSpacing);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int usedWidth = 0;
        int rowHeight = 0;
        int totalHeight = getPaddingTop();

        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) continue;
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            int childW = child.getMeasuredWidth();
            int childH = child.getMeasuredHeight();

            if (usedWidth + childW > widthSize - getPaddingRight() && usedWidth > 0) {
                // 換行
                totalHeight += rowHeight + verticalSpacing;
                usedWidth = 0;
                rowHeight = 0;
            }

            usedWidth += childW + (usedWidth == 0 ? 0 : horizontalSpacing);
            rowHeight = Math.max(rowHeight, childH);
        }
        totalHeight += rowHeight + getPaddingBottom();

        setMeasuredDimension(
            widthMode == MeasureSpec.EXACTLY ? widthSize : usedWidth + getPaddingLeft() + getPaddingRight(),
            heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight
        );
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int parentWidth = r - l;
        int curX = getPaddingLeft();
        int curY = getPaddingTop();
        int rowHeight = 0;
        int childCountInRow = 0;

        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) continue;

            int childW = child.getMeasuredWidth();
            int childH = child.getMeasuredHeight();

            if (curX + childW > parentWidth - getPaddingRight() && childCountInRow > 0) {
                // 換行
                curX = getPaddingLeft();
                curY += rowHeight + verticalSpacing;
                rowHeight = 0;
                childCountInRow = 0;
            }

            child.layout(curX, curY, curX + childW, curY + childH);
            curX += childW + horizontalSpacing;
            rowHeight = Math.max(rowHeight, childH);
            childCountInRow++;
        }
    }

    private int dp2px(int dp) {
        return (int) (dp * getResources().getDisplayMetrics().density + 0.5f);
    }

    public void setMaxPerRow(int max) { this.maxPerRow = max; }
}

動態添加按鈕

FlowLayout flowLayout = findViewById(R.id.flow_layout);
for (int i = 0; i < 10; i++) {
    Button btn = new Button(this);
    btn.setText("Btn " + (i+1));
    btn.setLayoutParams(new ViewGroup.LayoutParams(
        ViewGroup.LayoutParams.WRAP_CONTENT,
        ViewGroup.LayoutParams.WRAP_CONTENT
    ));
    flowLayout.addView(btn);
}
// 添加完成後觸發重新佈局
flowLayout.requestLayout();

進階優化建議

  • 使用 FlexboxLayout:Google 官方 com.google.android.flexbox:flexbox-layout 已完美支援流式、換行、權重分配,無需自寫 ViewGroup。
  • RecyclerView + FlexboxLayoutManager:大量資料時避免一次性 addView 導致記憶體/效能問題。
  • 間距屬性化:透過自定義屬性 app:horizontalSpacing 在 XML 設定。

總結

自定義 FlowLayout 覆寫 onMeasure/onLayout 可實現按鈕流式排列與自動換行,動態添加後呼叫 requestLayout() 即可刷新。生產環境建議直接使用 FlexboxLayout 獲得更完整、穩定的支援。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved