幅が足りない時に自動で折り返すレイアウトを探してたら、FlexboxLayoutがドンピシャだった


# FlexboxLayoutとは
[CSS Flexible Box Layout Module](https://www.w3.org/TR/css-flexbox-1/) と同等の機能がAndroidでも使えるようになったレイアウトです。
GoogleによりOSS化されているライブラリとなっています。
https://github.com/google/flexbox-layout

具体的には、コンテンツの幅に応じて**自動で折り返してくれる機能を持つLinearLayout**のようなレイアウトです。
Qiitaで言う**フォロー中のタグ**みたいなレスポンシブルな配置が、FlexboxLayoutを使用すればAndroidでも簡単に作ることができます。
![18829f58ab0bb1e247382c97e4bea833.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235269/f2f35cf6-1672-462c-5fac-80000c4c571f.png)


# 使い方
以下の2種類から選ぶことができます。

- XMLでViewGroupとして静的に定義
- RecyclerViewのLayoutManagerに動的に適用

ここではRecyclerViewとの併用について紹介していきます。



目指すレイアウトはこちらの、日本酒銘柄をひたすら表示するもの。
![Screenshot_20190628-111443.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235269/19ff390c-9c1b-cebc-3d19-04a79e268a1e.png)


## Gradle
gradleの記述は以下の通り

```gradle:build.gradle
dependencies {
    // AndroidXに移行済みの場合
    implementation 'com.google.android:flexbox:1.1.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha06'

    // AndroidXに移行していなければこっちで
//    implementation 'com.google.android:flexbox:1.0.0'
//    implementation 'com.android.support:recyclerview-v7:28.1.1'

}
```

flexboxのバージョンはAndroidXに移行していれば1.1.0、そうでなければ1.0.0を選択します。
追記後、gradleを同期しておきましょう。


## レイアウトファイル
Activityの方は、特に変わったことはせずRecyclerViewの準備だけしておきます。

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:id="@+id/recycler_view"/>

</LinearLayout>
```

---

RecyclerViewのitemファイルには、連続して表示させたい項目を定義しておきます。
一行に複数表示させたい場合でも、ここで複数の項目を定義する必要はありません。

```xml:list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

    <TextView
            android:id="@+id/item_name"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            android:layout_marginStart="4dp"
            android:layout_marginEnd="4dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="8dp"
            android:textSize="16sp"
            android:background="@drawable/item_background"/>

</LinearLayout>
```

親レイアウトのwidthやheightに"match_parent"を指定すると、アイテムが1つしか表示されなくなるため注意。

---

表示するTextViewのデザインです。お好みでカスタムしてください。

```xml:item_background.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">
    <solid android:color="#faebd7"/>
    <stroke
            android:width="1dp"
            android:color="#000000"/>
    <padding
            android:left="8dp"
            android:top="8dp"
            android:right="8dp"
            android:bottom="8dp"/>
    <corners android:radius="3dp"/>
</shape>


```

## MainActivity
リストを表示するActivity。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val items = ArrayList<FlexboxListItem>()
    private val adapter = FlexboxListAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val flexboxLayoutManager = FlexboxLayoutManager(this)
        // 配置方向を指定
        flexboxLayoutManager.flexDirection = FlexDirection.ROW
        // 折り返し方法を指定
        flexboxLayoutManager.flexWrap = FlexWrap.WRAP
        // 主軸方向の揃え位置を指定
        flexboxLayoutManager.justifyContent = JustifyContent.FLEX_START
        // 交差軸方向の揃え位置を指定
        flexboxLayoutManager.alignItems = AlignItems.STRETCH

        // RecyclerViewのLayoutManagerに、カスタムしたFlexboxLayoutManagerを指定
        recycler_view.layoutManager = flexboxLayoutManager
        recycler_view.adapter = adapter
        setupLocalItems()
    }

    private fun setupLocalItems() {

        val testItems = arrayOf(
            "十四代", "花陽浴", "而今", "No.6", "陽乃鳥", "花邑", "信州亀齢", "川中島 幻舞 ",
            "ソガペールエフィス", "飛露喜", "楽器正宗", "鳳凰美田", "亀泉", "写楽",
            "くどき上手", "新政", "醸し人九平次", "加茂錦", "赤武", "菊鷹", "風の森",
            "作", "澤屋まつもと", "王祿", "山間", "鍋島", "町田酒造", "農口尚彦研究所"
        )
        testItems.map { items.add(FlexboxListItem(it)) }
        adapter.setItems(items)
    }

}
```

ポイントは、RecyclerViewのLayoutManagerにカスタムしたFlexboxLayoutManagerを設定することです。
パラメータがいくつかあるので必要に応じて設定しましょう。
パラメータの概要は以下の通りとなっています。

### flexDirection
アイテムの配置方向を指定します。

|値|説明|
| --- | ---|
|ROW|左から右が主軸となる<br>_**デフォルト値**_|
|ROW_REVERSE|主軸の方向がROWの逆となる|
|COLUMN|上から下が主軸となる|
|COLUMN_REVERSE|主軸の方向がCOLUMNと逆になる|

### flexWrap
アイテムの折り返し方法を指定します。
WRAP_REVERSEはFlexboxLayoutManagerではサポートされていないため、RecyclerViewと併用すると**UnsupportedOperationException**が発生します。諦めてWRAPを使いましょう。

|値|説明|
| --- | ---|
|NOWRAP|折り返しなし<br>_**デフォルト値**_|
|WRAP|折り返す|
|WRAP_REVERSE|下から上に積み上がるようになり、逆方向に折り返す。<br>XMLのFlexboxLayoutでのみサポート|

### justifyContent
全体の横方向（主軸方向）の揃え位置を指定します。

|値|説明|
| --- | ---|
|FLEX_START|先頭に寄せる<br>_**デフォルト値**_|
|FLEX_END|末尾に寄せる|
|CENTER|中央に寄せる|
|SPACE_BETWEEN|アイテムを均等に配置して、最初のアイテムは先頭に、最後のアイテムは末尾に寄せる|
|SPACE_AROUND|アイテムを均等に配置し、各アイテムの両側に半分の大きさの間隔をおく|
|SPACE_EVENLY|定数は存在しているが未対応らしい|

### alignItems
アイテムの縦方向（交差軸方向）の揃え位置を指定します。

|値|説明|
| --- | ---|
|STRETCH|伸ばして揃える<br>_**デフォルト値**_|
|FLEX_START|先頭に揃える|
|FLEX_END|末尾に揃える|
|CENTER|中央に揃える|
|BASELINE|ベースラインに揃える|

### alignContent
全体の縦方向（交差軸方向）の揃え位置を指定します。
**FlexboxLayoutManagerではサポートされていない**ため、XMLでのみ使用することが可能です。

|値|説明|
| --- | ---|
|STRETCH|伸ばして揃える<br>_**デフォルト値**_|
|FLEX_START|開始位置に揃える|
|FLEX_END|終了位置に揃える|
|CENTER|中央に揃える|
|SPACE_BETWEEN|アイテムを均等に配置し、最初のアイテムは先頭に、最後のアイテムは末尾に寄せる|
|SPACE_AROUND|アイテムを均等に配置し、各アイテムの両側に半分の大きさの間隔をおく|

## FlexboxListItem

表示するアイテムのただのデータクラスです。

```kotlin:FlexboxListItem.kt
data class FlexboxListItem(val name: String)
```

## FlexboxListAdapter

RecyclerViewのAdapterです。
アイテムの追加処理はAdapterに持たせます。

```kotlin:FlexboxListAdapter.kt
class FlexboxListAdapter : RecyclerView.Adapter<FlexboxListViewHolder>() {

    private val items = ArrayList<FlexboxListItem>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FlexboxListViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
        return FlexboxListViewHolder(view)
    }

    override fun getItemCount(): Int {
        return items.size
    }

    override fun onBindViewHolder(holder: FlexboxListViewHolder, position: Int) {
        val item = items[position]
        holder.itemName.text = item.name
    }

    fun setItems(items: List<FlexboxListItem>) {
        this.items.clear()
        this.items.addAll(items)
        notifyDataSetChanged()
    }
}
```

## FlexboxListViewHolder
Viewの参照を保存するViewHolderクラスです。

```kotlin:FlexboxListViewHolder.kt
class FlexboxListViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val itemName: TextView = view.findViewById(R.id.item_name)
}
```

以上、これらを用意することで折り返しレイアウトを簡単に実装することができました。

# 余談
いざ、この折り返しレイアウトを使いたいと思い探してみると見つけるまでに少し時間がかかりました。
CSSに精通していなかったため、なかなかFlexboxに辿り着くことができずに「レンガ layout view」みたいな間抜けな検索をした記憶があります。

同じようなレイアウトを目指してたこちらの方の記事を読んで、これゴリゴリ実装するのか…と覚悟を決めたりもしました。
[Google検索のサジェストみたいなViewを作りたい](https://qiita.com/ryo_mm2d/items/a39d59657a6d98073edf)

FlexboxLayoutのライブラリ自体はどうやら2016年頃にはOSS化されていたようですが、あまり知名度は高くないように思えます。
割とよく見るレイアウトかと思いますので、どなたかのお役に立てば幸いです。


# 参考文献
[FlexboxLayout で柔軟なレイアウトを構築](https://developers-jp.googleblog.com/2017/03/build-flexible-layouts-with.html
)
[CSS Flexible Box Layout Module](https://www.w3.org/TR/css-flexbox-1/)