Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Master of NestedScroll

Swimmy
September 13, 2023
7.7k

Master of NestedScroll

DroidKaigi 2023での登壇資料になります。
https://2023.droidkaigi.jp/timetable/494286/

記事にも書き起こしました
https://developers.cyberagent.co.jp/blog/archives/45126/

Swimmy

September 13, 2023
Tweet

More Decks by Swimmy

Transcript

  1. JetpackCompose時代における解決策 Modifierで簡単にスクロール可能にできる // ScrollViewのように振る舞う val scrollState = rememberScrollState() Column(modifier =

    Modifier.verticalScroll(scrollState)) { ... } // offsetしないので注意 Column( modifier = Modifier.scrollable( orientation = Vertical, state = rememberScrollState { delta -> delta }, ) ) { ... }
  2. JetpackCompose時代における解決策 スクロール可能なコンポーネントにできる // ScrollViewのように振る舞う val scrollState = rememberScrollState() Column(modifier =

    Modifier.verticalScroll(scrollState)) { ... } // offsetしないので注意 Column( modifier = Modifier.scrollable( orientation = Vertical, state = rememberScrollState { delta -> delta }, ) ) { ... }
  3. JetpackCompose時代における解決策 スクロール可能なコンポーネントにできる // ScrollViewのように振る舞う val scrollState = rememberScrollState() Column(modifier =

    Modifier.verticalScroll(scrollState)) { ... } // offsetしないので注意 Column( modifier = Modifier.scrollable( orientation = Vertical, state = rememberScrollState { delta -> delta }, ) ) { ... }
  4. JetpackCompose時代における解決策 TopAppBarでCollapsing対応が可能 @ExperimentalMaterial3Api @Composable fun TopAppBar( title: @Composable () ->

    Unit, modifier: Modifier = Modifier, navigationIcon: @Composable () -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), scrollBehavior: TopAppBarScrollBehavior? = null )
  5. JetpackCompose時代における解決策 TopAppBarでCollapsing対応が可能 @ExperimentalMaterial3Api @Composable fun TopAppBar( title: @Composable () ->

    Unit, modifier: Modifier = Modifier, navigationIcon: @Composable () -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), scrollBehavior: TopAppBarScrollBehavior? = null )
  6. JetpackCompose時代における解決策 親のModifier#nestedScrollとTopAppBarに適用する @Composable fun EnterAlwaysTopAppBar() { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()

    Scaffold( modifier = Modifier.nestedScroll(behavior.nestedScrollConnection), topAppBar = { TopAppBar( ... scrollBehavior = scrollBehavior, ) }, ...
  7. JetpackCompose時代における解決策 親のModifier#nestedScrollとTopAppBarに適用する @Composable fun EnterAlwaysTopAppBar() { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()

    Scaffold( modifier = Modifier.nestedScroll(behavior.nestedScrollConnection), topAppBar = { TopAppBar( ... scrollBehavior = scrollBehavior, ) }, ...
  8. JetpackCompose時代における解決策 親のModifier#nestedScrollとTopAppBarに適用する @Composable fun EnterAlwaysTopAppBar() { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()

    Scaffold( modifier = Modifier.nestedScroll(behavior.nestedScrollConnection), topAppBar = { TopAppBar( ... scrollBehavior = scrollBehavior, ) }, ...
  9. JetpackCompose時代における解決策 親のModifier#nestedScrollとTopAppBarに適用する @Composable fun EnterAlwaysTopAppBar() { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()

    Scaffold( modifier = Modifier.nestedScroll(behavior.nestedScrollConnection), topAppBar = { TopAppBar( ... scrollBehavior = scrollBehavior, ) }, ...
  10. スクロール動作をカスタムできる JetpackCompose時代における解決策 // 子のネストスクロールに参加できるようになる fun Modifier.nestedScroll( connection: NestedScrollConnection, dispatcher: NestedScrollDispatcher?

    = null ): Modifier // スクロール消費量などの伝播を制御することが可能 interface NestedScrollConnection // ネストスクロールシステムに消費量などを送る class NestedScrollDispatcher
  11. 子のスクロールに参加する JetpackCompose時代における解決策 // 子のネストスクロールに参加できるようになる fun Modifier.nestedScroll( connection: NestedScrollConnection, dispatcher: NestedScrollDispatcher?

    = null ): Modifier // スクロール消費量などの伝播を制御することが可能 interface NestedScrollConnection // 親のスクロールシステムに消費量などを通知 class NestedScrollDispatcher
  12. スクロールの消費量を取得する JetpackCompose時代における解決策 // 子のネストスクロールに参加できるようになる fun Modifier.nestedScroll( connection: NestedScrollConnection, dispatcher: NestedScrollDispatcher?

    = null ): Modifier // スクロール消費量などの伝播を制御することが可能 interface NestedScrollConnection // 親のスクロールシステムに消費量などを通知 class NestedScrollDispatcher
  13. 子のスクロール消費量などを親に送る JetpackCompose時代における解決策 // 子のネストスクロールに参加できるようになる fun Modifier.nestedScroll( connection: NestedScrollConnection, dispatcher: NestedScrollDispatcher?

    = null ): Modifier // スクロール消費量などの伝播を制御することが可能 interface NestedScrollConnection // 親のスクロールシステムに消費量などを通知 class NestedScrollDispatcher
  14. JetpackCompose時代における解決策 NestedScrollConnectionの内部実装 // 引数など簡略化してます // returnした値は親のcomposable関数が吸収する interface NestedScrollConnection { fun

    onPreScroll(...): Offset = Offset.Zero fun onPostScroll(...):Offset = Offset.Zero suspend fun onPreFling(...): Velocity = Velocity.Zero suspend fun onPostFling(...): Velocity = Velocity.Zero }
  15. JetpackCompose時代における解決策 NestedScrollConnectionの内部実装 // 引数など簡略化してます // returnした値は親のcomposable関数が吸収する interface NestedScrollConnection { fun

    onPreScroll(...): Offset = Offset.Zero fun onPostScroll(...):Offset = Offset.Zero suspend fun onPreFling(...): Velocity = Velocity.Zero suspend fun onPostFling(...): Velocity = Velocity.Zero }
  16. JetpackCompose時代における解決策 NestedScrollConnectionの内部実装 // 引数など簡略化してます // returnした値は親のcomposable関数が吸収する interface NestedScrollConnection { fun

    onPreScroll(...): Offset = Offset.Zero fun onPostScroll(...):Offset = Offset.Zero suspend fun onPreFling(...): Velocity = Velocity.Zero suspend fun onPostFling(...): Velocity = Velocity.Zero }
  17. JetpackCompose時代における解決策 ① NestedScrollConnection #onPreScroll // スクロール前に消費量を制御することが可能 // availableは消費量, NestedScrollSourceはスクロール操作の種別 //

    活用事例: 縦スクロールの端に到達するまで伝播させたくない場合 fun onPreScroll(available:Offset,source:NestedScrollSource): Offset = Offset.Zero // スクロールの端に達している時にFABを表示させるケース isVisible = (available.y == 0 && source == NestedScrollSource.Drag)
  18. JetpackCompose時代における解決策 ① NestedScrollConnection #onPreScroll // スクロール前に消費量を制御することが可能 // availableは消費量, NestedScrollSourceはスクロール操作の種別 //

    活用事例: 縦スクロールの端に到達するまで伝播させたくない場合 fun onPreScroll(available:Offset,source:NestedScrollSource): Offset = Offset.Zero // スクロールの端に達している時にFABを表示させるケース isVisible = (available.y == 0 && source == NestedScrollSource.Drag)
  19. JetpackCompose時代における解決策 ① NestedScrollConnection #onPreScroll // スクロール前に消費量を制御することが可能 // availableは消費量, NestedScrollSourceはスクロール操作の種別 //

    活用事例: 縦スクロールの端に到達するまで伝播させたくない場合 fun onPreScroll(available:Offset,source:NestedScrollSource): Offset = Offset.Zero // スクロールの端に達している時にFABを表示させるケース isVisible = (available.y == 0 && source == NestedScrollSource.Drag)
  20. JetpackCompose時代における解決策 ① NestedScrollConnection #onPreScroll // スクロール前に消費量を制御することが可能 // availableは消費量, NestedScrollSourceはスクロール操作の種別 //

    活用事例: 縦スクロールの端に到達するまで伝播させたくない場合 fun onPreScroll(available:Offset,source:NestedScrollSource): Offset = Offset.Zero // スクロールの端に達している時にFABを表示させるケース isVisible = (available.y == 0 && source == NestedScrollSource.Drag)
  21. JetpackCompose時代における解決策 ① NestedScrollConnection #onPreScroll // スクロール前に消費量を制御することが可能 // availableは消費量, NestedScrollSourceはスクロール操作の種別 //

    活用事例: 縦スクロールの端に到達するまで伝播させたくない場合 fun onPreScroll(available:Offset,source:NestedScrollSource): Offset = Offset.Zero // スクロールの端に達している時にFABを表示させるケース isVisible = (available.y == 0 && source == NestedScrollSource.Drag)
  22. JetpackCompose時代における解決策 ① NestedScrollConnection #onPreScroll // スクロール前に消費量を制御することが可能 // availableは消費量, NestedScrollSourceはスクロール操作の種別 //

    活用事例: 縦スクロールの端に到達するまで伝播させたくない場合 fun onPreScroll(available:Offset,source:NestedScrollSource): Offset = Offset.Zero // スクロールの端に達している時にFABを表示させるケース isVisible = (available.y == 0 && source == NestedScrollSource.Drag)
  23. JetpackCompose時代における解決策 NestedScrollConnectionの内部実装 // 引数など簡略化してます // returnした値は親のcomposable関数が吸収する interface NestedScrollConnection { fun

    onPreScroll(...): Offset = Offset.Zero fun onPostScroll(...):Offset = Offset.Zero suspend fun onPreFling(...): Velocity = Velocity.Zero suspend fun onPostFling(...): Velocity = Velocity.Zero }
  24. JetpackCompose時代における解決策 ② NestedScrollConnection #onPostScroll // スクロール 後に消費されなかったスクロール量をどうするか決められる // consumedは消費量, availableは消費されなかったスクロール量

    fun onPostScroll( consumed: Offset, available:Offset, source:NestedScrollSource, ): Offset = Offset.Zero // 伝播させず自身でスクロール量を吸収する ... return available
  25. JetpackCompose時代における解決策 ② NestedScrollConnection #onPostScroll // スクロール後に消費されなかったスクロール量をどうするか決められる // consumedは消費量, availableは消費されなかったスクロール量 fun

    onPostScroll( consumed: Offset, available:Offset, source:NestedScrollSource, ): Offset = Offset.Zero // 伝播させず自身でスクロール量を吸収する ... return available
  26. JetpackCompose時代における解決策 ② NestedScrollConnection #onPostScroll // スクロール 後に消費されなかったスクロール量をどうするか決められる // consumedは消費量, availableは消費されなかったスクロール量

    fun onPostScroll( consumed: Offset, available:Offset, source:NestedScrollSource, ): Offset = Offset.Zero // 伝播させず自身でスクロール量を吸収する ... return available
  27. JetpackCompose時代における解決策 ② NestedScrollConnection #onPostScroll // スクロール 後に消費されなかったスクロール量をどうするか決められる // consumedは消費量, availableは消費されなかったスクロール量

    fun onPostScroll( consumed: Offset, available:Offset, source:NestedScrollSource, ): Offset = Offset.Zero // 伝播させず自身でスクロール量を吸収する ... return available
  28. JetpackCompose時代における解決策 ② NestedScrollConnection #onPostScroll // スクロール 後に消費されなかったスクロール量をどうするか決められる // consumedは消費量, availableは消費されなかったスクロール量

    fun onPostScroll( consumed: Offset, available:Offset, source:NestedScrollSource, ): Offset = Offset.Zero // 伝播させず自身でスクロール量を吸収する ... return available
  29. JetpackCompose時代における解決策 ② NestedScrollConnection #onPostScroll // スクロール 後に消費されなかったスクロール量をどうするか決められる // consumedは消費量, availableは消費されなかったスクロール量

    fun onPostScroll( consumed: Offset, available:Offset, source:NestedScrollSource, ): Offset = Offset.Zero // 伝播させず自身でスクロール量を吸収する ... return available
  30. JetpackCompose時代における解決策 NestedScrollConnectionの内部実装 // 引数など簡略化してます // returnした値は親のcomposable関数が吸収する interface NestedScrollConnection { fun

    onPreScroll(...): Offset = Offset.Zero fun onPostScroll(...):Offset = Offset.Zero suspend fun onPreFling(...): Velocity = Velocity.Zero suspend fun onPostFling(...): Velocity = Velocity.Zero }
  31. JetpackCompose時代における解決策 ③ NestedScrollConnection #onPreFling // スクロールで指を離した時の慣性速度 // availableは発生した慣性速度 fun onPreFling(consumed:

    Velocity): Velocity = Velocity.Zero // 縦にFlingさせないようにする(慎重に閲覧できる状態) fun onPreFling(consumed: Velocity): Velocity = Velocity(0F, consumed.y)
  32. JetpackCompose時代における解決策 ③ NestedScrollConnection #onPreFling // スクロールで指を離した時の慣性速度 // availableは発生した慣性速度 fun onPreFling(consumed:

    Velocity): Velocity = Velocity.Zero // 縦にFlingさせないようにする(慎重に閲覧できる状態) fun onPreFling(consumed: Velocity): Velocity = Velocity(0F, consumed.y)
  33. JetpackCompose時代における解決策 ③ NestedScrollConnection #onPreFling // スクロールで指を離した時の慣性速度 // availableは発生した慣性速度 fun onPreFling(available:

    Velocity): Velocity = Velocity.Zero // 縦にFlingさせないようにする(慎重に閲覧できる状態) fun onPreFling(consumed: Velocity): Velocity = Velocity(0F, consumed.y)
  34. JetpackCompose時代における解決策 ③ NestedScrollConnection #onPreFling // スクロールで指を離した時の慣性速度 // availableは発生した慣性速度 fun onPreFling(available:

    Velocity): Velocity = Velocity.Zero // 縦にFlingさせないようにする(慎重に閲覧できる状態) fun onPreFling(consumed: Velocity): Velocity = Velocity(0F, consumed.y)
  35. JetpackCompose時代における解決策 ③ NestedScrollConnection #onPreFling // スクロールで指を離した時の慣性速度 // availableは発生した慣性速度 fun onPreFling(available:

    Velocity): Velocity = Velocity.Zero // 縦にFlingさせないようにする(慎重に閲覧できる状態) fun onPreFling(consumed: Velocity): Velocity = Velocity(0F, consumed.y)
  36. JetpackCompose時代における解決策 NestedScrollConnectionの内部実装 // 引数など簡略化してます // returnした値は親のcomposable関数が吸収する interface NestedScrollConnection { fun

    onPreScroll(...): Offset = Offset.Zero fun onPostScroll(...):Offset = Offset.Zero suspend fun onPreFling(...): Velocity = Velocity.Zero suspend fun onPostFling(...): Velocity = Velocity.Zero }
  37. JetpackCompose時代における解決策 可変にしたい部分をStateにする // 初期値を定義する val paddingState by remember { mutableStateOf(16.dp)

    } // 描画時に初期値を取得する modifier = Modifier.onGloballyPositioned { heightState = it.size.height } ... modifier = Modifier.onSizeChanged { widthState = it.width }
  38. JetpackCompose時代における解決策 可変にしたい部分をStateにする // 初期値を定義する val paddingState by remember { mutableStateOf(16.dp)

    } // 描画時に初期値を取得する modifier = Modifier.onGloballyPositioned { heightState = it.size.height } ... modifier = Modifier.onSizeChanged { widthState = it.width }
  39. JetpackCompose時代における解決策 可変にしたい部分をStateにする // 初期値を定義する val paddingState by remember { mutableStateOf(16.dp)

    } // 描画時に初期値を取得する modifier = Modifier.onGloballyPositioned { heightState = it.size.height } ... modifier = Modifier.onSizeChanged { widthState = it.width }
  40. JetpackCompose時代における解決策 可変にしたい部分をStateにする // 初期値を定義する val paddingState by remember { mutableStateOf(16.dp)

    } // 描画時に初期値を取得する modifier = Modifier.onGloballyPositioned { heightState = it.size.height } ... modifier = Modifier.onSizeChanged { widthState = it.width }
  41. AndroidView時代のコードと比較 onInterceptTouchEventでスクロールの競合を解消 class NestedScrollableHost : FrameLayout { ... override fun

    onInterceptTouchEvent(e: MotionEvent): Boolean { handleInterceptTouchEvent(e) return super.onInterceptTouchEvent(e) } private fun handleInterceptTouchEvent(e: MotionEvent) { ... } }
  42. AndroidView時代のコードと比較 onInterceptTouchEventをオーバーライド class NestedScrollableHost : FrameLayout { ... override fun

    onInterceptTouchEvent(e: MotionEvent): Boolean { handleInterceptTouchEvent(e) return super.onInterceptTouchEvent(e) } private fun handleInterceptTouchEvent(e: MotionEvent) { ... } }
  43. AndroidView時代のコードと比較 onInterceptTouchEventをオーバーライド class NestedScrollableHost : FrameLayout { ... override fun

    onInterceptTouchEvent(e: MotionEvent): Boolean { handleInterceptTouchEvent(e) return super.onInterceptTouchEvent(e) } private fun handleInterceptTouchEvent(e: MotionEvent) { ... } }
  44. AndroidView時代のコードと比較 親ビューがタッチイベントを受け取らないようにする class NestedScrollableHost : FrameLayout { ... private fun

    handleInterceptTouchEvent(e: MotionEvent) { if (e.action == MotionEvent.ACTION_DOWN) { ... parent.requestDisallowInterceptTouchEvent(true) } ... } }
  45. AndroidView時代のコードと比較 スクロールの端に到達した場合、親に伝播させる class NestedScrollableHost : FrameLayout { ... if (scaledDx

    > touchSlop || scaledDy > touchSlop) { if (isVpHorizontal == (scaledDy > scaledDx)) { ... } else { if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { ... } else { parent.requestDisallowInterceptTouchEvent(false) ...
  46. AndroidView時代のコードと比較 RecyclerViewのタッチイベントを邪魔しない class NestedScrollableHost : FrameLayout { ... if (scaledDx

    > touchSlop || scaledDy > touchSlop) { if (isVpHorizontal == (scaledDy > scaledDx)) { ... } else { if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { ... } else { parent.requestDisallowInterceptTouchEvent(false) ...
  47. AndroidView時代のコードと比較 RecyclerViewのタッチイベントを邪魔しない class NestedScrollableHost : FrameLayout { ... if (scaledDx

    > touchSlop || scaledDy > touchSlop) { if (isVpHorizontal == (scaledDy > scaledDx)) { ... } else { if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { ... } else { parent.requestDisallowInterceptTouchEvent(false) ... ページを切り替えていると みなすスクロール量
  48. AndroidView時代のコードと比較 RecyclerViewのタッチイベントを邪魔しない class NestedScrollableHost : FrameLayout { ... if (scaledDx

    > touchSlop || scaledDy > touchSlop) { if (isVpHorizontal == (scaledDy > scaledDx)) { ... } else { if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { ... } else { parent.requestDisallowInterceptTouchEvent(false) ... Viewシステムがスクロールとみなす距離 ViewConfigurationで取得できる
  49. AndroidView時代のコードと比較 縦スクロールか横スクロールかの判定 class NestedScrollableHost : FrameLayout { ... if (scaledDx

    > touchSlop || scaledDy > touchSlop) { if (isVpHorizontal == (scaledDy > scaledDx)) { ... } else { if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { ... } else { parent.requestDisallowInterceptTouchEvent(false) ... ViewPagerが横スワイプ可能 RecyclerViewがどちらにスクロールされたか
  50. AndroidView時代のコードと比較 縦スクロールか横スクロールかの判定 class NestedScrollableHost : FrameLayout { ... if (scaledDx

    > touchSlop || scaledDy > touchSlop) { if (isVpHorizontal == (scaledDy > scaledDx)) { ... } else { if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { ... } else { parent.requestDisallowInterceptTouchEvent(false) ... これ以上スクロールできるか
  51. AndroidView時代のコードと比較 子がスクロールできるかどうか確認する class NestedScrollableHost : FrameLayout { ... private fun

    canChildScroll(orientation: Int, delta: Float): Boolean { val direction = -delta.sign.toInt() return when (orientation) { 0 -> child?.canScrollHorizontally(direction) ?: false 1 -> child?.canScrollVertically(direction) ?: false else -> throw IllegalArgumentException() } } ...
  52. AndroidView時代のコードと比較 子がスクロールできるかどうか確認する class NestedScrollableHost : FrameLayout { ... private fun

    canChildScroll(orientation: Int, delta: Float): Boolean { val direction = -delta.sign.toInt() return when (orientation) { 0 -> child?.canScrollHorizontally(direction) ?: false 1 -> child?.canScrollVertically(direction) ?: false else -> throw IllegalArgumentException() } } ...
  53. AndroidView時代のコードと比較 子がスクロールできるかどうか確認する class NestedScrollableHost : FrameLayout { ... private fun

    canChildScroll(orientation: Int, delta: Float): Boolean { val direction = -delta.sign.toInt() return when (orientation) { 0 -> child?.canScrollHorizontally(direction) ?: false 1 -> child?.canScrollVertically(direction) ?: false else -> throw IllegalArgumentException() } } ...
  54. AndroidView時代のコードと比較 子がスクロールできるかどうか確認する class NestedScrollableHost : FrameLayout { ... private fun

    canChildScroll(orientation: Int, delta: Float): Boolean { val direction = -delta.sign.toInt() return when (orientation) { 0 -> child?.canScrollHorizontally(direction) ?: false 1 -> child?.canScrollVertically(direction) ?: false else -> throw IllegalArgumentException() } ...
  55. AndroidView時代のコードと比較 子がスクロールできない場合、親がインターセプトする class NestedScrollableHost : FrameLayout { ... if (scaledDx

    > touchSlop || scaledDy > touchSlop) { if (isVpHorizontal == (scaledDy > scaledDx)) { ... } else { if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { ... } else { parent.requestDisallowInterceptTouchEvent(false) ...
  56. AndroidView時代のコードと比較 NestedWebViewの実装(簡略化する) class NestedWebView (...) : WebView, NestedScrollingChild { private

    val mChildHelper = NestedScrollingChildHelper(this) override fun onTouchEvent(...): Boolean {...} override fun dispatch override fun setNestedScrollingEnabled(...) {...} override fun isNestedScrollingEnabled(...): Boolean {...} override fun startNestedScroll(...): Boolean {...} override fun stopNestedScroll(...) {...} override fun hasNestedScrollingParent(...): Boolean {...} override fun dispatchNestedScroll(...): Boolean {...} override fun dispatchNestedPreScroll(...): Boolean {...} override fun dispatchNestedFling(...): Boolean {... override fun dispatchNestedPreFling(...): Boolean {...} }
  57. AndroidView時代のコードと比較 NestedScrollingChildを実装する class NestedWebView (...) : WebView, NestedScrollingChild { private

    val mChildHelper = NestedScrollingChildHelper(this) override fun onTouchEvent(...): Boolean {...} override fun setNestedScrollingEnabled(...) {...} override fun isNestedScrollingEnabled(...): Boolean {...} override fun startNestedScroll(...): Boolean {...} override fun stopNestedScroll(...) {...} override fun hasNestedScrollingParent(...): Boolean {...} override fun dispatchNestedScroll(...): Boolean {...} override fun dispatchNestedPreScroll(...): Boolean {...} override fun dispatchNestedFling(...): Boolean {... override fun dispatchNestedPreFling(...): Boolean {...} }
  58. AndroidView時代のコードと比較 NestedScrollingChildHelperを介してイベントを送る class NestedWebView (...) : WebView, NestedScrollingChild { private

    val mChildHelper = NestedScrollingChildHelper(this) override fun onTouchEvent(...): Boolean {...} override fun setNestedScrollingEnabled(...) {...} override fun isNestedScrollingEnabled(...): Boolean {...} override fun startNestedScroll(...): Boolean {...} override fun stopNestedScroll(...) {...} override fun hasNestedScrollingParent(...): Boolean {...} override fun dispatchNestedScroll(...): Boolean {...} override fun dispatchNestedPreScroll(...): Boolean {...} override fun dispatchNestedFling(...): Boolean {... override fun dispatchNestedPreFling(...): Boolean {...} }
  59. AndroidView時代のコードと比較 ACTION_DOWNでネストスクロールを開始する class NestedWebView (...) : WebView, NestedScrollingChild { private

    val mChildHelper = NestedScrollingChildHelper(this) override fun onTouchEvent(...): Boolean {...} override fun setNestedScrollingEnabled(...) {...} override fun isNestedScrollingEnabled(...): Boolean {...} override fun startNestedScroll(...): Boolean {...} override fun stopNestedScroll(...) {...} override fun hasNestedScrollingParent(...): Boolean {...} override fun dispatchNestedScroll(...): Boolean {...} override fun dispatchNestedPreScroll(...): Boolean {...} override fun dispatchNestedFling(...): Boolean {... override fun dispatchNestedPreFling(...): Boolean {...} }
  60. AndroidView時代のコードと比較 ネストスクロールを有効化 / 確認 class NestedWebView (...) : WebView, NestedScrollingChild

    { private val mChildHelper = NestedScrollingChildHelper(this) override fun onTouchEvent(...): Boolean {...} override fun setNestedScrollingEnabled(...) {...} override fun isNestedScrollingEnabled(...): Boolean {...} override fun startNestedScroll(...): Boolean {...} override fun stopNestedScroll(...) {...} override fun hasNestedScrollingParent(...): Boolean {...} override fun dispatchNestedScroll(...): Boolean {...} override fun dispatchNestedPreScroll(...): Boolean {...} override fun dispatchNestedFling(...): Boolean {... override fun dispatchNestedPreFling(...): Boolean {...} }
  61. AndroidView時代のコードと比較 NestedScrollConnectionと同様にスクロール量を送る class NestedWebView (...) : WebView, NestedScrollingChild { private

    val mChildHelper = NestedScrollingChildHelper(this) override fun onTouchEvent(...): Boolean {...} override fun setNestedScrollingEnabled(...) {...} override fun isNestedScrollingEnabled(...): Boolean {...} override fun startNestedScroll(...): Boolean {...} override fun stopNestedScroll(...) {...} override fun hasNestedScrollingParent(...): Boolean {...} override fun dispatchNestedScroll(...): Boolean {...} override fun dispatchNestedPreScroll(...): Boolean {...} override fun dispatchNestedFling(...): Boolean {... override fun dispatchNestedPreFling(...): Boolean {...} }
  62. AndroidView時代のコードと比較 ACTION_UP / ACTION_CANCELでスクロールを終了する class NestedWebView (...) : WebView, NestedScrollingChild

    { private val mChildHelper = NestedScrollingChildHelper(this) override fun onTouchEvent(...): Boolean {...} override fun setNestedScrollingEnabled(...) {...} override fun isNestedScrollingEnabled(...): Boolean {...} override fun startNestedScroll(...): Boolean {...} override fun stopNestedScroll(...) {...} override fun hasNestedScrollingParent(...): Boolean {...} override fun dispatchNestedScroll(...): Boolean {...} override fun dispatchNestedPreScroll(...): Boolean {...} override fun dispatchNestedFling(...): Boolean {... override fun dispatchNestedPreFling(...): Boolean {...} }
  63. 相互運用上の注意点 NestedScrollInteropConnectionは何をしているのか? internal class NestedScrollInteropConnection(...) : NestedScrollConnection() { private val

    nestedScrollChildHelper = NestedScrollingChildHelper(...) ... override fun onPreScroll(...): Offset { if(nestedScrollChildHelper.startNestedScroll(...)) { ... nestedScrollChildHelper.dispatchNestedScroll(...) return toOffset(consumedScrollCache, available) } }
  64. 相互運用上の注意点 NestedScrollConnectionを実装 internal class NestedScrollInteropConnection(...) : NestedScrollConnection() { private val

    nestedScrollChildHelper = NestedScrollingChildHelper(...) ... override fun onPreScroll(...): Offset { if(nestedScrollChildHelper.startNestedScroll(...)) { ... nestedScrollChildHelper.dispatchNestedScroll(...) return toOffset(consumedScrollCache, available) } }
  65. 相互運用上の注意点 AndroidView同様にHelperクラスを介する internal class NestedScrollInteropConnection(...) : NestedScrollConnection() { private val

    nestedScrollChildHelper = NestedScrollingChildHelper(...) ... override fun onPreScroll(...): Offset { if(nestedScrollChildHelper.startNestedScroll(...)) { ... nestedScrollChildHelper.dispatchNestedScroll(...) return toOffset(consumedScrollCache, available) } }
  66. 相互運用上の注意点 スクロールする前の制御 internal class NestedScrollInteropConnection(...) : NestedScrollConnection() { private val

    nestedScrollChildHelper = NestedScrollingChildHelper(...) ... override fun onPreScroll(...): Offset { if(nestedScrollChildHelper.startNestedScroll(...)) { ... nestedScrollChildHelper.dispatchNestedScroll(...) return toOffset(consumedScrollCache, available) } }
  67. 相互運用上の注意点 ネストスクロールができるか確認 internal class NestedScrollInteropConnection(...) : NestedScrollConnection() { private val

    nestedScrollChildHelper = NestedScrollingChildHelper(...) ... override fun onPreScroll(...): Offset { if(nestedScrollChildHelper.startNestedScroll(...)) { ... nestedScrollChildHelper.dispatchNestedScroll(...) return toOffset(consumedScrollCache, available) } }
  68. 相互運用上の注意点 親にスクロール消費量を送る internal class NestedScrollInteropConnection(...) : NestedScrollConnection() { private val

    nestedScrollChildHelper = NestedScrollingChildHelper(...) ... override fun onPreScroll(...): Offset { if(nestedScrollChildHelper.startNestedScroll(...)) { ... nestedScrollChildHelper.dispatchNestedScroll(...) return toOffset(consumedScrollCache, available) } }
  69. 相互運用上の注意点 スクロール消費量と消費されなかった量を送る internal class NestedScrollInteropConnection(...) : NestedScrollConnection() { private val

    nestedScrollChildHelper = NestedScrollingChildHelper(...) ... override fun onPreScroll(...): Offset { if(nestedScrollChildHelper.startNestedScroll(...)) { ... nestedScrollChildHelper.dispatchNestedScroll( composeToViewOffset(consumed.x), composeToViewOffset(consumed.y), composeToViewOffset(available.x), composeToViewOffset(available.y), ... )
  70. 相互運用上の注意点 Composable関数のAndroidViewを用いる Box(Modifier.nestedScroll(nestedScrollConnection)) { AndroidViewBinding( factory = InteropNestedScrollBinding::inflate, update =

    { recyclerview.layoutManager = LinearLayoutManager(root.context) recyclerview.adapter = AndroidViewViewPagerAdapter(...) }, ... ) TopAppBar(...) }
  71. 相互運用上の注意点 Composable関数のAndroidViewを用いる Box(Modifier.nestedScroll(nestedScrollConnection)) { AndroidViewBinding( factory = InteropNestedScrollBinding::inflate, update =

    { recyclerview.layoutManager = LinearLayoutManager(root.context) recyclerview.adapter = AndroidViewViewPagerAdapter(...) }, ... ) TopAppBar(...) }
  72. 相互運用上の注意点 Composable関数のAndroidViewを用いる Box(Modifier.nestedScroll(nestedScrollConnection)) { AndroidViewBinding( factory = InteropNestedScrollBinding::inflate, update =

    { recyclerview.layoutManager = LinearLayoutManager(root.context) recyclerview.adapter = AndroidViewViewPagerAdapter(...) }, ... ) TopAppBar(...) }
  73. 相互運用上の注意点 Composable関数のAndroidViewを用いる Box(Modifier.nestedScroll(nestedScrollConnection)) { AndroidViewBinding( factory = InteropNestedScrollBinding::inflate, update =

    { recyclerview.layoutManager = LinearLayoutManager(root.context) recyclerview.adapter = AndroidViewViewPagerAdapter(...) }, ... ) TopAppBar(...) }
  74. 相互運用上の注意点 Composable関数のAndroidViewを用いる Box(Modifier.nestedScroll(nestedScrollConnection)) { AndroidViewBinding( factory = InteropNestedScrollBinding::inflate, update =

    { recyclerview.layoutManager = LinearLayoutManager(root.context) recyclerview.adapter = AndroidViewViewPagerAdapter(...) }, ... ) TopAppBar(...) }
  75. 相互運用上の注意点 Composable関数のAndroidViewを用いる Box(Modifier.nestedScroll(nestedScrollConnection)) { AndroidViewBinding( factory = InteropNestedScrollBinding::inflate, update =

    { recyclerview.layoutManager = LinearLayoutManager(root.context) recyclerview.adapter = AndroidViewViewPagerAdapter(...) }, ... ) TopAppBar(...) }