【Jetpack Compose】脱 Tv Lazy Layout --- アニメーション編
本記事の内容
Android TV 向けのライブラリである tv-foundation 1.0.0-alpha11 にて、遂に Tv Lazy Layout が非推奨 になりました。
Tv Lazy Layout の引数である PivotOffsets
に関して、デフォルト値を使用している場合は単に Tv
のプレフィックスを外すだけで問題ないです。
しかし、デフォルトとは異なる値を設定している場合は、CompositionLocal を利用して BringIntoViewSpec
インスタンスを配布する実装に変更する必要があります。
(詳しい内容は 移行前後の差分編 を参照してください🙏)
本記事ではこの Tv Lazy Layout から Lazy Layout の移行中にハマったポイントのうち、アニメーションに関する内容を記載します。
パート | 内容 |
---|---|
Part.1 | 移行前後の差分編 |
Part.2 | アニメーション編 |
Part.3 | テスト編 |
スクロールアニメーションの無効化
まず紹介するのはスクロールアニメーションの無効化です。
スクロールアニメーションを無効化しておくと、フォーカス移動に伴うスクロール処理は一切実行されなくなります。
スクロールアニメーションが有効 | スクロールアニメーションが無効 |
---|---|
利用用途に困りそうなこの機能ですが、初期フォーカスと組み合わせた使い方ができそうです。
初期フォーカスを当てた際にも BringIntoViewSpec
によるスクロール処理が実行されてしまいます。
そのため、スクロールアニメーションが常時有効になっていると以下二つの仕様を同時に満たせられません。
- ファーストビューにおけるスクロール位置は最上部とする
- 2個目以降の item に初期フォーカスを当てる
初期フォーカスを当てるタイミングだけスクロールアニメーションを無効化させられると、以下の動画のようにこれら二つの仕様を同時に満たせられます。
初期フォーカス (スクロールアニメーションが有効) |
初期フォーカス (スクロールアニメーションが無効) |
---|---|
少し前置きが長くなってしまいましたが、ここで述べたスクロールアニメーションにおける有効状態の設定方法が Tv Lazy Layout と Lazy Layout で異なります😵
【Tv Lazy Layout】 userScrollEnabled
移行前の Tv Lazy Layout での設定方法を見てみます。
まずは TvLazyColumn
の引数[1]を確認してみましょう。
@Composable
fun TvLazyColumn(
modifier: Modifier = Modifier,
state: TvLazyListState = rememberTvLazyListState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
verticalArrangement: Arrangement.Vertical =
if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
userScrollEnabled: Boolean = true, // 怪しい👀
pivotOffsets: PivotOffsets = PivotOffsets(),
content: TvLazyListScope.() -> Unit
) { ... }
userScrollEnabled
という如何にもな名前の Boolean 型引数がありますね。
コードコメントに記載してある引数の説明からも、スクロールアニメーションに関係したパラメータであることが伺えます。
whether the scrolling via the user gestures or accessibility actions is allowed. You can still scroll programmatically using the state even when it is disabled.
実際にこの引数の真偽値を変更すると、スクロールアニメーションにおける有効状態も変更できました🙌
userScrollEnabled == true | userScrollEnabled == false |
---|---|
【Lazy Layout】 BringIntoViewSpec#calculateScrollDistance
移行後の Lazy Layout でもスクロールアニメーションの無効化に挑戦してみます。
引数のパラメータを変更してみる
まずは Tv Lazy Layout と同様に設定できないか探るため、LazyColumn
の引数[2]を見てみます。
@Composable
fun LazyColumn(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
verticalArrangement: Arrangement.Vertical =
if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true, // 怪しい👀
content: LazyListScope.() -> Unit
) { ... }
こちらにも userScrollEnabled
がありますね。
コードコメントに記載してある引数の説明も TvLazyColumn
と同一です。
whether the scrolling via the user gestures or accessibility actions is allowed. You can still scroll programmatically using the state even when it is disabled
これなら TvLazyColumn
と同様に、userScrollEnabled
でスクロールアニメーションにおける有効状態を設定できそうです。
実際にパラメーターを変えた際の挙動差を以下に示します。
userScrollEnabled == true | userScrollEnabled == false |
---|---|
userScrollEnabled = false
を設定してもフツーにスクロールしちゃってますね🤯
ここがハマりポイントで、LazyColumn
の引数である userScrollEnabled
ではスクロールアニメーションにおける有効状態を設定できません😱
テレビデバイスのフォーカス移動に伴うスクロールは BringIntoViewSpec
が制御しているのですが、この BringIntoViewSpec
は引数の userScrollEnabled
を参照していない というのが原因です。
BringIntoViewSpec#calculateScrollDistance での制御
Tv Lazy Layout では、引数の userScrollEnabled
が TvBringIntoViewSpec
のコンストラクタに渡されます[1:1]。
@OptIn(ExperimentalFoundationApi::class)
private class TvBringIntoViewSpec(
val pivotOffsets: PivotOffsets,
val userScrollEnabled: Boolean
) : BringIntoViewSpec {
// アイテムにフォーカスが当たった際に生じるスクロールのアニメーションを設定する
override val scrollAnimationSpec: AnimationSpec<Float> = tween<Float>(...)
// アイテムにフォーカスが当たった際に生じるスクロール量を返す
override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float {
if (!userScrollEnabled) return 0f // ここでスクロールアニメーションを無効化
...
}
override fun hashCode(): Int { ... }
override fun equals(other: Any?): Boolean { ... }
}
そしてその後は calculateScrollDistance
メソッドから userScrollEnabled
が参照されるようになっています。
マイグレーションガイド にこのような実装が無いため見落としがちですが、BringIntoViewSpec
インスタンスは userScrollEnabled
も受け取れるようになっていると良さそうです👌
実際に BringIntoViewSpec#calculateScrollDistance
で 0f を返却させるとスクロールアニメーションが無効化されました。
デフォルトの calculateScrollDistance
|
calculateScrollDistance が 0f を return |
---|---|
まとめ
Tv Lazy Layout から Lazy Layout への移行にあたって、アニメーションロジックは少なからず変更が入っています。
今回の記事ではスクロールアニメーションにおける有効状態を変更する方法の差分について記載しました。
フォーカス移動に伴うスクロールは BringIntoViewSpec
が担っているため、スクロールを無効化する場合のロジックも BringIntoViewSpec
に持たせる必要があります。
実はもう一つハマったポイントがあり、記載しようと思ったのですが、サンプルアプリでは再現しませんでした。
(また再現できたら追記するか、新たに投稿するかで対応しようと思います)
このような、特定の状況限定のデグレも潜んでいる可能性があるので、引き続き多様なケースを想定しながらデバッグしていけると良さそうです。
Discussion