详细了解 Android 巧用 flexboxLayout 布局
FlexBoxlayout是Google推出的開源的可伸縮性布局,在項目中也會經場使用,大大提高了用戶的體驗。
compile 'com.google.android:flexbox:1.0.0'有前端基礎的同學估計都知道 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,提高性能。具體使用示例:
| ? 1 2 3 4 5 6 7 8 9 10 | ? //設置主軸方向為橫軸 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 中去設置,設置示例代碼為:
| ? 1 2 3 4 5 6 | ? 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
實際應用中還有種很常見的就是那種分類選擇的布局,像圖中的網易和簡書中,這種布局用我們今天的這個主角是不是輕而易舉的就實現了?都不用設置特別的屬性,內容超過一行自動換行。
代碼如下:
| ? 1 2 3 4 5 6 7 8 9 10 | ? //設置主軸方向為橫軸 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
以項目需求為例,如圖所示:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? ? ? ? ? ? ? ? ? ? ? ? ??
在第一張需求圖中,品牌篩選時 最后有一個”+“號,想要的效果時 根據品牌選擇后,這個”+“號會緊挨著顯示,如果顯示不完,就換行顯示。
剛開始考慮的是的在RecyclerView添加FooterView,FooterView為一個“+”的按鈕,這個按鈕會換一行顯示,不能緊挨著顯示,如果超過一行再換行顯示。
此時會遇到兩個問題,
按照RecyclerView中的基本用法使用后 會報錯,日志如下:
ClassCastException: android.support.v7.widget.RecyclerView$LayoutParams cannot be cast to com.google.android.flexbox.FlexItem。
我們添加的FooterView,無法轉換成FlexlItem。網上找到解決方案,詳情見FlexboxLayoutManager 踩坑。
一是需要重寫FlexboxLayoutManager,代碼示例如下:
?
import com.google.android.flexbox.FlexboxLayoutManager
class MyFlexboxLayoutManager : FlexboxLayoutManager {
constructor(context: Context) : super(context)
constructor(context: Context, flexDirection: Int) : super(context, flexDirection)
constructor(context: Context, flexDirection: Int, flexWrap: Int) : super(context, flexDirection, flexWrap)
/**
* 將LayoutParams轉換成新的FlexboxLayoutManager.LayoutParams
*/
override fun generateLayoutParams(lp: ViewGroup.LayoutParams): RecyclerView.LayoutParams {
return when (lp) {
//TODO:可能需要適配,特殊處理"+"的寬度
is RecyclerView.LayoutParams -> LayoutParams(lp)
is ViewGroup.MarginLayoutParams -> LayoutParams(lp)
else -> LayoutParams(lp)
}
}
}
第二個問題:顯示問題。此時會出現我們的FooterView(”+“號)換一行顯示,不能緊挨著顯示。
于是陷入思考,在上述代碼中,RecyclerView.LayoutParams的計算方法LayoutParams(lp)也需要重寫。
在查閱資料和源碼后發現比較麻煩,最后在網友的幫助下(FlexboxLayoutManager 踩坑),換一種思路,用RecyclerView多布局來實現,豁然開朗。
思路如下:在數據實體類中添加一個標志,如是否是添加標志isAdd,根據該值的是不同顯示不同的item布局。
?class CameraVehicleBrandAdapter : MyBaseMultiItemAdapter<CameraVehicleBrandEntity>() {
init {
addItemType(CameraVehicleBrandEntity.ITEM_TYPE_NORMAL, R.layout.home_recycle_item_camera_vehicle_brand_selected)
addItemType(CameraVehicleBrandEntity.ITEM_TYPE_ADD, R.layout.home_recycle_item_camera_vehicle_brand_add)
}
override fun convert(helper: BaseViewHolder, item: CameraVehicleBrandEntity) {
super.convert(helper, item)
if (item.isAdd) {
helper.addOnClickListener(R.id.ivAddBrand)
return
}
helper.setText(R.id.tvBrandName, item.brandName)
helper.addOnClickListener(R.id.ivDelete)
}
}
調用方法:
?//品牌篩選
rvBrand.layoutManager = FlexboxLayoutManager(context)
rvBrand.adapter = brandAdapter
?
總結
所以說了這么多,那么我們什么時候會用到這種布局呢?我目前想到的場景主要有 3 類:
當然這些場景加上 RecycleView 就會更加暢享絲滑了。
總結
以上是生活随笔為你收集整理的详细了解 Android 巧用 flexboxLayout 布局的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JDK1.8 升级这么久!Stream
- 下一篇: ACwing166数独与183靶形数独