Infinito Nirone 7

白羽の矢を刺すスタイル

Jetpack Compose で GridLayout を実現する

Jetpack Compose には LazyVerticalGrid というグリッド表示をしてくれる Composable がありますが、LazyColumn など他のスクロール可能な Composable にネストしてグリッド表示を作りたい場合は LazyVerticalGrid は利用できません(スクロール可能な Composable をネストしてしまうので)。 Android View にある GridLayout の代替となる Composable があればよいのですが、現状はまだないようです *1

そこでなんとか GridLayout をうまく再現する方法として、accompanist にある FlowLayout を利用して実装してみます。 今回は Grid の各セルの横幅を画面幅やセル間のマージンから算出して設定し、GridLayout を横幅いっぱいに表示するものを作ります。あんまり汎用性はないです。

@Composable
fun GridLayout(
  modifier: Modifier = Modifier
  items: List<String>,
) {
  // カラム数
  val numOfColumns = 2
  // セル間のマージン
  val gridSpacing = 8.dp,
  // セル間のマージンを取るための Spacer の数
  val spacerCount = numOfColumns - 1
  val config = LocalConfiguration.current
  val gridPadding = // ... グリッド両脇につける padding の dp
  // 画面の横幅からセル間のマージンとグリッド両脇の padding を取り除き、カラム数で割ってセル1個の横幅を出す
  val cellWidth = (config.screenWidthDp.dp - (gridSpacing * spacerCount) - (gridPadding * 2)) / numOfColumns

  Box(
    modifier = modifier.fillMaxWidth().wrapContentHeight()
  ) {
    FlowRow(
      modifier = Modifier.fillMaxWidth().wrapContentHeight()
    ) {
      // [0, 1, 2, 3, 4] のようなリストを [0, 1], [2, 3], [4] といった形の 2 個要素をもつチャンクに分割し、チャンクを Grid の 1 行分として扱う
      categoryTags.chunked(numOfColumns).forEach { chunkedList ->
        val left = chunkedList[0] // チャンクの最初の要素は必ず存在する
        val right = chunkedList.getOrNull(1) // チャンクの 2 個目の要素は存在しないかもしれない

        Box(
          modifier = Modifier
            .requiredWidth(cellWidth)
            .wrapContentHeight(),
        ) {
          // left を使って UI をつくる
        }

        // セル間のマージンを取る Spacer
        Spacer(modifier = Modifier.width(gridSpacing))

        if (right == null) {
          // 要素はなくてもスペースは確保したい
          Spacer(
            modifier = Modifier
              .requiredWidth(cellWidth)
              .wrapContentHeight()
          )
        } else {
          Box(
            modifier = Modifier
              .requiredWidth(cellWidth)
              .wrapContentHeight(),
          ) {
            // right を使って UI をつくる
          }
        }

        // 最後に、セルの下部にもマージンをつける。横幅いっぱいに Spacer を置いて、次のセルが確実にこの Spacer の下に来るようにする。
        Spacer(
          modifier = Modifier
            .fillMaxWidth()
            .height(gridSpacing)
        )
      }
    }
  }
}

*1:Issue Tracker: https://issuetracker.google.com/issues/190893487 イシューは切られていますが優先度は高くないようです