Jetpack Compose v1.4.0に気になったAPIのまとめ

こんにちは!Jetpack Compose と KMM が好きなエンジニアのスージです。

Jetpack Compose v1.4.0 stable になりました。今回もたくさんのAPIの追加や更新がありますし、その中で個人的に気になった API を紹介しようと思います。Compose チームもまとめ記事を出しましたので、ぜひ読んでみてください。

android-developers.googleblog.com

以前 Jetpack Compose v1.3.0 が stable になった時もまとめ記事を書きましたので、良かったらぜひ読んでみてください。

goodpatch-tech.hatenablog.com

Pager の Accompanist 卒業

Accompanist ライブラリーから卒業して、Compose 1.4.0 でHorizontalPagerVerticalPager が追加されました。

  • Accompanist からマイグレーションのガイドが用意されてますので参考にしてください。
  • インジケーター対応が入っていないため、カスタムインジケータを実装したくない場合 accompanist-pager-indicators:0.29.2-rc が既に androidx.compose.foundation.pager.PagerState をサポートしてます。使い方についてこの記事を参考にしてください。

zenn.dev

val state = rememberPagerState(initialPage = 0)

Column {
  HorizontalPager(pageCount = 4, state = state ..) {
    //.. ページコンテンツ
  }
  
  // Accompanist インジケータ
  // pageCount あるメソッドは androidx.compose.foundation.pager.PagerState をサポートしてます
  HorizontalPagerIndicator(pagerState = state, pageCount = 4 ..)
}

Flowレイアウト

スペースがない場合に改行を入れる用の FlowRowFlowColumn が追加されてます。RowColumn と同様で alignment と arrangement を定義でき、各行のアイテムの最大数も定義できます。

FlowRow(
  modifier = Modifier
    .fillMaxWidth()
    .padding(horizontal = 16.dp),
  horizontalArrangement = Arrangement.spacedBy(8.dp),
  maxItemsInEachRow = 4
) {
  listOf("豚", "姫", "虎", "王", "王子", "お嬢", "ねこ", "ネズミ", "カワウソ").forEach {
    AssistChip(
      onClick = { .. },
      label = { Text(text = it) }
    )
  }
}

marquee アニメーション

marquee アニメーション対応する新しい Modifier.basicMarquee() が追加されてます。スクロール速度、ループ間の余白、アニメーション delay、回数などを調整できます。

Modifier
  .basicMarquee(
    spacing = .., // ループ間のスペース
    velocity = .., // スクロール速度
    iterations = .., // 回数。Int.MAX_VALUE:無限ループ、0:ループしない
    initialDelayMillis = .., // 初回ループ始まるまでの delay
    delayMillis = .. // 次のループ始まるまでの delay
  )

注意点として、コンテンツの利用可能なスペースに収まらない場合のみこのアニメーションが実行されます。収まれたら効かないです。

この Modifier は Text だけでなく、上記の例のようにどのコンポーネントでも付けることができます。

val transition = rememberInfiniteTransition()
val topBannerColor by transition.animateColor(..)

Column {
  Row(
    modifier = Modifier
      .basicMarquee(
        spacing = MarqueeSpacing.fractionOfContainer(0f),
        velocity = 50.dp,
        iterations = Int.MAX_VALUE,
        delayMillis = 0
      )
      .background(color = topBannerColor)
  ) {
    repeat(10) {
        if (it % 2 == 0) {
            Text(text = "・JAPAN ARE CHAMPIONS")
        } else {
            Text(text = "・WBC 2023")
        }
    }
  }

  Text(
    modifier = Modifier
      .basicMarquee(
        iterations = Int.MAX_VALUE,
        spacing = MarqueeSpacing.fractionOfContainer(0.1f),
        delayMillis = 0
      ),
    text = "SAMURAI JAPAN 3 : 2 TEAM USA"
  )
}

minLines サポート

TextTextField で minLines サポートが追加されてます。

TextField(.., minLines = 3)

LazyList の focus バグ修正と PinnableContainer

1.4.0 以降 LazyList で focus 持ちの item が viewport から消えたら focus 外されてしまうバグがあって、今回のリリースで修正が入ってます。

1.4.0 以下の focus バグ1.4.0 で修正が入ってます

修正として PinnableContainer という汎用的なピン留め仕組みが追加されてます。LazyList の場合は、このピン留め仕組みを使って item が viewport から消えても Composition から破棄されないような実装が追加されてます。

これについて調べてみてこの記事でまとめてますので、興味あればぜひ読んでみてください。 goodpatch-tech.hatenablog.com

TextMotion

Text が画面で固定されたとアニメーションされた場合レンダリングを最適化するために新しいTextMotionが追加されてます。

早いアニメーションで差分がわかりにくいんですが、以下のようにアニメーションされる Text に TextMotion.Animated 指定しないと Animation がスムーズにレンダリングされない。

アニメーションされる Text に必ず textMotion = TextMotion.Animated を定義しましょう

val transition = rememberInfiniteTransition()

val fontScale by transition.animateFloat(..)

Text(
  style = TextStyle(
    textMotion = TextMotion.Animated, // デフォルトは TextMotion.Static
    fontSize = 12.sp.times(fontScale)
  ),
  //..
)

AndroidView の View の再利用が可能

View.java 系のビューを Compose化するための AndroidViewonResetonRelease 2つコールバックが新しく追加されてます。

  • onReset: AndroidView が Composition から一時的に破棄や別のところで移動された後、また Composition に追加される時のコールバックです。
  • onRelease:Composition から完全に破棄された(再利用される可能性がない)時のコールバックです。
@Composable
fun <T : View> AndroidView(
    factory: (Context) -> T,
    modifier: Modifier = Modifier,
    onReset: ((T) -> Unit)? = null,
    onRelease: (T) -> Unit = NoOpUpdate,
    update: (T) -> Unit = NoOpUpdate
)

LazyList のような再利用サポートしてるレイアウトで、この onReset を実装すれば AndroidView が wrap してる View が再利用されます。注意点としては、onReset が null になった場合、 View が再利用されないです

サンプル実装も用意されてます。

@Composable
fun ReusableAndroidViewInLazyColumnSample() {
  LazyVerticalGrid(columns = GridCells.Adaptive(512.dp)) {
    items(urls) { url ->
      AndroidView(
        factory = { context ->
          WebView(context)
          //..
        },
        onReset = { webView ->
            // [1]
            webView.stopLoading()
            webView.loadUrl("about:blank")
            webView.clearHistory()
        },
        update = { 
            // [2]
            webView.loadUrl(url) 
        }
      )
    }
  }
  //..
}
  1. onReset で再利用される WebView インスタンスをupdate が呼び出させる前にリセットされてます(url を about:blank と履歴を削除)。
  2. 次呼び出せる update で url がまたロードされてます。

Compose node のライフサイクル観察

1.4.0 で新しい ComposeNodeLifecycleCallback という Compose node のライフサイクルを観察できるような interface が追加されました。

これを使って上記の2つのコールバックが実装されました。内部的な実装について以下のスレッドでまとめてみましたので良ければ読んでみてください。

上記のツイートは正式リリース前の調査なため、一部間違っている箇所があります 🙇‍♂️

  • 「スクロールパフォーマンスの向上対応が入ってます」ではなく、View を再利用してパフォーマンス向上するように実装することもできるようになりました。

Modifier.Node 化の対応

去年の Android Dev Summit で Compose UI のパフォーマンス向上のため Modifier.Node が紹介されました。この API Modifier.Node について少し解釈してみた記事書いたことあるので良かったら読んでみてください。

goodpatch-tech.hatenablog.com

v1.4.0 で focuspaddingsizedrawWithCache などいくつかの Modifier が Modifier.Node 化がされてますので、パフォーマンス向上のために 1.4.0 にアップデートして方がいいと思ってます。API は変わっておらず内部的な実装が変わっているので、バージョンアップすれば大丈夫と思います。

AOSP の Modifier.Node 化の pull request も参考になるかもしれないのでぜひチェックしてみてください。

https://android-review.googlesource.com/q/Modifier.Node

Material 3

Material Design 3 のライブラリーは 1.4.0 ではないんですが、1.3.0 のリリースからいくつかコンポーネント追加されてます。

BottomSheet

Compose Material Design 3 ライブラリの未対応だった bottom sheet コンポーネントが追加されてます。Material Design 2 と 3 で bottom sheet の使い方についてこの記事で簡単にまとめられているので参考にしてください。 www.composables.co

Mobile 勉強会で発表してくれた @RyuNen344さんの資料も参考になります。

Tooltips

Material Design 3 のコンポーネント一覧でまだ Tool tips が入ってないです。

Plain TooltipRich Tooltip

Time picker と Date picker

Time picker Date picker Date range picker

m3.material.io

m3.material.io

終わりに

他にも、ここに書いてない 1.4.0 のAPI追加と更新は沢山あります。次は、5月 Google I/O あるので Compose 含めて Android 開発周りの色んな発表楽しみですね!

io.google