瀑布流
瀑布流大家都太熟悉了,經常聽說,最出名的恐怕就是pinterest
瀑布流的應用已經很普遍了,最初是開源庫的瀑布流,大家用的也是不亦樂乎
開源瀑布流
随便一搜,就有很多很成熟的開源的瀑布流
官方瀑布流
可能是鑒于瀑布流的使用确實非常多,在google推出RecyclerView時除了以往的線性布局跟網格布局,也推出了瀑布流布局。
API位址
瀑布流的ViewGroup
不管是開源的,還是官方的StaggeredGridLayoutManager,都是清單的瀑布流,也就是說在一整個清單中錯落開來達到一個瀑布流的效果。
但是卻沒有基于ViewGroup的瀑布流。
比如要實作這樣的需求
這樣的設計頁面,每一個子View都是不一樣的布局,其實就是兩層,分組的意思
而開源的瀑布流或者StaggeredGridLayoutManager都是以清單為緯度的,隻有一層,就沒有辦法實作這個需求了。
是以這樣的需求,我們隻能自己寫了。
第一種實作方式 – XML布局中寫死
最直接的方式當然就是在XML布局中寫死這樣的布局,哪一組是9個子View,哪一組是4個子View都是寫死的。
這樣的寫當然很low,而且如果伺服器沒有傳回規定的資料個數,這還會出現空白,總之這樣實作太死闆,根本無法适應資料的變化。
第二種實作方式–自定義ViewGroup
自定義去排列ViewGroup中的子View,重寫onMeasure、onLayout方法,實作自定義測量,自定義布局,自動排列的功能。
先來看看View繪制的知識
部落格位址
看完上面的部落格,我們應該對view繪制有了一定的了解,然後看我們的自定義的ViewGroup
public class ViewContainer extends ViewGroup {
/*一共幾列*/
public int columns = ;
/*是否有分割線,0為沒有,1為有分割線,分割線隻是子View連接配接之間的分割線,上下左右邊線用padding就好*/
public int hasDivider = ;
/*分割線的寬度*/
public int dividerWid = ;
/*分割線的顔色*/
public int dividerColor = R.color.background_color;
public ViewContainer(Context context) {
super(context);
}
public ViewContainer(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/*螢幕總寬度*/
int match_parent_wid_value = MeasureSpec.getSize(widthMeasureSpec);
/*需要的總高度*/
int total_hei = ;
/*目前的寬度*/
int tempWid = ;
/*上一個子View的高度*/
int beforeHeight = ;
/*子View的寬度值*/
int childWidthValue = ;
/*子View的高度值*/
int childHeiValue = ;
/*子View的高度MeasureSpec值*/
int childHeight = ;
/*子View的寬度MeasureSpec值*/
int childWidth = ;
/**計算一共有多少行**/
int rowCount = ;
/*分割線的寬度*/
int divider= ;
if (hasDivider == ){
divider = ;
}else if (hasDivider == ){
divider = dividerWid;
}
for (int i = ; i < getChildCount(); i++) {
ViewContainerItem view = (ViewContainerItem) getChildAt(i);
if (view.isMatchParent == ) {
childWidthValue = match_parent_wid_value;
} else {
/**每個子View的真正的寬度,減去分割線的寬度**/
childWidthValue = (match_parent_wid_value - (columns - ) * divider) / columns;
}
/*高度是否跟寬度相等,正方形*/
if (view.isHeiEqualWid == ) {
childHeiValue = childWidthValue;
} else {
/**如果沒有指定寬度和高度相等,那麼檢查有沒有設定radio,寬高比例**/
if (Math.abs(view.radio - f) != ) {
childHeiValue = (int) (childWidthValue / view.radio);
} else {
/*什麼都沒有設定的view,高度等于自身設定的高度*/
childHeiValue = view.height;
}
}
/*為每一個子View的寬高計算MeasureSpec值*/
childWidth = MeasureSpec.makeMeasureSpec(childWidthValue, MeasureSpec.EXACTLY);
childHeight = MeasureSpec.makeMeasureSpec(childHeiValue, MeasureSpec.EXACTLY);
/**換行**/
if (tempWid + childWidthValue > match_parent_wid_value) {
total_hei += beforeHeight;
rowCount++;
tempWid = ;
}
if (view.isMatchParent == ) {
view.measure(widthMeasureSpec, childHeight);
tempWid += match_parent_wid_value;
} else {
view.measure(childWidth, childHeight);
tempWid += childWidthValue;
}
beforeHeight = childHeight;
if (i == getChildCount() - ) {
total_hei += beforeHeight;
rowCount++;
}
}
/**計算完畢後,加上divider的高度**/
/** 有底部分割線時,分割線的數目等于行數,沒有底部分割線時,分割線的數目等于行數減一 **/
int dividerCount = ;
dividerCount = rowCount - ;
/*總高度加上分割線的高度*/
total_hei += dividerCount * divider;
int totalHei = MeasureSpec.makeMeasureSpec(total_hei, MeasureSpec.EXACTLY);
/*設定背景色,分割線其實就是背景色透過來的顔色,在onlayout時,每個子View之間有間隙,透過來的縫隙就是分割線*/
setBackgroundColor(getResources().getColor(dividerColor));
setMeasuredDimension(widthMeasureSpec, totalHei);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int tempWidth = ;
int tempHeight = ;
int beforeHeight = ;
int divider = ;
/*分割線*/
if (hasDivider == ) {
divider = dividerWid;
} else if (hasDivider == ){
divider = ;
}
for (int i = ; i < getChildCount(); i++) {
ViewContainerItem view = (ViewContainerItem) getChildAt(i);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
/*如果已經超過了螢幕寬度,則換行*/
if (tempWidth + width > r) {
/*換行時,加上上一行的高度,還要加上分割線的高度*/
tempHeight += beforeHeight+divider;
/*寬度置為0,從最左邊開始布局子view*/
tempWidth = ;
}
/*占滿螢幕寬度的子View*/
if (view.isMatchParent == ) {
view.layout(tempWidth, tempHeight, r, tempHeight + height);
tempWidth += r;
} else {
/*普通子View*/
view.layout(tempWidth, tempHeight, tempWidth + width, tempHeight + height);
/*布局完前一個子View,加上前一個View的寬度後,還要加上分割線的寬度
* 下一次布局時,就留出來了分割線的寬度
* 因為算的是布局時起始的位置,是以即使是一行中最後一個View也不會因為多加了分割線出問題
* */
tempWidth += width + divider;
}
/*記錄上一個View的高度,如果下一個View在目前行排不開了,需要另起一行,需要加上前一行的高度*/
beforeHeight = height;
}
}
}
代碼中的注釋都很清楚了,當然就算代碼再清楚恐怕我們還是看不太明白到底是怎麼能達到自動排列view的目的。
demo
先看下效果圖
具體代碼就不多說了,大家可以下載下傳demo來看看,有了我們自定義的ViewGroup之後,demo的實作就很簡單了。
demo Github
希望能幫到你。