最近有這麼一個需求,如下圖
動態添加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上