效果圖
思維導(dǎo)圖
一炸枣、流式布局的實現(xiàn)
實現(xiàn)原理:采用面向?qū)ο笏枷雽⒄麄€布局分為很多行的對象,每個行對象管理自己行內(nèi)的孩子弄唧,這里通過集合來管理适肠。
1. 內(nèi)部類Line的實現(xiàn)
1.1 定義行的基本屬性
List<View>:管理行中的孩子
maxWidth:行的最大寬度
usedWidth:使用的寬度
height:行的高度
space:孩子之間的間距
-
構(gòu)造初始化maxWidth和space
public Line(int maxWidth, int horizontalSpace) { this.maxWidth = maxWidth; this.space = horizontalSpace; }
1.2 addView(View view)方法實現(xiàn)
-
往行的集合里添加View,更新行的使用寬度和高度
/** * 往集合里添加孩子 */ public void addView(View view) { int childWidth = view.getMeasuredWidth(); int childHeight = view.getMeasuredHeight(); // 更新行的使用寬度和高度 if (views.size() == 0) { // 集合里沒有孩子的時候 if (childWidth > maxWidth) { usedWidth = maxWidth; height = childHeight; } else { usedWidth = childWidth; height = childHeight; } } else { usedWidth += childWidth + space; height = childHeight > height ? childHeight : height; } // 添加孩子到集合 views.add(view); }
1.3 canAddView(View view)方法實現(xiàn)
-
判斷是否能往行里添加孩子候引,如果孩子的寬度大于剩余寬度就不能
/** * 判斷當(dāng)前的行是否能添加孩子 * * @return */ public boolean canAddView(View view) { // 集合里沒有數(shù)據(jù)可以添加 if (views.size() == 0) { return true; } // 最后一個孩子的寬度大于剩余寬度就不添加 if (view.getMeasuredWidth() > (maxWidth - usedWidth - space)) { return false; } // 默認可以添加 return true; }
2. 對容器進行測量(onMeasure方法的實現(xiàn))
2.1 獲取寬度侯养,計算maxWidth,構(gòu)造傳入Line
-
總寬度減去左右邊距就是行的最大寬度
// 獲取總寬度 int width = MeasureSpec.getSize(widthMeasureSpec); // 計算最大的寬度 mMaxWidth = width - getPaddingLeft() - getPaddingRight();
2.2 循環(huán)獲取孩子進行測量
-
獲取孩子總數(shù)澄干,遍歷獲取每一個孩子沸毁,然后進行測量,測量完之后還需要將孩子添加到行集合里傻寂,然后將行添加到管理行的集合里
// ******************** 測量孩子 ******************** // 遍歷獲取孩子 int childCount = this.getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 測量孩子 measureChild(childView, widthMeasureSpec, heightMeasureSpec); // 測量完需要將孩子添加到管理行的孩子的集合中息尺,將行添加到管理行的集合中 if (mCurrentLine == null) { // 初次添加第一個孩子的時候 mCurrentLine = new Line(mMaxWidth, HORIZONTAL_SPACE); // 添加孩子 mCurrentLine.addView(childView); // 添加行 mLines.add(mCurrentLine); } else { // 行中有孩子的時候,判斷時候能添加 if (mCurrentLine.canAddView(childView)) { // 繼續(xù)往該行里添加 mCurrentLine.addView(childView); } else { // 添加到下一行 mCurrentLine = new Line(mMaxWidth, HORIZONTAL_SPACE); mCurrentLine.addView(childView); mLines.add(mCurrentLine); } } }
2.3 測量自己
-
由于寬度肯定是填充整個屏幕疾掰,這里只需要處理行的高度搂誉,累加所有的行高和豎直邊距算出高度
// ******************** 測量自己 ********************* // 測量自己只需要計算高度,寬度肯定會被填充滿的 int height = getPaddingTop() + getPaddingBottom(); for (int i = 0; i < mLines.size(); i++) { // 所有行的高度 height += mLines.get(i).height; } // 所有豎直的間距 height += (mLines.size() - 1) * VERTICAL_SPACE; // 測量 setMeasuredDimension(width, height);
3. 指定孩子的顯示位置(onLayout方法的實現(xiàn))
實現(xiàn)思路:指定孩子的位置静檬,孩子給了行管理炭懊,所以這里具體孩子的位置應(yīng)該交給行去指定。容器只需要指定行的位置就可以拂檩。
-
遍歷獲取所有的行侮腹,讓行去指定孩子的位置,指定行的高度
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // 這里只負責(zé)高度的位置稻励,具體的寬度和子孩子的位置讓具體的行去管理 l = getPaddingLeft(); t = getPaddingTop(); for (int i = 0; i < mLines.size(); i++) { // 獲取行 Line line = mLines.get(i); // 管理 line.layout(t, l); // 更新高度 t += line.height; if (i != mLines.size() - 1) { // 不是最后一條就添加間距 t += VERTICAL_SPACE; } } }
4. Line中l(wèi)ayout方法的實現(xiàn)(指定孩子的位置)
-
遍歷獲取每一個孩子父阻,獲取孩子的寬度和高度愈涩,計算上下左右的大小,指定孩子的位置加矛,之后還需要更新孩子左邊的大小
// 循環(huán)指定孩子位置 for (View view : views) { // 獲取寬高 int measuredWidth = view.getMeasuredWidth(); int measuredHeight = view.getMeasuredHeight(); // 重新測量 view.measure(MeasureSpec.makeMeasureSpec(measuredWidth + avg, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)); // 重新獲取寬度值 measuredWidth = view.getMeasuredWidth(); int top = t; int left = l; int right = measuredWidth + left; int bottom = measuredHeight + top; // 指定位置 view.layout(left, top, right, bottom); // 更新數(shù)據(jù) l += measuredWidth + space; }
5. 細節(jié)處理
-
第一次測量之后履婉,行管理器中就有了行的對象,之后每次測量都會去創(chuàng)建下一行斟览,這樣就會出現(xiàn)很多空行出來毁腿,所以需要在測量之前將集合清空。
mLines.clear(); mCurrentLine = null;
-
每一行的最后一個孩子放不下就放到下一行苛茂,這樣每一行就都會有空格已烤,這里將這些空格平分給行里的每一個孩子,重新指定其寬度妓羊。
// 平分剩下的空間 int avg = (maxWidth - usedWidth) / views.size(); // 重新測量 view.measure(MeasureSpec.makeMeasureSpec(measuredWidth + avg, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)); // 重新獲取寬度值 measuredWidth = view.getMeasuredWidth();
6. 使用自定義屬性草戈,將水平間距和豎直間距做成屬性,在布局中指定侍瑟,增強擴展性
-
attrs文件指定屬性名
<declare-styleable name="FlowLayout"> <attr name="width_space" format="dimension"/> <attr name="height_space" format="dimension"/> </declare-styleable>
-
構(gòu)造中獲取屬性
// 獲取自定義屬性 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout); horizontal_space = array.getDimension(R.styleable.FlowLayout_width_space,0); vertical_space = array.getDimension(R.styleable.FlowLayout_height_space,0); array.recycle();
-
布局中使用屬性
app:width_space="10dp" app:height_space="10dp"
經(jīng)過以上步驟之后,F(xiàn)lowLayout基本就已經(jīng)實現(xiàn)了丙猬,接下來就是使用了涨颜。
二、流式布局的使用
-
布局中申明
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:fillViewport="true" tools:context="com.pinger.sample.MainActivity"> <com.pinger.library.FlowLayout app:width_space="10dp" app:height_space="10dp" android:id="@+id/flow_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="5dp"/> </ScrollView>
代碼中使用
其實就是循環(huán)遍歷數(shù)據(jù)的長度茧球,不斷的創(chuàng)建TextView庭瑰,然后設(shè)置TextView的屬性和背景,包括五彩背景等抢埋,最后將TextView添加到FlowLayout中就可以弹灭。