使用FlexboxLayout构建灵活的布局

原文:Build flexible layouts with FlexboxLayout。 

去年的 Google I/O大会上我们宣布了ConstraintLayout, 一个用简单的视图结构就能构建复杂布局的控件。另外,它还完全支持Android Studio的可视化布局编辑器

与此同时,我们开源了FlexboxLayout,把css中的 Flexbox布局(Flexible Box)模块 带到了安卓中。这里就是一些FlexboxLayout特别有用的情形。

FlexboxLayout可以理解成一个高级版的LinearLayout,因为两个布局都把子view按顺序排列。两者之间最大的差别在于FlexboxLayout具有换行的特性。

也就是说如果你添加了flexWrap="wrap"属性,在当前行空间不足的情况下FlexboxLayout将把view放在下一行,就如下图这样:

Untitled_document0.png

一个布局适应不同屏幕尺寸

在这个特性的基础上,让我们考虑一个按序排列视图,但是在空间发生变化的时候可以让view移到新的一行的情况(可能因为设备因素,横竖屏变化,或者多窗口模式下的窗口调整)。

Untitled_document1.png

Nexus5X 竖屏

Untitled_document2.png

Nexus5X 横屏

Untitled_document3.png

启用多窗口模式,分割线在左边的的Pixel C

Untitled_document4.png

启用多窗口模式,分割线在中间的的Pixel C

Untitled_document5.png

启用多窗口模式,分割线在右边的的Pixel C

如果使用传统的布局,比如 LinearLayout或者RelativeLayout,你需要定义多个DP分类的布局(layout-600dp, layout-720dp, layout-1020dp)来处理屏幕尺寸的问题。但是上面的对话框其实是用一个FlexboxLayout构建的。

这个例子中使用到的技术就是前面提到的flexWrap="wrap",

<com .google.android.flexbox.flexboxlayout 
     android:layout_width="match_parent" 
     android:layout_height="wrap_content" 
     app:flexwrap="wrap">

然后你就可以得到下面这种能换行的布局,而不是视图从它的父布局益出。

Untitled_document6.png

另一个要强调的技术就是为单个子view设置layout_flexGrow 。在空间有剩余的情况下,这可以帮助改善布局的最终外观。layout_flexGrow属性类似于LinearLayout中的layout_weight。也就是说,FlexboxLayout将根据同一行中每个子view的layout_flexGrow的值来分配剩余的空间。

在下面的例子中,假设每个子view的layout_flexGrow属性为1,那么剩余空间将平均分配到每个子view。

 <android .support.design.widget.TextInputLayout
     android:layout_width="100dp"
     android:layout_height="wrap_content" 
     app:layout_flexgrow="1">

Untitled_document7.png

你可以在GitHub仓库上找到完整的 layout xml 文件。

RecyclerView集成 

FlexboxLayout的另一个好处是可以和RecyclerView集成。最新的alpha 版本中,Flexbox可以作为一个LayoutManager(FlexboxLayoutManager)用在RecyclerView中。现在你可以以更内存友好的方式在滚动容器中使用Flexbox的功能了。

不过你仍然可以把FlexboxLayout放在ScrollView中来实现滚动。但是这样的话如果item过多很可能会造成卡顿和内存益处的错误,因为FlexboxLayout并不会回收那些滚动出屏幕的view。

(如果你想了解RecyclerView的更多细节,可以看看来自ndroid UI toolkit团队的视频,比如 12

集成RecyclerView能带来好处的真实的例子是 Google Photo app或者新闻app,两者都具有大量的宽度各异的item。

FlexboxLayout仓库的demo application中有一个这样的例子,RecyclerView中的每个image宽度不同。但是通过把flexWrap设置为wrap,

FlexboxLayoutManager layoutManager = new FlexboxLayoutManager();
layoutManager.setFlexWrap(FlexWrap.WRAP);

并把每个子view的flexGrow设置为一个正数,(就如你看到的,我们可以通过FlexboxLayoutManager和FlexboxLayoutManager.LayoutParam设置这个属性,而不通过xml)

void bindTo(Drawable drawable) {
  mImageView.setImageDrawable(drawable);
  ViewGroup.LayoutParams lp = mImageView.getLayoutParams();
  if (lp instanceof FlexboxLayoutManager.LayoutParams) {
    FlexboxLayoutManager.LayoutParams flexboxLp = 
        (FlexboxLayoutManager.LayoutParams) mImageView.getLayoutParams();
    flexboxLp.setFlexGrow(1.0f);
  }
}

你可以看到不管屏幕方向如何,布局中的每一个image都排列得都很合适。

要看看完整的FlexboxLayout,请到:

接下来是什么?

需要其它属性构建量为自己身定做的布局请看完整的文档。我们非常欢迎来自你的反馈,如果发现什么问题或者有新的功能需求,请在 GitHub repository上提交issue。