流式佈局FlowLayout及行數限制

最近有這麼一個需求,如下圖
這裏寫圖片描述

動態添加childView並實現自動換行操作,這個比較簡單,重寫ViewGroup的onMesure()方法,遍歷動態計算每個View的寬高,寬度累加,當超過ViewGroup寬度,則換行顯示,負責設置子控件的測量模式和大小 根據所有子控件設置自己的寬和高 。然後重寫onLayout()方法,完成對所有childView的位置以及大小的指定。

網上有鴻洋大神寫的工具類,但是不能做到動態的控制行數限制,所以我進行了改動。鴻洋大神此文章的地址:https://blog.csdn.net/lmj623565791/article/details/38352503

如果有和我相同需求的人,請繼續往下看

1.對外暴露幾個參數

首先加了幾個對外暴露的變量,默認顯示的行數,是否有行數限制,這個是根據自身去求動態設置的,另外一個參數isOverFlow 是否溢出,因爲接口返回的數據數量是不確定的,可能不會超過行限制,也可能超過行限制,如果超過,則顯示點擊顯示全部按鈕,所以這個參數是起到這個作用的。

private int limitLineCount = 5; //顯示行數

private boolean isLimitLine; //是否有行限制

private boolean isOverFlow; //是否溢出

public boolean isOverFlow() {
    return isOverFlow;
}

public void setOverFlow(boolean overFlow) {
    isOverFlow = overFlow;
}

public void setIsLimitLine(boolean isLimitLine) {
    this.isLimitLine = isLimitLine;
    this.requestLayout();
    this.invalidate();

}
2.在測量時做行數限制

如果超過,則不繼續測量高度

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 獲得它的父容器爲它設置的測量模式和大小
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

    // 如果是warp_content情況下,記錄寬和高
    int width = 0;
    int height = 0;
    /**
     * 記錄每一行的寬度,width不斷取最大寬度
     */
    int lineWidth = 0;
    /**
     * 每一行的高度,累加至height
     */
    int lineHeight = 0;

    int lineCount = 1;
    isOverFlow = false;

    int cCount = getChildCount();

    // 遍歷每個子元素
    for (int i = 0; i < cCount; i++) {
        View child = getChildAt(i);
        // 測量每一個child的寬和高
        measureChild(child, widthMeasureSpec, heightMeasureSpec);
        // 得到child的lp
        MarginLayoutParams lp = (MarginLayoutParams) child
                .getLayoutParams();
        // 當前子空間實際佔據的寬度
        int childWidth = child.getMeasuredWidth() + lp.leftMargin
                + lp.rightMargin;
        // 當前子空間實際佔據的高度
        int childHeight = child.getMeasuredHeight() + lp.topMargin
                + lp.bottomMargin;
        /**
         * 如果加入當前child,則超出最大寬度,則的到目前最大寬度給width,累加height 然後開啓新行
         */
        if (lineWidth + childWidth > sizeWidth) {
            if(isLimitLine) {
                if(lineCount == this.limitLineCount + 1) {
                    setOverFlow(true);
                    break;
                }
            }

            width = Math.max(lineWidth, childWidth);// 取最大的
            lineWidth = childWidth; // 重新開啓新行,開始記錄
            // 疊加當前高度,
            height += lineHeight;
            // 開啓記錄下一行的高度
            lineHeight = childHeight;
            lineCount ++;
        } else
        // 否則累加值lineWidth,lineHeight取最大高度
        {
            lineWidth += childWidth;
            lineHeight = Math.max(lineHeight, childHeight);
        }
        // 如果是最後一個,則將當前記錄的最大寬度和當前lineWidth做比較
        if (i == cCount - 1) {
            width = Math.max(width, lineWidth);
            height += lineHeight;
        }


    }
    setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth
            : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight
            : height);
    YrLogger.d("CMM", "measure flowLayout  width = "
            + ((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width)
            + ",height = "
            + ((modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height));
}
#可以看到,我自己定義了參數 int lineCount;用來記錄行數,核心代碼
 if (lineWidth + childWidth > sizeWidth) {
        if(isLimitLine) {
            if(lineCount == this.limitLineCount + 1) {//這裏必須加一,如果超過限制則break;
                setOverFlow(true);
                break;
            }
        }
.
.
.
3.放置位置的時候進行限制
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    mAllViews.clear();
    mLineHeight.clear();

    int width = getWidth();

    int lineWidth = 0;
    int lineHeight = 0;
    // 存儲每一行所有的childView
    List<View> lineViews = new ArrayList<View>();
    int cCount = getChildCount();
    int lineCount = 1;
    // 遍歷所有的孩子
    for (int i = 0; i < cCount; i++) {
        View child = getChildAt(i);
        MarginLayoutParams lp = (MarginLayoutParams) child
                .getLayoutParams();
        int childWidth = child.getMeasuredWidth();
        int childHeight = child.getMeasuredHeight();

        // 如果已經需要換行
        if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {
            if(isLimitLine) {
                if(lineCount == this.limitLineCount + 1) {
                    break;
                }
            }
            // 記錄這一行所有的View以及最大高度
            mLineHeight.add(lineHeight);
            // 將當前行的childView保存,然後開啓新的ArrayList保存下一行的childView
            mAllViews.add(lineViews);
            lineWidth = 0;// 重置行寬
            lineViews = new ArrayList<View>();
            lineCount ++;
        }
        /**
         * 如果不需要換行,則累加
         */
        lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
        lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
                + lp.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++) {
        // 每一行的所有的views
        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();

            // 計算childView的left,top,right,bottom
            int lc = left + lp.leftMargin;
            int tc = top + lp.topMargin;
            int rc = lc + child.getMeasuredWidth();
            int bc = tc + child.getMeasuredHeight();

            // 修正rc margin
            if (rc + lp.rightMargin > getWidth()) {
                rc = getWidth() - lp.rightMargin;
                // 單行textView,設置爲FocusInTouch mode,
                // 第一次點擊是獲取focus,第二次點擊執行onClick事件。
                // if (lineViews.size() == 1 && lineViews.get(0) instanceof
                // TextView) {
                // ((TextView)
                // lineViews.get(0)).setFocusableInTouchMode(true);
                // }
            }
            child.layout(lc, tc, rc, bc);

            left += child.getMeasuredWidth() + lp.rightMargin
                    + lp.leftMargin;

        }
        left = 0;
        top += lineHeight;
    }

}

依舊是添加了個變量用於記錄行數,當超過是,不去放置子view

// 如果已經需要換行
        if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {
            if(isLimitLine) {
                if(lineCount == this.limitLineCount + 1) {
                    break;
                }
            }
            // 記錄這一行所有的View以及最大高度
            mLineHeight.add(lineHeight);
            // 將當前行的childView保存,然後開啓新的ArrayList保存下一行的childView
            mAllViews.add(lineViews);
            lineWidth = 0;// 重置行寬
            lineViews = new ArrayList<View>();
            lineCount ++;
        }

到這裏其實已經差不多了,下面說一下怎麼動態添加數據的吧

4.添加數據
this.mFlowLayout.addView(textView,params);// params是textView的佈局樣式,如果不需要則不要這個參數
5.最後一個問題

當添加完數據之後,按理說我們應該知道開頭那個變量判斷是否溢出,從而動態顯示點擊顯示更多樣式,難點來了,怎麼判斷view繪製完成呢?查了一波view流程,發現有個方法dispatchDraw()很適合

FlowLayout中重新dispatchDraw方法

@Override
protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    if(isLimitLine) {
        ...這裏寫一個回調,activity中收到後判斷isOverFlow,如果溢出,則顯示點擊更多樣式,否則不顯示。
    }


}

如果有不懂得地方歡迎私信我,稍後有時間我會寫個demo整理到github上

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章