思路:得到每一個控件的寬,判斷剩餘的控件是否能滿足控件,不能的話就換一行。
換一行就要對佈局的擺放進行改變。
package com.dn_alan.myapplication;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* 思路,我們知道繪製流程最終會調用到我門的OnMesure 和 onLayout,
* 而不同的佈局,他們自己的實現不一樣,所以纔有了我們使用的這些基本佈局組件
* 那麼我們現在自己來開發一個瀑布式的流式佈局
*/
public class WaterfallFlowLayout extends ViewGroup {
private static final String TAG = "WaterfallFlowLayout";
/**
* 用來保存行高的列表
*/
private List<Integer> lstLineHegiht = new ArrayList<>();
/**
* 用來保存每行views的列表
*/
private List<List<View>> lstLineView = new ArrayList<>();
private boolean isFlag;
public WaterfallFlowLayout(Context context) {
super(context);
}
public WaterfallFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WaterfallFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
/**
* @param widthMeasureSpec
* @param heightMeasureSpec MeasureSpec是父控件提供給子View的一個參數,作爲設定自身大小參考,只是個參考,要多大,還是View自己說了算。
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "onMeasure");
//測量的方法會走兩次,如果不添加這個flag,佈局會向下移動
if(isFlag){
return;
}
isFlag = true;
//1.先完成自己的寬高測量
//需要得到mode進行判斷我的顯示模式是怎樣的
//獲取父容器建議的寬和高
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//獲取父容器模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//當前控件寬高(自己)
int measureWidth = 0;
int measureHeight = 0;
//當前行寬,行高,因爲存在多行,下一行數據要放到下方,行高需要保存
int iCurLineW = 0;
int iCurLineH = 0;
//1.確認自己當前空間的寬高,這裏因爲會有兩次OnMeasure,進行二級測量優化,所以採用IF_ELSE結構
//二級優化原理在源碼具體Draw時,第一次不會直接進行performDraw的調用反而是在下面重新進行了一次scheduleTraversals
//在ViewRootImpl源碼2349-2372之中我門會看到 scheduleTraversals在我們的2363
//對View遍歷之前我們要判斷不同的模式,判斷父容器的模式
//MeasureSpec.EXACTLY是matchParent或者具體的固定值
if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY){
measureWidth = widthSize;
measureHeight = heightSize;
}else{
//當前VIEW寬高
int iChildWidth = 0;
int iChildHeight = 0;
//獲取子VIEW數量用於迭代
int childCount = getChildCount();
//單行信息容器
//當前行存放多少個View
List<View> viewList = new ArrayList<>();
for (int i = 0;i < childCount;i++){
View childAt = getChildAt(i) ;
//1.測量自己
//測量子View的寬和高,測量完之後會進行保存
measureChild(childAt,widthMeasureSpec,heightMeasureSpec);
//2.獲取getLayoutParams 即XML資源中定義的參數
//獲取偏移量,即佈局中margin屬性的值
MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();
//3.獲得實際寬度和高度(MARGIN+WIDTH)
iChildWidth = childAt.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
iChildHeight = childAt.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
//4.是否需要換行
//判斷當前剩餘的寬度是否需要下一個控件的繪製。判斷是否需要換行
if(iCurLineW + iChildWidth > widthSize){
//4.1.紀錄當前行信息
//4.1.1.紀錄當前行最大寬度,高度累加
measureWidth = Math.max(measureWidth,iCurLineW);
measureHeight += iCurLineH;
//4.1.2.保存這一行數據,及行高
lstLineHegiht.add(iCurLineH);
lstLineView.add(viewList);
//4.2.紀錄新的行信息
//4.2.1.賦予新行新的寬高
iCurLineW = iChildWidth;
iCurLineH = iChildHeight;
//4.2.2添加新行紀錄
viewList = new ArrayList<View>();
viewList.add(childAt);
}else{
//5.1.不換行情況
//5.1.1.記錄某行內的消息行內寬度的疊加、高度比較
iCurLineW += iChildWidth;
iCurLineH = Math.max(iCurLineH, iChildHeight);
//5.1.2.添加至當前行的viewList中
viewList.add(childAt);
}
//6.如果正好是最後一行需要換行
if(i == childCount - 1){
//6.1.記錄當前行的最大寬度,高度累加
measureWidth = Math.max(measureWidth,iCurLineW);
measureHeight += iCurLineH;
//6.2.將當前行的viewList添加至總的mViewsList,將行高添加至總的行高List
lstLineView.add(viewList);
lstLineHegiht.add(iCurLineH);
}
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//開始佈局
//1.取得所有視圖信息
//與之當前組件上下左右四個邊距
int left,top,right,bottom;
//當前頂部高度和左部高度
int curTop = 0;
int curLeft = 0;
//開始迭代
int lineCount = lstLineView.size();
for(int i = 0 ; i < lineCount ; i++) {
List<View> viewList = lstLineView.get(i);
int lineViewSize = viewList.size();
for(int j = 0; j < lineViewSize; j++){
View childView = viewList.get(j);
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
left = curLeft + layoutParams.leftMargin;
top = curTop + layoutParams.topMargin;
right = left + childView.getMeasuredWidth();
bottom = top + childView.getMeasuredHeight();
//同理,通過調用自身的layout進行佈局
childView.layout(left,top,right,bottom);
//左邊部分累加
curLeft += childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
}
//進入下一行
curLeft = 0;
curTop += lstLineHegiht.get(i);
}
lstLineView.clear();
lstLineHegiht.clear();
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.dn_alan.myapplication.WaterfallFlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
style="@style/text_flag_01"
android:text="hello" />
<TextView
style="@style/text_flag_01"
android:text="hello,hi" />
<TextView
style="@style/text_flag_01"
android:text="你是我的"
android:textSize="18sp" />
<TextView
style="@style/text_flag_01"
android:text="hello,man" />
<TextView
style="@style/text_flag_01"
android:text="helloview" />
<TextView
style="@style/text_flag_01"
android:text="view" />
<TextView
style="@style/text_flag_01"
android:text="我是你的"
android:textSize="20sp" />
<TextView
style="@style/text_flag_01"
android:text="he" />
<TextView
style="@style/text_flag_01"
android:text="hello" />
<TextView
style="@style/text_flag_01"
android:text="textview" />
<TextView
style="@style/text_flag_01"
android:text="view" />
<TextView
style="@style/text_flag_01"
android:text="hello,view" />
<TextView
style="@style/text_flag_01"
android:text="hello,mt" />
<TextView
style="@style/text_flag_01"
android:text="hel" />
<TextView
style="@style/text_flag_01"
android:text="hel" />
<TextView
style="@style/text_flag_01"
android:text="hel2" />
</com.dn_alan.myapplication.WaterfallFlowLayout>
</LinearLayout>
<style name="text_flag_01">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_margin">4dp</item>
<item name="android:background">@drawable/flag_01</item>
<item name="android:textColor">#ffffff</item>
</style>
看下效果