32
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-06-28

FlexboxLayoutとは

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

具体的には、コンテンツの幅に応じて自動で折り返してくれる機能を持つLinearLayoutのようなレイアウトです。
Qiitaで言うフォロー中のタグみたいなレスポンシブルな配置が、FlexboxLayoutを使用すればAndroidでも簡単に作ることができます。
18829f58ab0bb1e247382c97e4bea833.png

使い方

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

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

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

目指すレイアウトはこちらの、日本酒銘柄をひたすら表示するもの。
Screenshot_20190628-111443.png

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の準備だけしておきます。

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

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のデザインです。お好みでカスタムしてください。

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。

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 左から右が主軸となる
デフォルト値
ROW_REVERSE 主軸の方向がROWの逆となる
COLUMN 上から下が主軸となる
COLUMN_REVERSE 主軸の方向がCOLUMNと逆になる

flexWrap

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

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

justifyContent

全体の横方向(主軸方向)の揃え位置を指定します。

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

alignItems

アイテムの縦方向(交差軸方向)の揃え位置を指定します。

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

alignContent

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

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

FlexboxListItem

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

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

FlexboxListAdapter

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

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クラスです。

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

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

余談

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

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

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

参考文献

FlexboxLayout で柔軟なレイアウトを構築
CSS Flexible Box Layout Module

32
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?