0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IME追従のscroll方式(試行の記録)

0
Posted at

scroll方式を試行錯誤しながら、
最終形へ持っていった経緯をまとめた“試行の記録”です。
"思考の記録"とも言う:laughing:

関連記事:

LaunchedEffect 単体方式の破綻

差分を Compose 側の値(inset / viewport)で取る。

1. inset

class Ref<T>(var value: T)
val previousInset = remember { Ref(0f) }
val currentInset = WindowInsets.ime.getBottom((LocalDensity.current)).toFloat()
LaunchedEffect(currentInset) {
    val diff = currentInset - previousInset.value
    listState.scrollBy(diff)
    Log.d("",
        "currentInset=${currentInset} " + // IME の高さ
        "previousInset=${previousInset.value} " + // 前回の再コンポーズ時の IME の高さ
        "diff=${diff}" // IME の高さ変化量
    )    
    previousInset.value = currentInset
}

数字も計算も間違っていない(と思う)けど、以下のケースで
アニメーション終了時の位置がなぜか想定位置にならない。

  • LazyColumn末尾でIMEを閉じたとき
  • LazyColumnの途中でIMEを開閉したとき
実行時のログ
LazyColumn末尾でIMEを開閉
currentInset=190.0 previousInset=0.0 diff=190.0
currentInset=473.0 previousInset=190.0 diff=283.0
currentInset=623.0 previousInset=473.0 diff=150.0
currentInset=705.0 previousInset=623.0 diff=82.0
currentInset=759.0 previousInset=705.0 diff=54.0
currentInset=778.0 previousInset=759.0 diff=19.0
currentInset=792.0 previousInset=778.0 diff=14.0
currentInset=811.0 previousInset=792.0 diff=19.0
currentInset=816.0 previousInset=811.0 diff=5.0
currentInset=821.0 previousInset=816.0 diff=5.0

currentInset=785.0 previousInset=821.0 diff=-36.0
currentInset=630.0 previousInset=785.0 diff=-155.0
currentInset=347.0 previousInset=630.0 diff=-283.0
currentInset=262.0 previousInset=347.0 diff=-85.0
currentInset=200.0 previousInset=262.0 diff=-62.0
currentInset=152.0 previousInset=200.0 diff=-48.0
currentInset=86.0 previousInset=152.0 diff=-66.0
currentInset=62.0 previousInset=86.0 diff=-24.0
currentInset=43.0 previousInset=62.0 diff=-19.0
currentInset=28.0 previousInset=43.0 diff=-15.0
currentInset=17.0 previousInset=28.0 diff=-11.0
currentInset=9.0 previousInset=17.0 diff=-8.0
currentInset=4.0 previousInset=9.0 diff=-5.0
currentInset=1.0 previousInset=4.0 diff=-3.0
currentInset=0.0 previousInset=1.0 diff=-1.0

インセットの値は、コンポジション フェーズの後、レイアウト フェーズの前に更新されます。つまり、コンポジションでインセットの値を読み取る場合、通常は 1 フレーム遅れたインセットの値が使用されます。(引用元:Android Developers)

:star: inset はアニメーション中に途中値を返すため、差分方式としては不安定。
そのため、別の手段で差分を作ることにする。

2. viewport

var lastViewportHeight by remember { mutableIntStateOf(0) }
val current = listState.layoutInfo.viewportSize.height
LaunchedEffect(listState.layoutInfo.viewportSize.height) {
    if (lastViewportHeight != 0) {
        val diff = lastViewportHeight - current
        Log.d("before ",
            "viewHeight=${current} " + // 今の viewport の高さ(IME 開閉で変化する)
            "lastViewportHeight=${lastViewportHeight} " + // 前回の viewport の高さ(差分計算の基準)
            "index=${listState.firstVisibleItemIndex} " + // 画面の一番上に見えてるアイテムの番号
            "offset=${listState.firstVisibleItemScrollOffset} " + // その index のアイテムのスクロール位置
            "start=${listState.layoutInfo.viewportStartOffset} " + // viewport の上端(ほぼ 0)
            "end=${listState.layoutInfo.viewportEndOffset} " + // viewport の下端(=viewHeight)
            "diff=${diff} " + // viewport の高さ変化量(IME の開閉量)
            "last_offset=${listState.layoutInfo.visibleItemsInfo.last().offset} " + // 最後のアイテムの表示開始位置
            "last_size=${listState.layoutInfo.visibleItemsInfo.last().size} " // 最後のアイテムの高さ
        )
        listState.scrollBy(diff.toFloat())

        Log.d("after ",
            "viewHeight=${current} " + // 今の viewport の高さ(IME 開閉で変化する)
            "lastViewportHeight=${lastViewportHeight} " + // 前回の viewport の高さ(差分計算の基準)
            "index=${listState.firstVisibleItemIndex} " + // 画面の一番上に見えてるアイテムの番号
            "offset=${listState.firstVisibleItemScrollOffset} " + // その index のアイテムのスクロール位置
            "start=${listState.layoutInfo.viewportStartOffset} " + // viewport の上端(ほぼ 0)
            "end=${listState.layoutInfo.viewportEndOffset} " + // viewport の下端(=viewHeight)
            "diff=${diff} " + // viewport の高さ変化量(IME の開閉量)
            "last_offset=${listState.layoutInfo.visibleItemsInfo.last().offset} " + // 最後のアイテムの表示開始位置
            "last_size=${listState.layoutInfo.visibleItemsInfo.last().size} " // 最後のアイテムの高さ
        )
    }
    lastViewportHeight = current
}

数字も計算も間違っていない(と思う)けど、以下のケースで
アニメーション終了時の位置がなぜか想定位置にならない。

  • LazyColumn末尾でIMEを閉じたとき
実行時のログ
LazyColumn末尾でIMEを開閉
before	viewHeight=1946 lastViewportHeight=1999 index=10 offset=10833 start=0 end=1658 diff=53 last_offset=-10833 last_size=12832
after	viewHeight=1946 lastViewportHeight=1999 index=10 offset=10886 start=0 end=1658 diff=53 last_offset=-10886 last_size=12832
before	viewHeight=1658 lastViewportHeight=1946 index=10 offset=10886 start=0 end=1572 diff=288 last_offset=-10886 last_size=12832
after	viewHeight=1658 lastViewportHeight=1946 index=10 offset=11174 start=0 end=1572 diff=288 last_offset=-11174 last_size=12832
before	viewHeight=1572 lastViewportHeight=1658 index=10 offset=11174 start=0 end=1462 diff=86 last_offset=-11174 last_size=12832
after	viewHeight=1572 lastViewportHeight=1658 index=10 offset=11260 start=0 end=1462 diff=86 last_offset=-11260 last_size=12832
before	viewHeight=1462 lastViewportHeight=1572 index=10 offset=11260 start=0 end=1391 diff=110 last_offset=-11260 last_size=12832
after	viewHeight=1462 lastViewportHeight=1572 index=10 offset=11370 start=0 end=1391 diff=110 last_offset=-11370 last_size=12832
before	viewHeight=1391 lastViewportHeight=1462 index=10 offset=11370 start=0 end=1367 diff=71 last_offset=-11370 last_size=12832
after	viewHeight=1391 lastViewportHeight=1462 index=10 offset=11441 start=0 end=1367 diff=71 last_offset=-11441 last_size=12832
before	viewHeight=1367 lastViewportHeight=1391 index=10 offset=11441 start=0 end=1333 diff=24 last_offset=-11441 last_size=12832
after	viewHeight=1367 lastViewportHeight=1391 index=10 offset=11465 start=0 end=1333 diff=24 last_offset=-11465 last_size=12832
before	viewHeight=1333 lastViewportHeight=1367 index=10 offset=11465 start=0 end=1322 diff=34 last_offset=-11465 last_size=12832
after	viewHeight=1333 lastViewportHeight=1367 index=10 offset=11499 start=0 end=1322 diff=34 last_offset=-11499 last_size=12832
before	viewHeight=1322 lastViewportHeight=1333 index=10 offset=11499 start=0 end=1314 diff=11 last_offset=-11499 last_size=12832
after	viewHeight=1322 lastViewportHeight=1333 index=10 offset=11510 start=0 end=1314 diff=11 last_offset=-11510 last_size=12832
before	viewHeight=1314 lastViewportHeight=1322 index=10 offset=11510 start=0 end=1306 diff=8 last_offset=-11510 last_size=12832
after	viewHeight=1314 lastViewportHeight=1322 index=10 offset=11518 start=0 end=1306 diff=8 last_offset=-11518 last_size=12832
before	viewHeight=1306 lastViewportHeight=1314 index=10 offset=11518 start=0 end=1304 diff=8 last_offset=-11518 last_size=12832
after	viewHeight=1306 lastViewportHeight=1314 index=10 offset=11526 start=0 end=1304 diff=8 last_offset=-11526 last_size=12832
before	viewHeight=1304 lastViewportHeight=1306 index=10 offset=11526 start=0 end=1304 diff=2 last_offset=-11526 last_size=12832
after	viewHeight=1304 lastViewportHeight=1306 index=10 offset=11528 start=0 end=1304 diff=2 last_offset=-11528 last_size=12832
	
before	viewHeight=1340 lastViewportHeight=1304 index=10 offset=11337 start=0 end=1495 diff=-36 last_offset=-11337 last_size=12832
after	viewHeight=1340 lastViewportHeight=1304 index=10 offset=11301 start=0 end=1495 diff=-36 last_offset=-11301 last_size=12832
before	viewHeight=1495 lastViewportHeight=1340 index=10 offset=11054 start=0 end=1778 diff=-155 last_offset=-11054 last_size=12832
after	viewHeight=1495 lastViewportHeight=1340 index=10 offset=10899 start=0 end=1778 diff=-155 last_offset=-10899 last_size=12832
before	viewHeight=1778 lastViewportHeight=1495 index=10 offset=10899 start=0 end=1863 diff=-283 last_offset=-10899 last_size=12832
after	viewHeight=1778 lastViewportHeight=1495 index=10 offset=10616 start=0 end=1863 diff=-283 last_offset=-10616 last_size=12832
before	viewHeight=1863 lastViewportHeight=1778 index=10 offset=10616 start=0 end=1925 diff=-85 last_offset=-10616 last_size=12832
after	viewHeight=1863 lastViewportHeight=1778 index=10 offset=10531 start=0 end=1925 diff=-85 last_offset=-10531 last_size=12832
before	viewHeight=1925 lastViewportHeight=1863 index=10 offset=10531 start=0 end=1973 diff=-62 last_offset=-10531 last_size=12832
after	viewHeight=1925 lastViewportHeight=1863 index=10 offset=10469 start=0 end=1973 diff=-62 last_offset=-10469 last_size=12832
before	viewHeight=1973 lastViewportHeight=1925 index=10 offset=10469 start=0 end=1999 diff=-48 last_offset=-10469 last_size=12832
after	viewHeight=1973 lastViewportHeight=1925 index=10 offset=10421 start=0 end=1999 diff=-48 last_offset=-10421 last_size=12832
before	viewHeight=1999 lastViewportHeight=1973 index=10 offset=10421 start=0 end=1999 diff=-26 last_offset=-10421 last_size=12832
after	viewHeight=1999 lastViewportHeight=1973 index=10 offset=10395 start=0 end=1999 diff=-26 last_offset=-10395 last_size=12832

:star: 計算式も数値も不審な箇所がないのなら、
疑うのはイベント(LaunchedEffect)の発火タイミングしかないじゃない:sob:
というわけで、AndroidViewを使用した、正確なタイミングを取りにいくことにする。

AndroidView + LaunchedEffect 方式の破綻

差分を OS 側イベント(WindowInsetsAnimation)で取る。

3. eventflag

//▼AndroidViewでIME開閉動作開始/終了のイベントを捕まえる
val maxInset = remember { Ref(0f) }
val directionSign = remember { Ref(+1) }
val isStart = remember { Ref(false) }
val isScroll = remember { Ref(false) }
val isEnd = remember { Ref(false) }
val previousFraction = remember { Ref(0f) }
val previousDistance = remember { Ref(0f) }
var distance by remember { mutableFloatStateOf(0f) }
AndroidView(
    modifier = Modifier.Companion
        .fillMaxSize()
        .alpha(0f), // 完全透過(当たり判定なし)
    factory = { context ->
        View(context).apply {
            ViewCompat.setWindowInsetsAnimationCallback(
                this,
                object: WindowInsetsAnimationCompat.Callback(
                    DISPATCH_MODE_CONTINUE_ON_SUBTREE
                ) {
                    override fun onPrepare(
                        animation: WindowInsetsAnimationCompat
                    ) {
                        super.onPrepare(animation)

                        val isVisibleIme = ViewCompat.getRootWindowInsets(this@apply)
                            ?.isVisible(animation.typeMask and WindowInsetsCompat.Type.ime())
                        if (isVisibleIme == false) {
                            directionSign.value = +1
                        } else {
                            directionSign.value = -1
                        }
                        Log.d("onPrepare", "isVisibleIme=${isVisibleIme}")
                    }

                    override fun onStart(
                        animation: WindowInsetsAnimationCompat,
                        bounds: WindowInsetsAnimationCompat.BoundsCompat
                    ): WindowInsetsAnimationCompat.BoundsCompat {
                        if (animation.typeMask and WindowInsetsCompat.Type.ime() != 0) {
                            maxInset.value =
                                (bounds.upperBound.bottom - bounds.lowerBound.bottom).toFloat()
                            distance = maxInset.value * animation.fraction
                            isStart.value = true
                            Log.d("onStart", "distance=${distance}")
                        }
                        return bounds
                    }

                    override fun onProgress(
                        insets: WindowInsetsCompat,
                        runningAnimations: List<WindowInsetsAnimationCompat>
                    ): WindowInsetsCompat {
                        val imeAnimation = runningAnimations.find {
                            it.typeMask and WindowInsetsCompat.Type.ime() != 0
                        }
                        if (imeAnimation != null) {
                            val fraction = imeAnimation.interpolatedFraction
                            val delta = fraction - previousFraction.value
                            if ((delta > 0.02f) or (fraction == 1.0f)) {
                                isScroll.value = true
                                distance =
                                    maxInset.value * fraction
                                previousFraction.value = fraction
                                Log.d("onProgress", "distance=${distance}")
                            }
                        }
                        return insets
                    }

                    // ▼onProgress()直後に呼ばれる(LaunchedEffectよりも早く)
                    override fun onEnd(
                        animation: WindowInsetsAnimationCompat
                    ){
                        if (animation.typeMask and WindowInsetsCompat.Type.ime() != 0) {
                            isEnd.value = true
                            distance = maxInset.value * animation.fraction
                            Log.d("onEnd", "distance=${distance}")
                        }
                    }
                }
            )
        }
    }
)

//▼IMEに追従してLazyColumnのスクロール位置補正
LaunchedEffect(distance) {
    if (isStart.value == true) {
        previousDistance.value = distance
        isStart.value = false
        isScroll.value = false
        previousFraction.value = 0f
        Log.d("isStart", "previousDistance=${previousDistance.value}")
        isEnd.value = false
    }
    if (isScroll.value == true) {
        val tmp =
            listState.scrollBy(directionSign.value * (distance - previousDistance.value))
        Log.d(
            "isScroll",
            "tmp=${tmp}, previousDistance=${previousDistance.value}, distance=${distance}"
        )
        previousDistance.value += (directionSign.value * tmp)
        isScroll.value = false
    }
    if (isEnd.value == true) {
        val tmp = listState.scrollBy(directionSign.value * (distance - previousDistance.value))
        Log.d("isEnd", "tmp=${tmp}, previousDistance=${previousDistance.value}, distance=${distance}")
        previousDistance.value += (directionSign.value * tmp)
        isEnd.value = false
    }
}

数字も計算も間違っていない(と思う)けど、以下のケースで
アニメーション終了時の位置がなぜか想定位置にならない。

  • LazyColumn末尾でIMEを開閉したとき
  • LazyColumnの途中でIMEを開閉したとき
実行時のログ
LazyColumn末尾でIMEを開閉
onPrepare	isVisibleIme=false
onStart		distance=0.0
onProgress	distance=35.733105
onProgress	distance=190.92627
isStart		previousDistance=190.92627
onProgress	distance=479.90775
isScroll	tmp=288.98148, previousDistance=190.92627, distance=479.90775
onProgress	distance=620.3834
isScroll	tmp=140.47568, previousDistance=479.90775, distance=620.3834
onProgress	distance=705.9918
isScroll	tmp=85.6084, previousDistance=620.3834, distance=705.9918
onProgress	distance=735.96844
isScroll	tmp=29.976624, previousDistance=705.9918, distance=735.96844
onProgress	distance=759.7538
isScroll	tmp=23.78534, previousDistance=735.96844, distance=759.7538
onProgress	distance=792.19476
isScroll	tmp=32.44098, previousDistance=759.7538, distance=792.19476
onProgress	distance=811.9358
isScroll	tmp=19.741028, previousDistance=792.19476, distance=811.9358
onProgress	distance=821.0
onEnd		distance=821.0
isScroll	tmp=9.064209, previousDistance=811.9358, distance=821.0
isEnd		tmp=0.0, previousDistance=821.0, distance=821.0
	
onPrepare	isVisibleIme=true
onStart		distance=0.0
isStart		previousDistance=0.0
onProgress	distance=35.733105
isScroll	tmp=-35.733105, previousDistance=0.0, distance=35.733105
onProgress	distance=361.95172
isScroll	tmp=-326.21863, previousDistance=35.733105, distance=361.95172
onProgress	distance=479.90775
isScroll	tmp=-117.956024, previousDistance=361.95172, distance=479.90775
onProgress	distance=558.31
isScroll	tmp=-78.40225, previousDistance=479.90775, distance=558.31
onProgress	distance=668.50354
isScroll	tmp=-110.19354, previousDistance=558.31, distance=668.50354
onProgress	distance=705.9918
isScroll	tmp=-37.48828, previousDistance=668.50354, distance=705.9918
onProgress	distance=735.96844
onProgress	distance=759.7538
isScroll	tmp=-53.761963, previousDistance=705.9918, distance=759.7538
onProgress	distance=777.4389
isScroll	tmp=-17.68512, previousDistance=759.7538, distance=777.4389
onProgress	distance=803.4847
isScroll	tmp=-26.045776, previousDistance=777.4389, distance=803.4847
onProgress	distance=821.0
onEnd		distance=821.0
isScroll	tmp=-17.51532, previousDistance=803.4847, distance=821.0
isEnd		tmp=-0.0, previousDistance=821.0, distance=821.0

:zap: WindowInsetsAnimation のイベント順は OS が決める
 1. onProgress
 2. onEnd
 3. isScroll(LaunchedEffect)
 4. isEnd(LaunchedEffect)
この順番で呼ばれていることは、ログから確定できる。

:zap: maxInset × fraction が端末依存で不安定
maxInset.value = (bounds.upperBound.bottom - bounds.lowerBound.bottom).toFloat()
これは WindowInsetsAnimation の bounds に依存した IME 高さの推定だけど、
bounds は端末・OS・IME の種類で値が変わるため、
実際の IME 高さと一致しないケースが多い。

:star: WindowInsetsAnimationのイベント非同期 と
maxInset×fraction方式の不安定さ から、
eventflag+LaunchedEffect側での最終調整は構造的に破綻している。
補正ロジックはAndroidView側に全倒しするのが安全策。

AndroidView 単体方式

全ての処理を OS 側イベント(WindowInsetsAnimation) で行う。

4. dispatchRawDelta

AndroidView(
    modifier = Modifier.Companion
        .fillMaxSize()
        .alpha(0.0f), // 完全透過(当たり判定なし)
    factory = { context ->
        View(context).apply {
            //▼IMEのアニメーションを拾うためのコールバック
            ViewCompat.setWindowInsetsAnimationCallback(
                this,
                object: WindowInsetsAnimationCompat.Callback(
                    DISPATCH_MODE_CONTINUE_ON_SUBTREE
                ) {
                    override fun onPrepare(
                        animation: WindowInsetsAnimationCompat
                    ) {
                        super.onPrepare(animation)

                        // アニメーション開始前に変数を初期化
                        previousFraction.value = 0.0f
                        previousInset.value = null
                        finalExpectedDelta.value = 0.0f
                    }

                    override fun onProgress(
                        insets: WindowInsetsCompat,
                        runningAnimations: List<WindowInsetsAnimationCompat>
                    ): WindowInsetsCompat {

                        // 今動いているIMEのアニメーションを取得
                        val imeAnimation = runningAnimations.find {
                            it.typeMask and WindowInsetsCompat.Type.ime() != 0
                        }
                        if (imeAnimation != null) {
                            val fraction = imeAnimation.interpolatedFraction
                            val delta = fraction - previousFraction.value

                            // IME+ナビゲーションバーのbottomを取得
                            val currentInset = insets.getInsets(
                                WindowInsetsCompat.Type.ime() or WindowInsetsCompat.Type.navigationBars()
                            ).bottom.toFloat()
                            if (previousInset.value == null) {
                                // 初回は基準値として保持
                                previousInset.value = currentInset
                            } else if ((delta > 0.02f) or (fraction == 1.0f)) {
                                // deltaで間引きながらスクロールを適用
                                val scrollDelta =
                                    listState.dispatchRawDelta(currentInset - previousInset.value!!)
                                Log.d(
                                    "onProgress",
                                    "fraction=${fraction}, scrollDelta=${scrollDelta}, currentInset=${currentInset}, previousInset=${previousInset.value}"
                                )

                                // スクロール適用時点のfractionとinsetを代入
                                previousFraction.value = fraction
                                previousInset.value = previousInset.value!! + scrollDelta
                            }
                            if (fraction == 1.0f) {
                                // アニメーション終了時点での「最終的に必要な差分」を記録
                                finalExpectedDelta.value =
                                    currentInset - previousInset.value!!
                            }
                        }
                        return insets
                    }

                    override fun onEnd(animation: WindowInsetsAnimationCompat) {
                        super.onEnd(animation)

                        // 最終的に残った差分をもう一度適用して位置調整を行う
                        val scrollDelta =
                            listState.dispatchRawDelta(finalExpectedDelta.value)
                        Log.d(
                            "onEnd",
                            "scrollDelta=${scrollDelta}, finalExpectedDelta=${finalExpectedDelta.value}"
                        )
                    }
                }
            )
        }
    }
)

以下のケースで
アニメーション終了時の位置が想定位置になることを確認。

  • LazyColumn末尾でIMEを開閉したとき
  • LazyColumnの途中でIMEを開閉したとき
実行時のログ
LazyColumn末尾でIMEを開閉
onProgress	fraction=0.043523878, scrollDelta=0.0, currentInset=126.0, previousInset=126.0
onProgress	fraction=0.23255332, scrollDelta=-0.0, currentInset=190.0, previousInset=126.0
onProgress	fraction=0.576855, scrollDelta=64.0, currentInset=473.0, previousInset=126.0
onProgress	fraction=0.7556436, scrollDelta=283.0, currentInset=620.0, previousInset=190.0
onProgress	fraction=0.859917, scrollDelta=147.0, currentInset=705.0, previousInset=473.0
onProgress	fraction=0.8964293, scrollDelta=85.0, currentInset=735.0, previousInset=620.0
onProgress	fraction=0.9238281, scrollDelta=30.0, currentInset=758.0, previousInset=705.0
onProgress	fraction=0.96491444, scrollDelta=23.0, currentInset=792.0, previousInset=735.0
onProgress	fraction=0.98895955, scrollDelta=45.0, currentInset=811.0, previousInset=758.0
onProgress	fraction=1.0, scrollDelta=16.0, currentInset=821.0, previousInset=803.0
onEnd		scrollDelta=-0.0, finalExpectedDelta=2.0
	
onProgress	fraction=0.038190294, scrollDelta=-32.0, currentInset=789.0, previousInset=821.0
onProgress	fraction=0.21868287, scrollDelta=-148.0, currentInset=641.0, previousInset=789.0
onProgress	fraction=0.43101135, scrollDelta=-174.0, currentInset=467.0, previousInset=641.0
onProgress	fraction=0.576855, scrollDelta=-120.0, currentInset=347.0, previousInset=467.0
onProgress	fraction=0.7516256, scrollDelta=-144.0, currentInset=203.0, previousInset=347.0
onProgress	fraction=0.8113777, scrollDelta=-49.0, currentInset=154.0, previousInset=203.0
onProgress	fraction=0.8575219, scrollDelta=-28.0, currentInset=126.0, previousInset=154.0
onProgress	fraction=0.89446723, scrollDelta=0.0, currentInset=126.0, previousInset=126.0
onProgress	fraction=0.9238281, scrollDelta=0.0, currentInset=126.0, previousInset=126.0
onProgress	fraction=0.94694144, scrollDelta=0.0, currentInset=126.0, previousInset=126.0
onProgress	fraction=0.978052, scrollDelta=0.0, currentInset=126.0, previousInset=126.0
onProgress	fraction=0.9982162, scrollDelta=0.0, currentInset=126.0, previousInset=126.0
onProgress	fraction=1.0, scrollDelta=0.0, currentInset=126.0, previousInset=126.0
onEnd		scrollDelta=0.0, finalExpectedDelta=0.0

:star: IME アニメーションの途中値・イベント順・発火タイミングの問題を全て回避するため、
最終的には AndroidView 側で IME の実測値を使い、
dispatchRawDelta で直接補正する方式が最も安定する。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?