前言
某天在做專案的時候,遇到了個看似簡單但又無從下手的小功能,效果類似微信群聊選擇聯絡人介面:
有沒有發現選擇人員變動後上面部分的展示是從中間往兩邊擴散的?最開始的時候我是直接用 RecycleView 展示的,就是一再普通不過的九宮格佈局了。在產品的要求下要改成類似微信的這種佈局,然後我就開始了 “面向搜尋引擎”的程式設計,搜尋了半天,什麼仿微信啊,什麼中間佈局啊(事實證明就算是“面向搜尋引擎”的程式設計,我還是要學很多,因為,關鍵字定位的不準確),未果。然後一想,嘿嘿,沒事,輪子沒有自己造唄,一看就得自定義佈局。遂拿起我的小本本,準備大幹一番,用筆寫下思路,磨蹭了十幾分鍾,本子上只有幾個立體幾何。。。(有個小癖好,寫不出來喜歡在本子上畫立方體)。
然後想一時半會自定義也掌握不好啊,時間又趕,遂又開始了大規模搜尋。在快要放棄的時候看到了這篇文章:Android 彈性佈局 FlexboxLayout瞭解一下 看到其中這張圖的時候,兩眼放光,異常激動啊。
看完是不是感覺希望就在前方? 於是看了下 flexboxLayout 的介紹,完完全全就是前端的 flexbox 佈局啊,連屬性名稱都一樣。因為有點 react native 基礎,所以事情變得 so easy。最後看到竟然支援 RecycleView,只需改動極少量的程式碼,簡直不能太贊。嗯,廢話那麼多,鋪墊那麼長,下面來介紹一下今天的主角 : flexboxLayout 。
首先要做的事情當然是在專案中整合了:
compile 'com.google.android:flexbox:1.0.0'
複製程式碼
1.什麼是 flexboxLayout 佈局?
github.com/google/flex… 專案簡介中是這樣一句話來概括 flexboxLayout 的:
FlexboxLayout is a library project which brings the similar capabilities of CSS Flexible Box Layout Module to Android. FlexboxLayout 是一個在 Android 上實現 CSS 的 彈性盒狀佈局 模組的庫
有前端基礎的同學估計都知道 CSS 中這個佈局,用來為盒狀模型提供最大的靈活性。因為 android 中這個庫屬性和 CSS 中 都一樣,並且阮一峰老師寫的前端知識真的很通俗易懂,所以這裡的介紹大多來自 Flex 佈局教程。
採用 Flex 佈局的元素,稱為 Flex 容器(flex container),簡稱"容器"。它的所有子元素自動成為容器成員,稱為 Flex 專案(flex item),簡稱"專案"。容器預設存在兩根軸:水平的主軸(main axis)和垂直的交叉軸(cross axis)。這裡與 react native 相反,與前端 CSS 保持一致。
主軸的開始位置(與邊框的交叉點)叫做main start,結束位置叫做main end;交叉軸的開始位置叫做cross start,結束位置叫做cross end
專案預設沿主軸排列。單個專案佔據的主軸空間叫做 main size,佔據的交叉軸空間叫做 cross size。
2.容器的屬性 (FlexboxLayout 屬性介紹)
這裡說的容器也就是上面的採用了 Flex 佈局的元素,在 android 中也就是引用了 FlexboxLayout 的 控制元件。即 FlexboxLayout 控制元件支援的屬性。主要屬性有:
各個屬性的詳細含義這裡就不再贅述,阮一峰老師 這篇文章 寫的超級棒,圖文並茂,很容易理解,推薦大家看一下。3.專案的屬性 (子 View 屬性介紹)
設定被 FlexboxLayout 包裹的子 View 的屬性,因為 android 中的 flexboxLayout 佈局 和 CSS 中 flex 佈局關於子 View 屬性有些差異,所以這裡詳細說明下,取值如下:
- layout_order (integer)
- layout_flexGrow (float)
- layout_flexGrow (float)
- layout_alignSelf
- layout_flexBasisPercent (fraction)
- layout_minWidth / layout_minHeight (dimension)
- layout_maxWidth / layout_maxHeight (dimension)
- layout_wrapBefore (boolean)
下面來看一下github文件中對這些屬性的描述
3.1 layout_order
這個屬性可以改變佈局子檢視的順序。預設情況下,子元素的顯示和佈局順序與佈局XML中的順序相同。如果沒有指定,則將1設定為預設值( CSS 中預設值為 0) ,數值越小,排列越靠前。
看下文件中的這張圖,可以看到將 “2” 號 View 的 layout_order 屬性設定為 -1時,由於其他的 View 預設都是 1,所以 “2” 號 view會排在最前面,同理,將 “2” 號 View 的 layout_order 的屬性值設為 2 時,比其他預設值 1 都大,所以會排在最後。3.2 layout_flexGrow
這個屬性類似於 LinearLayout 中的 layout_weight 屬性,如果沒有指定,則將 0 設定為預設值。如果果同一 flex 行中的多個子 View 有正的 layout_flexGrow 值,那麼剩餘的空閒空間將根據它們宣告的 layout_flexGrow 值的比例分佈。
3.3 layout_flexShrink
該屬性定義了子 View 的縮小比例,預設為 1,即如果空間不足,該子 View 將縮小。 如果所有子 View 的 layout_flexShrink 屬性都為 1,當空間不足時,都將等比例縮小。如果一個專案的 layout_flexShrink 屬性為0,其他子View都為 1,則空間不足時,layout_flexShrink 屬性為 0 的不縮小。
看一下文件中的這張圖,開始設定所有子 view 的 layout_flexShrink 屬性為1,新增子 view 的時候所有子 view 等比縮小,但是如果設定 layout_flexShrink 屬性值為 0,子 view 將會按照原有比例顯示,不縮小。3.4 layout_alignSelf
layout_alignSelf 屬性允許單個子 View 有與其他 View 不一樣的對齊方式,可覆蓋 align-items 屬性。預設值為 auto,表示繼承父元素的 align-items 屬性,如果沒有父元素,則等同於 stretch。 該屬性可能取 6 個值,除了 auto,其他都與 align-items 屬性完全一致
3.5 layout_flexBasisPercent
flex-layout_flexBasisPercent 屬性定義了在分配多餘空間之前,子 View 佔據的主軸空間(main size)。根據這個屬性來計算主軸是否有多餘空間。它的預設值為 -1,即不設定,採用子 View 的本來大小。
如果設定了這個值,layout_width (或 layout_height )中指定的長度將被該屬性的計算值覆蓋。這個屬性只有在父 View 的長度是確定的時候才有效(測量模式是 MeasureSpec.EXACTLY 模式下)。 並且該屬性值只接受百分比值。
可以分析下文件中的這張圖:可以看到,如果把中間子 View 的這個屬性值設為 50% 或 90%,那麼這個 View 將佔據主軸 50% 或 90% 的空間,然後剩餘 View 會看有沒有剩餘空間換行。如果設定為 -1 預設值,那麼將佔據給定的大小。3.6 layout_minWidth / layout_minHeight
這個屬性設定了子 View 的最小的寬和高。在 layout_flexShrink 模式下,再怎麼縮小也不會小於這個值
3.7 layout_maxWidth / layout_maxHeight
這個屬性設定了子 View 的最大的寬和高。在 layout_flexGrow 模式下,再怎麼放大也不會大於這個值
3.8 layout_wrapBefore
這個屬性使得子 View 可以強制換行,不管在 main size 剩餘空間有多少。這種對於類似 grid 網格佈局中特殊設定某一個 item 佈局特別有用。 這個屬性是 CSS 中沒有的屬性。該屬性在 flex_wrap 屬性值 為 nowrap(不換行)的時候是無效的。 該屬性結束 boolean 變數,預設 false,即不強制換行
分析下文件中的這張圖,“5” 號和 “6” 號 View 設定 layout_wrapBefore 屬性為ture 的時候,不管前面剩餘多少空間,都會強制換行到這裡,flexboxLayout 基本屬性就介紹完畢了。
然後再來介紹一下跟 recycleView 結合使用。
4. 高能:與 RecyclewView 結合使用
Flexbox 能夠作為一個 LayoutManager(FlexboxlayoutManager) 用在 RecyclerView 裡面,這也就意味著你可以在一個有大量 Item 的可滾動容器裡面使用 Flexbox,提高效能。具體使用示例:
//設定主軸方向為橫軸
FlexboxLayoutManager manager = new FlexboxLayoutManager(this, FlexDirection.ROW);
//設定item沿主軸方向的位置
manager.setJustifyContent(JustifyContent.CENTER);
//設定item 沿次軸方向的位置
manager.setAlignItems(AlignItems.CENTER);
recycleView.setLayoutManager(manager);
centerGridAdapter = new CenterGridAdapter(items, this);
recycleView.setAdapter(centerGridAdapter);
複製程式碼
可以看到跟 RecycleView 的其他 manager 使用一樣,只需設定 manager 屬性即可,屬性值為上面敘述的幾個容器的屬性。 如果想對某個 item 進行單獨的設定,可以在 adapter 中去設定,設定示例程式碼為:
ViewGroup.LayoutParams lp = holder.itemLL.getLayoutParams();
if (lp instanceof FlexboxLayoutManager.LayoutParams) {
FlexboxLayoutManager.LayoutParams flexboxLp =
(FlexboxLayoutManager.LayoutParams) holder.itemLL.getLayoutParams();
flexboxLp.setFlexGrow(1.0f);
}
複製程式碼
我這裡是設定每個 item 有個權重(相當於 Linearlayout 的 weight 屬性),所以會按比例分配 item 的寬,而不是我佈局中設定的固定寬高。看下效果:
是不是有種鍵盤的感覺?並且我只是修改了極少的程式碼就實現了這個功能。
小試牛刀1
最後,看了那麼多,回到最開始的問題上,現在知道類似微信的那個中間擴充套件的網格佈局怎麼寫的嗎? 首先我們簡單分析一下,
- 主軸方向我們應該設定為水平方向,即預設 flexDirection :“row”’
- 可以換行,即 flexWrap:“wrap”
- 子 View 在主軸方向的對其方式為居中(這一步實現從中間往兩邊展開),即 justifyContent: "center"
- 子 View 在交叉軸方向的對其方式為居中,即 alignItems:"center"
- 子 View 寬高固定
也就是上面講 Recycleview 結合的例子中去掉單獨設定 item 的部分,並且 item 的寬高要根據螢幕來適配的。 嗯,就是 so easy。
小試牛刀2
實際應用中還有種很常見的就是那種分類選擇的佈局,像圖中的網易和簡書中,這種佈局用我們今天的這個主角是不是輕而易舉的就實現了?都不用設定特別的屬性,內容超過一行自動換行。 程式碼如下: //設定主軸方向為橫軸
FlexboxLayoutManager manager = new FlexboxLayoutManager(this, FlexDirection.ROW);
//設定item沿主軸方向的位置
manager.setJustifyContent(JustifyContent.FLEX_START);
//設定item 沿次軸方向的位置
manager.setAlignItems(AlignItems.CENTER);
recycleView.setLayoutManager(manager);
labelAdapter = new LabelAdapter(labels,this);
recycleView.setAdapter(labelAdapter);
複製程式碼
總結
所以說了這麼多,那麼我們什麼時候會用到這種佈局呢?我目前想到的場景主要有 3 類:
- 類似LinearLayout 線性佈局,但是又可以自動換行的
- 類似grid 網格佈局的,但總有一兩個item的排列方式很特立獨行的
- 類似瀑布流的,但也是總有一兩個item跟別人不一樣的
當然這些場景加上 RecycleView 就會更加暢享絲滑了。
demo 傳送門