Android可伸縮布局-FlexboxLayout(支持RecyclerView集成)
1 . 前言
前幾天看到Google官方的博客介紹了Google開源的一個強大的布局-FlexboxLayout,看見第一眼我心里的想法是,臥槽,Android 居然有這么一個強大的布局。作為一個有好奇心的工程獅,當然第一時間就去試了試手,效果非常贊,因此這篇文章就介紹一下它的用法和最新版添加的一些特性(支持集成RecyclerView),本文目錄如下:
本文目錄.png
2 . 什么是FlexboxLayout
那么FlexboxLayout 它到底是個什么東西呢?看一下Github對這個庫的介紹:FlexboxLayout is a library project which brings the similar capabilities of CSS Flexible Box Layout Module to Android. 意思是:FlexboxLayout是一個Android平臺上與CSS的 Flexible box 布局模塊 有相似功能的庫。Flexbox 是CSS 的一種布局方案,可以簡單、快捷的實現復雜布局。FlexboxLayout可以理解成一個高級版的LinearLayout,因為兩個布局都把子view按順序排列。兩者之間最大的差別在于FlexboxLayout具有換行的特性。
3 . FlexboxLayout示例
既然說FlexboxLayout方便、強大,那么我們就先以一個示例來看一下它的一個簡單實用場景:現在很多APP都有標簽功能,本節以簡書首頁的熱門專題(標簽)為例,看一下使用FlexboxLayout來實現有多方便。
簡書首頁熱門專題如下圖:
簡書專題標簽.png
使用Flexbox實現效果如下:
FlexboxLayout.png
添加依賴:
compile 'com.google.android:flexbox:0.2.5'
代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.flexbox.FlexboxLayout
xmlns:android="
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="程序員"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="影視天堂"
app:layout_flexGrow="1"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_flexGrow="1"
app:layout_alignSelf="flex_start"
android:text="美食"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_flexGrow="1"
app:layout_alignSelf="flex_start"
android:text="漫畫.手繪"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="廣告圈"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="旅行.在路上"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="娛樂八卦"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="青春"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="談寫作"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="短篇小說"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="散文"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
app:layout_alignSelf="flex_start"
android:text="攝影"
app:layout_order="2"
android:gravity="center"
android:textColor="@color/text_color"
android:background="@drawable/label_bg_shape"
/>
</com.google.android.flexbox.FlexboxLayout></code></pre>
很簡單,就一個布局文件,以FlexboxLayout為父布局,向容器里面添加子Item 就行了。要是不用這FlexboxLayout,以前要實現這樣的一個界面,需要自定義View,還是有點麻煩,網上也有很多通過自定義View來實現可伸縮變遷界面的,有興趣的可以去搜來看一下。要知道,這只是FlexboxLayout的冰山一角,FlexboxLayout真正強大的是它定義的這些屬性,通過設置不同的值可以得到不同的效果,接下來就看一下它支持哪些屬性。
4 . FlexboxLayout支持的屬性介紹
上面說了FlexboxLayout真正強大的是它定義的屬性,那么這一節我們看一下Flexbox支持哪些屬性,分為2個方面,FlexboxLayout支持的屬性和FlexboxLayout 子元素支持的屬性:
FlexboxLayout 支持的屬性:
flexDirection
flexDirection 屬性決定了主軸的方向,即FlexboxLayout里子Item的排列方向,有以下四種取值:
- row (default): 默認值,主軸為水平方向,從左到右。
- row_reverse:主軸為水平方向,起點在有端,從右到左。
- column:主軸為豎直方向,起點在上端,從上到下。
-
column_reverse:主軸為豎直方向,起點在下端,從下往上。
文字有點蒼白無力,看一下四種排列的對比,就明白了:

Paste_Image.png
flexWrap
flexWrap 這個屬性決定Flex 容器是單行還是多行,并且決定副軸(與主軸垂直的軸)的方向。可能有以下3個值:
- noWrap: 不換行,一行顯示完子元素。
- wrap: 按正常方向換行。
- wrap_reverse: 按反方向換行。
justifyContent
justifyContent 屬性控制元素主軸方向上的對齊方式,有以下5種取值:
- flex_start (default): 默認值,左對齊
- flex_end: 右對齊
- center: 居中對齊
- space_between: 兩端對齊,中間間隔相同
- space_around: 每個元素到兩側的距離相等。
alignItems
alignItems 屬性控制元素在副軸方向的對齊方式,有以下5種取值:
- stretch (default) :默認值,如果item沒有設置高度,則充滿容器高度。
- flex_start:頂端對齊
- flex_end:底部對齊
- center:居中對齊
- baseline:第一行內容的的基線對齊。
文字顯示有些蒼白無力,用一張圖來對比看一下,更好理解(圖片來自 Google 開源的 Android 排版庫:FlexboxLayout ):

AlignItems.png
alignContent
alignContent 屬性控制多根軸線的對齊方式(也就是控制多行,如果子元素只有一行,則不起作用),可能有一下6種取值:
- stretch (default): 默認值,充滿交叉軸的高度(測試發現,需要alignItems 的值也為stretch 才有效)。
- flex_start: 與交叉軸起點對齊。
- flex_end: 與交叉軸終點對齊。
- center: 與交叉軸居中對齊。
- space_between: 交叉軸兩端對齊,中間間隔相等。
- space_around: 到交叉軸兩端的距離相等。
showDividerHorizontal
showDividerHorizontal 控制顯示水平方向的分割線,值為 none | beginning | middle | end 其中的一個或者多個。
dividerDrawableHorizontal
dividerDrawableHorizontal 設置Flex 軸線之間水平方向的分割線。
showDividerVertical
showDividerVertical 控制顯示垂直方向的分割線,值為 none | beginning | middle | end 其中的一個或者多個。
dividerDrawableVertical
dividerDrawableVertical 設置子元素垂直方向的分割線。
showDivider
showDivider 控制顯示水平和垂直方向的分割線,值為 none | beginning | middle | end 其中的一個或者多個。
dividerDrawable
dividerDrawable 設置水平和垂直方向的分割線, 但是注意,如果同時和其他屬性使用,比如為 Flex 軸、子元素設置了 justifyContent="space_around" 、 alignContent="space_between" 等等。可能會看到意料不到的空間,因此應該避免和這些值同時使用。
5 . FleboxLayout子元素支持的屬性介紹
FlexboxLayout 子元素支持的屬性:
layout_order
layout_order 屬性可以改變子元素的排列順序,默認情況下,FlexboxLayout子元素的排列是按照xml文件中出現的順序。默認值為1,值越小排在越靠前。
layout_flexGrow(float)
layout_flexGrow 子元素的放大比例, 決定如何分配剩余空間(如果存在剩余空間的話),默認值為0,不會分配剩余空間,如果有一個item的 layout_flexGrow 是一個正值,那么會將全部剩余空間分配給這個Item,如果有多個Item這個屬性都為正值,那么剩余空間的分配按照 layout_flexGrow 定義的比例(有點像 LinearLayout 的 layout_weight 屬性)。
layout_flexShrink(float)
layout_flexShrink :子元素縮小比例,當空間不足時,子元素需要縮小(設置了換行則無效),默認值為1,如果所有子元素的 layout_flexShrink 值為1,空間不足時,都等比縮小,如果有一個為0,其他為1,空間不足時,為0的不縮小,負值無效。
layout_alignSelf
layout_alignSelf 屬性可以給子元素設置對齊方式,上面講的 alignItems 屬性可以設置對齊,這個屬性的功能和 alignItems 一樣,只不過 alignItems 作用于所有子元素,而 layout_alignSelf 作用于單個子元素。默認值為auto, 表示繼承 alignItems 屬性,如果為auto以外的值,則會覆蓋 alignItems 屬性。有以下6種取值:
- auto (default)
- flex_start
- flex_end
- center
- baseline
- stretch
除了 auto 以外,其他和 alignItems 屬性一樣。
layout_flexBasisPercent (fraction)
layout_flexBasisPercent 的值為一個百分比,表示設置子元素的長度為它父容器長度的百分比,如果設置了這個值,那么通過這個屬性計算的值將會覆蓋 layout_width 或者 layout_height 的值。 但是需要注意,這個值只有設置了父容器的長度時才有效(也就是MeasureSpec mode 是 MeasureSpec.EXACTLY) 。默認值時-1。
layout_minWidth / layout_minHeight (dimension)
強制限制 FlexboxLayout的子元素(寬或高)不會小于最小值,不管 layout_flexShrink 這個屬性的值為多少,子元素不會被縮小到小于設置的這個最小值。
layout_maxWidth / layout_maxHeight (dimension)
這個和上面的剛好相反,強制限制FlexboxLayout子元素不會大于這個最大值, 不管 layout_flexGrow 的值為多少,子元素不會被放大到超過這個最大值。
layout_wrapBefore
layout_wrapBefore 屬性控制強制換行,默認值為false,如果將一個子元素的這個屬性設置為true,那么這個子元素將會成為一行的第一個元素。這個屬性將忽略 flex_wrap 設置的 noWrap值。
6 . 與RecyclerView 的結合
在最新的alpha版本,Flexbox能夠作為一個LayoutManager(FlexboxlayoutManager) 用在RecyclerView里面,這也就意味著你可以在一個有大量Item的可滾動容器里面使用Flexbox了。
來看一下繼承RecyclerView 使用的示例:
(1)首先要依賴最新的alpha版本,如下:
// compile 'com.google.android:flexbox:0.2.5'
// 使用最新的alpha版本
compile 'com.google.android:flexbox:0.3.0-alpha2'
(2)使用FlexboxLayoutManager代替LinearLayoutManager,如下:
FlexboxLayoutManager manager = new FlexboxLayoutManager();
//設置主軸排列方式
manager.setFlexDirection(FlexDirection.ROW);
//設置是否換行
manager.setFlexWrap(FlexWrap.WRAP);
manager.setAlignItems(AlignItems.STRETCH);
(3) 在Adapter里設置 flexGrow :
ImageView mImageView = rvBaseViewHolder.getImageView(R.id.image_src);
mImageView .setImageResource(mData);
ViewGroup.LayoutParams lp = mImageView.getLayoutParams();
if (lp instanceof FlexboxLayoutManager.LayoutParams) {
FlexboxLayoutManager.LayoutParams flexboxLp =
(FlexboxLayoutManager.LayoutParams) mImageView.getLayoutParams();
flexboxLp.setFlexGrow(1.0f);
}</code></pre>
效果圖如下:

FlexboxLayoutManager.gif
是不是很強大?效果類似瀑布流,但是瀑布流是指定了列數的,這個完全是自定換行,大小屏幕的適配完全解決,簡直完美。但是目前與RecyclerView 的集成還是alpha版本,還沒有合并到master分支,相信不久后能出穩定的版本。
由于RecyclerView 的一些特性,Flexbox 的一些屬性在FlexboxLayoutManager中沒有實現,下面是FlexboxLayout和FlexboxLayoutManager支持的屬性的對比:

支持屬性的異同.png
注意紅框中圈出來的屬性,FlexboxlayoutManager是支持View回收的,而FlexboxLayout是不支持View回收的,FlexboxLayout只適用于少量Item的場景,這也是為什么會出現FlexboxLayoutManager的原因吧.
7 . 總結
FlexboxLayout是Google 去年開源的一個與CSS Flexbox有類似功能的強大布局,具有換行特性,使用起來特別方便,但是,FlexboxLayout是沒有考慮View回收的,因此,它只使用于只有少量子Item的場景,如果向其中添加大量Item 是灰導致內存溢出的。所幸,最新的版本添加了與RecyclerView的集成,這就可以在有大量子Item的場景下使用了,只是最新的版本還是alpha版,還沒有出穩定的版本,相信不久后就能使用穩定的版本了。 另外,FlexboxLayout的這寫屬性的意義可能不好理解 ,建議大家去寫個demo試一下每個屬性的每個值看看是什么效果,這樣就能很好的理解每個屬性了。
參考:
https://github.com/google/flexbox-layout
Build flexible layouts with FlexboxLayout
Google 開源的 Android 排版庫:FlexboxLayout
來自:http://www.jianshu.com/p/3c471953e36d