From b5b1e44eb3c8c267fdb962ddbd5df70013b8681b Mon Sep 17 00:00:00 2001 From: Peng Quah Date: Fri, 18 Sep 2026 19:23:08 +0000 Subject: [PATCH 1/9] Add code snippets for migrate-swipeable --- .../pointerinput/MigrateSwipeable.kt | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt new file mode 100644 index 000000000..1a78c2420 --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt @@ -0,0 +1,220 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.touchinput.pointerinput + +import androidx.compose.animation.core.exponentialDecay +import androidx.compose.animation.core.spring +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.gestures.AnchoredDraggableState +import androidx.compose.foundation.gestures.DraggableAnchors +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.offset +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.SwipeableState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp + +@OptIn(ExperimentalMaterialApi::class) +private object SwipeableStateExample { + // [START android_compose_touchinput_pointerinput_migrate_swipeable_state_legacy] + class MySwitchState : SwipeableState(initialValue = DragValue.Start) + // [END android_compose_touchinput_pointerinput_migrate_swipeable_state_legacy] +} + +@OptIn(ExperimentalFoundationApi::class) +private object AnchoredDraggableStateExample { + // [START android_compose_touchinput_pointerinput_migrate_swipeable_state_anchored] + class MySwitchState { + private val anchoredDraggableState = AnchoredDraggableState( + // ... + // [START_EXCLUDE silent] + initialValue = DragValue.Start, + positionalThreshold = { distance: Float -> distance * 0.5f }, + velocityThreshold = { 125f }, + snapAnimationSpec = spring(), + decayAnimationSpec = exponentialDecay() + // [END_EXCLUDE] + ) + } + // [END android_compose_touchinput_pointerinput_migrate_swipeable_state_anchored] +} + +@OptIn(ExperimentalFoundationApi::class) +// [START android_compose_touchinput_pointerinput_migrate_swipeable_access_offset] +@Composable +fun AnchoredDraggableBox() { + val state = remember { + AnchoredDraggableState( + // ... + // [START_EXCLUDE silent] + initialValue = DragValue.Start, + positionalThreshold = { distance: Float -> distance * 0.5f }, + velocityThreshold = { 125f }, + snapAnimationSpec = spring(), + decayAnimationSpec = exponentialDecay() + // [END_EXCLUDE] + ) + } + val density = LocalDensity.current + val anchors = remember { + DraggableAnchors { + // ... + // [START_EXCLUDE silent] + DragValue.Start at -100f + DragValue.Center at 0f + DragValue.End at 100f + // [END_EXCLUDE] + } + } + SideEffect { + state.updateAnchors(anchors) + } + Box( + Modifier.offset { IntOffset(x = state.requireOffset().toInt(), y = 0) } + ) +} +// [END android_compose_touchinput_pointerinput_migrate_swipeable_access_offset] + +@OptIn(ExperimentalFoundationApi::class) +private object AnchoredDraggableConstructorExample { + // [START android_compose_touchinput_pointerinput_migrate_swipeable_anchors_constructor] + enum class DragValue { Start, Center, End } + + @Composable + fun AnchoredDraggableBox() { + val density = LocalDensity.current + val anchors = with(density) { + DraggableAnchors { + DragValue.Start at -100.dp.toPx() + DragValue.Center at 0f + DragValue.End at 100.dp.toPx() + } + } + val state = remember { + AnchoredDraggableState( + anchors = anchors, + // [START_EXCLUDE silent] + initialValue = DragValue.Start, + positionalThreshold = { distance: Float -> distance * 0.5f }, + velocityThreshold = { 125f }, + snapAnimationSpec = spring(), + decayAnimationSpec = exponentialDecay() + // [END_EXCLUDE] + ) + } + Box( + Modifier.offset { IntOffset(x = state.requireOffset().toInt(), y = 0) } + ) + } + // [END android_compose_touchinput_pointerinput_migrate_swipeable_anchors_constructor] +} + +@OptIn(ExperimentalFoundationApi::class) +private object AnchoredDraggableUpdateAnchorsExample { + // [START android_compose_touchinput_pointerinput_migrate_swipeable_anchors_update] + enum class DragValue { Start, Center, End } + + @Composable + fun AnchoredDraggableBox() { + val state = remember { + AnchoredDraggableState( + // ... + // [START_EXCLUDE silent] + initialValue = DragValue.Start, + positionalThreshold = { distance: Float -> distance * 0.5f }, + velocityThreshold = { 125f }, + snapAnimationSpec = spring(), + decayAnimationSpec = exponentialDecay() + // [END_EXCLUDE] + ) + } + val density = LocalDensity.current + val anchors = with(density) { + DraggableAnchors { + DragValue.Start at -100.dp.toPx() + DragValue.Center at 0f + DragValue.End at 100.dp.toPx() + } + } + SideEffect { + state.updateAnchors(anchors) + } + Box( + Modifier.offset { IntOffset(x = state.requireOffset().toInt(), y = 0) } + ) + } + // [END android_compose_touchinput_pointerinput_migrate_swipeable_anchors_update] +} + +@OptIn(ExperimentalFoundationApi::class) +private fun PositionalThresholdFraction() { + // [START android_compose_touchinput_pointerinput_migrate_swipeable_positional_threshold_fraction] + val anchoredDraggableState = AnchoredDraggableState( + positionalThreshold = { distance -> distance * 0.5f }, + // ... + // [START_EXCLUDE silent] + initialValue = DragValue.Start, + velocityThreshold = { 125f }, + snapAnimationSpec = spring(), + decayAnimationSpec = exponentialDecay() + // [END_EXCLUDE] + ) + // [END android_compose_touchinput_pointerinput_migrate_swipeable_positional_threshold_fraction] +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun PositionalThresholdDp() { + // [START android_compose_touchinput_pointerinput_migrate_swipeable_positional_threshold_dp] + val density = LocalDensity.current + val anchoredDraggableState = AnchoredDraggableState( + positionalThreshold = { with(density) { 56.dp.toPx() } }, + // ... + // [START_EXCLUDE silent] + initialValue = DragValue.Start, + velocityThreshold = { 125f }, + snapAnimationSpec = spring(), + decayAnimationSpec = exponentialDecay() + // [END_EXCLUDE] + ) + // [END android_compose_touchinput_pointerinput_migrate_swipeable_positional_threshold_dp] +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun VelocityThreshold() { + // [START android_compose_touchinput_pointerinput_migrate_swipeable_velocity_threshold] + val density = LocalDensity.current + val anchoredDraggableState = AnchoredDraggableState( + velocityThreshold = { with(density) { 125.dp.toPx() } }, + // ... + // [START_EXCLUDE silent] + initialValue = DragValue.Start, + positionalThreshold = { distance: Float -> distance * 0.5f }, + snapAnimationSpec = spring(), + decayAnimationSpec = exponentialDecay() + // [END_EXCLUDE] + ) + // [END android_compose_touchinput_pointerinput_migrate_swipeable_velocity_threshold] +} + +enum class DragValue { Start, Center, End } From 21d967dea6695167dbf4f338b33759f3497bb8d4 Mon Sep 17 00:00:00 2001 From: Peng Quah Date: Fri, 18 Sep 2026 19:43:05 +0000 Subject: [PATCH 2/9] Add code snippets for nested-scroll-modifiers --- .../scroll/NestedScrollModifiers.kt | 207 ++++++++++++++++++ .../main/res/layout/fragment_bottom_sheet.xml | 25 +++ .../src/main/res/layout/list_item.xml | 19 ++ .../view_in_compose_nested_scroll_interop.xml | 20 ++ 4 files changed, 271 insertions(+) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt create mode 100644 compose/snippets/src/main/res/layout/fragment_bottom_sheet.xml create mode 100644 compose/snippets/src/main/res/layout/list_item.xml create mode 100644 compose/snippets/src/main/res/layout/view_in_compose_nested_scroll_interop.xml diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt new file mode 100644 index 000000000..b758bc60c --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt @@ -0,0 +1,207 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.compose.snippets.touchinput.scroll + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.Adapter +import androidx.recyclerview.widget.RecyclerView.VERTICAL +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.example.compose.snippets.R +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlin.math.roundToInt + +// [START android_compose_touchinput_scroll_nested_scroll_interop_compose_parent] +@Composable +private fun NestedScrollInteropComposeParentWithAndroidChildExample() { + val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() } + val toolbarOffsetHeightPx = remember { mutableStateOf(0f) } + + // Sets up the nested scroll connection between the Box composable parent + // and the child AndroidView containing the RecyclerView + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + // Updates the toolbar offset based on the scroll to enable + // collapsible behaviour + val delta = available.y + val newOffset = toolbarOffsetHeightPx.value + delta + toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f) + return Offset.Zero + } + } + } + + Box( + Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection) + ) { + TopAppBar( + // [START_EXCLUDE silent] + title = {}, + // [END_EXCLUDE] + modifier = Modifier + .height(ToolbarHeight) + .offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) } + ) + + AndroidView( + { context -> + LayoutInflater.from(context) + .inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply { + with(findViewById(R.id.main_list)) { + layoutManager = LinearLayoutManager(context, VERTICAL, false) + adapter = NestedScrollInteropAdapter() + } + }.also { + // Nested scrolling interop is enabled when + // nested scroll is enabled for the root View + ViewCompat.setNestedScrollingEnabled(it, true) + } + }, + // ... + ) + } +} + +private class NestedScrollInteropAdapter : + Adapter() { + val items = (1..10).map { it.toString() } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): NestedScrollInteropViewHolder { + return NestedScrollInteropViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.list_item, parent, false) + ) + } + + override fun onBindViewHolder(holder: NestedScrollInteropViewHolder, position: Int) { + // ... + } + + class NestedScrollInteropViewHolder(view: View) : ViewHolder(view) { + fun bind(item: String) { + // ... + } + } + // ... + // [START_EXCLUDE silent] + override fun getItemCount(): Int = items.size + // [END_EXCLUDE] +} +// [END android_compose_touchinput_scroll_nested_scroll_interop_compose_parent] + +// [START android_compose_touchinput_scroll_view_in_compose_interop] +@Composable +fun ViewInComposeNestedScrollInteropExample() { + Box( + Modifier + .fillMaxSize() + .scrollable( + rememberScrollableState { + // View component deltas should be reflected in Compose + // components that participate in nested scrolling + it + }, + Orientation.Vertical + ) + ) { + AndroidView( + { context -> + LayoutInflater.from(context) + .inflate(R.layout.list_item, null) + .apply { + // Nested scrolling interop is enabled when + // nested scroll is enabled for the root View + ViewCompat.setNestedScrollingEnabled(this, true) + } + } + ) + } +} +// [END android_compose_touchinput_scroll_view_in_compose_interop] + +// [START android_compose_touchinput_scroll_bottom_sheet_interop] +class BottomSheetFragment : BottomSheetDialogFragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val rootView: View = inflater.inflate(R.layout.fragment_bottom_sheet, container, false) + + rootView.findViewById(R.id.compose_view).apply { + setContent { + val nestedScrollInterop = rememberNestedScrollInteropConnection() + LazyColumn( + Modifier + .nestedScroll(nestedScrollInterop) + .fillMaxSize() + ) { + item { + Text(text = "Bottom sheet title") + } + items(10) { + Text( + text = "List item number $it", + modifier = Modifier.fillMaxWidth() + ) + } + } + } + return rootView + } + } +} +// [END android_compose_touchinput_scroll_bottom_sheet_interop] + +private val ToolbarHeight = 48.dp diff --git a/compose/snippets/src/main/res/layout/fragment_bottom_sheet.xml b/compose/snippets/src/main/res/layout/fragment_bottom_sheet.xml new file mode 100644 index 000000000..d3df0699c --- /dev/null +++ b/compose/snippets/src/main/res/layout/fragment_bottom_sheet.xml @@ -0,0 +1,25 @@ + + + + + + diff --git a/compose/snippets/src/main/res/layout/list_item.xml b/compose/snippets/src/main/res/layout/list_item.xml new file mode 100644 index 000000000..a42dc97ac --- /dev/null +++ b/compose/snippets/src/main/res/layout/list_item.xml @@ -0,0 +1,19 @@ + + + diff --git a/compose/snippets/src/main/res/layout/view_in_compose_nested_scroll_interop.xml b/compose/snippets/src/main/res/layout/view_in_compose_nested_scroll_interop.xml new file mode 100644 index 000000000..a5bc47489 --- /dev/null +++ b/compose/snippets/src/main/res/layout/view_in_compose_nested_scroll_interop.xml @@ -0,0 +1,20 @@ + + + From 81b081a3b602a99066cfb7fc653a9ec7f39f0844 Mon Sep 17 00:00:00 2001 From: Peng Quah Date: Tue, 29 Sep 2026 21:54:22 +0000 Subject: [PATCH 3/9] Mark helper enum class DragValue private in MigrateSwipeable.kt --- .../snippets/touchinput/pointerinput/MigrateSwipeable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt index 1a78c2420..6641e92bf 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt @@ -217,4 +217,4 @@ private fun VelocityThreshold() { // [END android_compose_touchinput_pointerinput_migrate_swipeable_velocity_threshold] } -enum class DragValue { Start, Center, End } +private enum class DragValue { Start, Center, End } From c006d5c96c67cf07d42176c8f33ef4e64ffd506a Mon Sep 17 00:00:00 2001 From: Peng Quah Date: Wed, 30 Sep 2026 17:04:53 +0000 Subject: [PATCH 4/9] Use plain [START_EXCLUDE] instead of long-form // ... + [START_EXCLUDE silent] --- .../pointerinput/MigrateSwipeable.kt | 21 +++++++------------ .../scroll/NestedScrollModifiers.kt | 3 +-- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt index 6641e92bf..56f0a2bf9 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt @@ -45,8 +45,7 @@ private object AnchoredDraggableStateExample { // [START android_compose_touchinput_pointerinput_migrate_swipeable_state_anchored] class MySwitchState { private val anchoredDraggableState = AnchoredDraggableState( - // ... - // [START_EXCLUDE silent] + // [START_EXCLUDE] initialValue = DragValue.Start, positionalThreshold = { distance: Float -> distance * 0.5f }, velocityThreshold = { 125f }, @@ -64,8 +63,7 @@ private object AnchoredDraggableStateExample { fun AnchoredDraggableBox() { val state = remember { AnchoredDraggableState( - // ... - // [START_EXCLUDE silent] + // [START_EXCLUDE] initialValue = DragValue.Start, positionalThreshold = { distance: Float -> distance * 0.5f }, velocityThreshold = { 125f }, @@ -77,8 +75,7 @@ fun AnchoredDraggableBox() { val density = LocalDensity.current val anchors = remember { DraggableAnchors { - // ... - // [START_EXCLUDE silent] + // [START_EXCLUDE] DragValue.Start at -100f DragValue.Center at 0f DragValue.End at 100f @@ -137,8 +134,7 @@ private object AnchoredDraggableUpdateAnchorsExample { fun AnchoredDraggableBox() { val state = remember { AnchoredDraggableState( - // ... - // [START_EXCLUDE silent] + // [START_EXCLUDE] initialValue = DragValue.Start, positionalThreshold = { distance: Float -> distance * 0.5f }, velocityThreshold = { 125f }, @@ -170,8 +166,7 @@ private fun PositionalThresholdFraction() { // [START android_compose_touchinput_pointerinput_migrate_swipeable_positional_threshold_fraction] val anchoredDraggableState = AnchoredDraggableState( positionalThreshold = { distance -> distance * 0.5f }, - // ... - // [START_EXCLUDE silent] + // [START_EXCLUDE] initialValue = DragValue.Start, velocityThreshold = { 125f }, snapAnimationSpec = spring(), @@ -188,8 +183,7 @@ private fun PositionalThresholdDp() { val density = LocalDensity.current val anchoredDraggableState = AnchoredDraggableState( positionalThreshold = { with(density) { 56.dp.toPx() } }, - // ... - // [START_EXCLUDE silent] + // [START_EXCLUDE] initialValue = DragValue.Start, velocityThreshold = { 125f }, snapAnimationSpec = spring(), @@ -206,8 +200,7 @@ private fun VelocityThreshold() { val density = LocalDensity.current val anchoredDraggableState = AnchoredDraggableState( velocityThreshold = { with(density) { 125.dp.toPx() } }, - // ... - // [START_EXCLUDE silent] + // [START_EXCLUDE] initialValue = DragValue.Start, positionalThreshold = { distance: Float -> distance * 0.5f }, snapAnimationSpec = spring(), diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt index b758bc60c..4167bf650 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt @@ -132,8 +132,7 @@ private class NestedScrollInteropAdapter : // ... } } - // ... - // [START_EXCLUDE silent] + // [START_EXCLUDE] override fun getItemCount(): Int = items.size // [END_EXCLUDE] } From 971e6f89a9c0830697767162f7e5000d1f561ffe Mon Sep 17 00:00:00 2001 From: Peng Quah Date: Thu, 1 Oct 2026 15:31:37 +0000 Subject: [PATCH 5/9] Restore unqualified Start, Center, End enum entries in MigrateSwipeable.kt --- .../pointerinput/MigrateSwipeable.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt index 56f0a2bf9..9a8570cb6 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt @@ -32,6 +32,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import com.example.compose.snippets.touchinput.pointerinput.DragValue.Center +import com.example.compose.snippets.touchinput.pointerinput.DragValue.End +import com.example.compose.snippets.touchinput.pointerinput.DragValue.Start @OptIn(ExperimentalMaterialApi::class) private object SwipeableStateExample { @@ -101,16 +104,16 @@ private object AnchoredDraggableConstructorExample { val density = LocalDensity.current val anchors = with(density) { DraggableAnchors { - DragValue.Start at -100.dp.toPx() - DragValue.Center at 0f - DragValue.End at 100.dp.toPx() + Start at -100.dp.toPx() + Center at 0f + End at 100.dp.toPx() } } val state = remember { AnchoredDraggableState( anchors = anchors, // [START_EXCLUDE silent] - initialValue = DragValue.Start, + initialValue = Start, positionalThreshold = { distance: Float -> distance * 0.5f }, velocityThreshold = { 125f }, snapAnimationSpec = spring(), @@ -135,7 +138,7 @@ private object AnchoredDraggableUpdateAnchorsExample { val state = remember { AnchoredDraggableState( // [START_EXCLUDE] - initialValue = DragValue.Start, + initialValue = Start, positionalThreshold = { distance: Float -> distance * 0.5f }, velocityThreshold = { 125f }, snapAnimationSpec = spring(), @@ -146,9 +149,9 @@ private object AnchoredDraggableUpdateAnchorsExample { val density = LocalDensity.current val anchors = with(density) { DraggableAnchors { - DragValue.Start at -100.dp.toPx() - DragValue.Center at 0f - DragValue.End at 100.dp.toPx() + Start at -100.dp.toPx() + Center at 0f + End at 100.dp.toPx() } } SideEffect { From b54c04d195d769c73b5323f36fae797309d4aa3e Mon Sep 17 00:00:00 2001 From: Peng Quah Date: Tue, 6 Oct 2026 14:03:18 +0000 Subject: [PATCH 6/9] Remove MigrateSwipeable.kt per SME review --- .../pointerinput/MigrateSwipeable.kt | 216 ------------------ 1 file changed, 216 deletions(-) delete mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt deleted file mode 100644 index 9a8570cb6..000000000 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/MigrateSwipeable.kt +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright 2026 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.compose.snippets.touchinput.pointerinput - -import androidx.compose.animation.core.exponentialDecay -import androidx.compose.animation.core.spring -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.gestures.AnchoredDraggableState -import androidx.compose.foundation.gestures.DraggableAnchors -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.offset -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.SwipeableState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp -import com.example.compose.snippets.touchinput.pointerinput.DragValue.Center -import com.example.compose.snippets.touchinput.pointerinput.DragValue.End -import com.example.compose.snippets.touchinput.pointerinput.DragValue.Start - -@OptIn(ExperimentalMaterialApi::class) -private object SwipeableStateExample { - // [START android_compose_touchinput_pointerinput_migrate_swipeable_state_legacy] - class MySwitchState : SwipeableState(initialValue = DragValue.Start) - // [END android_compose_touchinput_pointerinput_migrate_swipeable_state_legacy] -} - -@OptIn(ExperimentalFoundationApi::class) -private object AnchoredDraggableStateExample { - // [START android_compose_touchinput_pointerinput_migrate_swipeable_state_anchored] - class MySwitchState { - private val anchoredDraggableState = AnchoredDraggableState( - // [START_EXCLUDE] - initialValue = DragValue.Start, - positionalThreshold = { distance: Float -> distance * 0.5f }, - velocityThreshold = { 125f }, - snapAnimationSpec = spring(), - decayAnimationSpec = exponentialDecay() - // [END_EXCLUDE] - ) - } - // [END android_compose_touchinput_pointerinput_migrate_swipeable_state_anchored] -} - -@OptIn(ExperimentalFoundationApi::class) -// [START android_compose_touchinput_pointerinput_migrate_swipeable_access_offset] -@Composable -fun AnchoredDraggableBox() { - val state = remember { - AnchoredDraggableState( - // [START_EXCLUDE] - initialValue = DragValue.Start, - positionalThreshold = { distance: Float -> distance * 0.5f }, - velocityThreshold = { 125f }, - snapAnimationSpec = spring(), - decayAnimationSpec = exponentialDecay() - // [END_EXCLUDE] - ) - } - val density = LocalDensity.current - val anchors = remember { - DraggableAnchors { - // [START_EXCLUDE] - DragValue.Start at -100f - DragValue.Center at 0f - DragValue.End at 100f - // [END_EXCLUDE] - } - } - SideEffect { - state.updateAnchors(anchors) - } - Box( - Modifier.offset { IntOffset(x = state.requireOffset().toInt(), y = 0) } - ) -} -// [END android_compose_touchinput_pointerinput_migrate_swipeable_access_offset] - -@OptIn(ExperimentalFoundationApi::class) -private object AnchoredDraggableConstructorExample { - // [START android_compose_touchinput_pointerinput_migrate_swipeable_anchors_constructor] - enum class DragValue { Start, Center, End } - - @Composable - fun AnchoredDraggableBox() { - val density = LocalDensity.current - val anchors = with(density) { - DraggableAnchors { - Start at -100.dp.toPx() - Center at 0f - End at 100.dp.toPx() - } - } - val state = remember { - AnchoredDraggableState( - anchors = anchors, - // [START_EXCLUDE silent] - initialValue = Start, - positionalThreshold = { distance: Float -> distance * 0.5f }, - velocityThreshold = { 125f }, - snapAnimationSpec = spring(), - decayAnimationSpec = exponentialDecay() - // [END_EXCLUDE] - ) - } - Box( - Modifier.offset { IntOffset(x = state.requireOffset().toInt(), y = 0) } - ) - } - // [END android_compose_touchinput_pointerinput_migrate_swipeable_anchors_constructor] -} - -@OptIn(ExperimentalFoundationApi::class) -private object AnchoredDraggableUpdateAnchorsExample { - // [START android_compose_touchinput_pointerinput_migrate_swipeable_anchors_update] - enum class DragValue { Start, Center, End } - - @Composable - fun AnchoredDraggableBox() { - val state = remember { - AnchoredDraggableState( - // [START_EXCLUDE] - initialValue = Start, - positionalThreshold = { distance: Float -> distance * 0.5f }, - velocityThreshold = { 125f }, - snapAnimationSpec = spring(), - decayAnimationSpec = exponentialDecay() - // [END_EXCLUDE] - ) - } - val density = LocalDensity.current - val anchors = with(density) { - DraggableAnchors { - Start at -100.dp.toPx() - Center at 0f - End at 100.dp.toPx() - } - } - SideEffect { - state.updateAnchors(anchors) - } - Box( - Modifier.offset { IntOffset(x = state.requireOffset().toInt(), y = 0) } - ) - } - // [END android_compose_touchinput_pointerinput_migrate_swipeable_anchors_update] -} - -@OptIn(ExperimentalFoundationApi::class) -private fun PositionalThresholdFraction() { - // [START android_compose_touchinput_pointerinput_migrate_swipeable_positional_threshold_fraction] - val anchoredDraggableState = AnchoredDraggableState( - positionalThreshold = { distance -> distance * 0.5f }, - // [START_EXCLUDE] - initialValue = DragValue.Start, - velocityThreshold = { 125f }, - snapAnimationSpec = spring(), - decayAnimationSpec = exponentialDecay() - // [END_EXCLUDE] - ) - // [END android_compose_touchinput_pointerinput_migrate_swipeable_positional_threshold_fraction] -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -private fun PositionalThresholdDp() { - // [START android_compose_touchinput_pointerinput_migrate_swipeable_positional_threshold_dp] - val density = LocalDensity.current - val anchoredDraggableState = AnchoredDraggableState( - positionalThreshold = { with(density) { 56.dp.toPx() } }, - // [START_EXCLUDE] - initialValue = DragValue.Start, - velocityThreshold = { 125f }, - snapAnimationSpec = spring(), - decayAnimationSpec = exponentialDecay() - // [END_EXCLUDE] - ) - // [END android_compose_touchinput_pointerinput_migrate_swipeable_positional_threshold_dp] -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -private fun VelocityThreshold() { - // [START android_compose_touchinput_pointerinput_migrate_swipeable_velocity_threshold] - val density = LocalDensity.current - val anchoredDraggableState = AnchoredDraggableState( - velocityThreshold = { with(density) { 125.dp.toPx() } }, - // [START_EXCLUDE] - initialValue = DragValue.Start, - positionalThreshold = { distance: Float -> distance * 0.5f }, - snapAnimationSpec = spring(), - decayAnimationSpec = exponentialDecay() - // [END_EXCLUDE] - ) - // [END android_compose_touchinput_pointerinput_migrate_swipeable_velocity_threshold] -} - -private enum class DragValue { Start, Center, End } From 876af93d2ae9d445a3c89383fff1c6b2a70d52c5 Mon Sep 17 00:00:00 2001 From: Peng Quah Date: Tue, 6 Oct 2026 14:24:05 +0000 Subject: [PATCH 7/9] Switch NestedScrollModifiers.kt to Material 3 Text and TopAppBar --- .../snippets/touchinput/scroll/NestedScrollModifiers.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt index 4167bf650..ed4ee9c04 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt @@ -29,8 +29,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.Text -import androidx.compose.material.TopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -55,6 +56,7 @@ import com.example.compose.snippets.R import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlin.math.roundToInt +@OptIn(ExperimentalMaterial3Api::class) // [START android_compose_touchinput_scroll_nested_scroll_interop_compose_parent] @Composable private fun NestedScrollInteropComposeParentWithAndroidChildExample() { From 28e4ddc833ff053bfb045bc821e8b61cfa5af6f2 Mon Sep 17 00:00:00 2001 From: Peng Quah Date: Thu, 8 Oct 2026 21:17:01 +0000 Subject: [PATCH 8/9] Remove exclude tags around title in TopAppBar --- .../compose/snippets/touchinput/scroll/NestedScrollModifiers.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt index ed4ee9c04..0bfa65327 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt @@ -84,9 +84,7 @@ private fun NestedScrollInteropComposeParentWithAndroidChildExample() { .nestedScroll(nestedScrollConnection) ) { TopAppBar( - // [START_EXCLUDE silent] title = {}, - // [END_EXCLUDE] modifier = Modifier .height(ToolbarHeight) .offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) } From f7e993ea864e17b2340c55b8a6a1d53a654f05f2 Mon Sep 17 00:00:00 2001 From: Peng Quah Date: Thu, 8 Oct 2026 21:26:38 +0000 Subject: [PATCH 9/9] Move nested-scroll-modifiers snippets into GesturesSnippets.kt --- .../touchinput/gestures/GesturesSnippets.kt | 163 ++++++++++++++ .../scroll/NestedScrollModifiers.kt | 206 ------------------ 2 files changed, 163 insertions(+), 206 deletions(-) delete mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/gestures/GesturesSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/gestures/GesturesSnippets.kt index c02932602..5d52fbabe 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/gestures/GesturesSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/gestures/GesturesSnippets.kt @@ -19,6 +19,9 @@ package com.example.compose.snippets.touchinput.gestures import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import androidx.activity.ComponentActivity import androidx.compose.foundation.MutatePriority import androidx.compose.foundation.background @@ -69,11 +72,13 @@ import androidx.compose.material.icons.filled.Settings import androidx.compose.material.rememberSwipeableState import androidx.compose.material.swipeable import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.annotation.FrequentlyChangingValue @@ -113,6 +118,15 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.fastRoundToInt import androidx.compose.ui.util.lerp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.Adapter +import androidx.recyclerview.widget.RecyclerView.VERTICAL +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.example.compose.snippets.R +import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlin.math.abs import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope @@ -981,3 +995,152 @@ fun NestedScrollSample() { } // [END android_compose_touchinput_gestures_nestedscrolldisabled] } + +@OptIn(ExperimentalMaterial3Api::class) +// [START android_compose_touchinput_scroll_nested_scroll_interop_compose_parent] +@Composable +private fun NestedScrollInteropComposeParentWithAndroidChildExample() { + val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() } + val toolbarOffsetHeightPx = remember { mutableStateOf(0f) } + + // Sets up the nested scroll connection between the Box composable parent + // and the child AndroidView containing the RecyclerView + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + // Updates the toolbar offset based on the scroll to enable + // collapsible behaviour + val delta = available.y + val newOffset = toolbarOffsetHeightPx.value + delta + toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f) + return Offset.Zero + } + } + } + + Box( + Modifier + .fillMaxSize() + .nestedScroll(nestedScrollConnection) + ) { + TopAppBar( + title = {}, + modifier = Modifier + .height(ToolbarHeight) + .offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) } + ) + + AndroidView( + { context -> + LayoutInflater.from(context) + .inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply { + with(findViewById(R.id.main_list)) { + layoutManager = LinearLayoutManager(context, VERTICAL, false) + adapter = NestedScrollInteropAdapter() + } + }.also { + // Nested scrolling interop is enabled when + // nested scroll is enabled for the root View + ViewCompat.setNestedScrollingEnabled(it, true) + } + }, + // ... + ) + } +} + +private class NestedScrollInteropAdapter : + Adapter() { + val items = (1..10).map { it.toString() } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): NestedScrollInteropViewHolder { + return NestedScrollInteropViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.list_item, parent, false) + ) + } + + override fun onBindViewHolder(holder: NestedScrollInteropViewHolder, position: Int) { + // ... + } + + class NestedScrollInteropViewHolder(view: View) : ViewHolder(view) { + fun bind(item: String) { + // ... + } + } + // [START_EXCLUDE] + override fun getItemCount(): Int = items.size + // [END_EXCLUDE] +} +// [END android_compose_touchinput_scroll_nested_scroll_interop_compose_parent] + +// [START android_compose_touchinput_scroll_view_in_compose_interop] +@Composable +fun ViewInComposeNestedScrollInteropExample() { + Box( + Modifier + .fillMaxSize() + .scrollable( + rememberScrollableState { + // View component deltas should be reflected in Compose + // components that participate in nested scrolling + it + }, + Orientation.Vertical + ) + ) { + AndroidView( + { context -> + LayoutInflater.from(context) + .inflate(R.layout.list_item, null) + .apply { + // Nested scrolling interop is enabled when + // nested scroll is enabled for the root View + ViewCompat.setNestedScrollingEnabled(this, true) + } + } + ) + } +} +// [END android_compose_touchinput_scroll_view_in_compose_interop] + +// [START android_compose_touchinput_scroll_bottom_sheet_interop] +class BottomSheetFragment : BottomSheetDialogFragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val rootView: View = inflater.inflate(R.layout.fragment_bottom_sheet, container, false) + + rootView.findViewById(R.id.compose_view).apply { + setContent { + val nestedScrollInterop = rememberNestedScrollInteropConnection() + LazyColumn( + Modifier + .nestedScroll(nestedScrollInterop) + .fillMaxSize() + ) { + item { + Text(text = "Bottom sheet title") + } + items(10) { + Text( + text = "List item number $it", + modifier = Modifier.fillMaxWidth() + ) + } + } + } + return rootView + } + } +} +// [END android_compose_touchinput_scroll_bottom_sheet_interop] + +private val ToolbarHeight = 48.dp diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt deleted file mode 100644 index 0bfa65327..000000000 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/scroll/NestedScrollModifiers.kt +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2026 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.compose.snippets.touchinput.scroll - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.rememberScrollableState -import androidx.compose.foundation.gestures.scrollable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.rememberNestedScrollInteropConnection -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.view.ViewCompat -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.Adapter -import androidx.recyclerview.widget.RecyclerView.VERTICAL -import androidx.recyclerview.widget.RecyclerView.ViewHolder -import com.example.compose.snippets.R -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import kotlin.math.roundToInt - -@OptIn(ExperimentalMaterial3Api::class) -// [START android_compose_touchinput_scroll_nested_scroll_interop_compose_parent] -@Composable -private fun NestedScrollInteropComposeParentWithAndroidChildExample() { - val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() } - val toolbarOffsetHeightPx = remember { mutableStateOf(0f) } - - // Sets up the nested scroll connection between the Box composable parent - // and the child AndroidView containing the RecyclerView - val nestedScrollConnection = remember { - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - // Updates the toolbar offset based on the scroll to enable - // collapsible behaviour - val delta = available.y - val newOffset = toolbarOffsetHeightPx.value + delta - toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f) - return Offset.Zero - } - } - } - - Box( - Modifier - .fillMaxSize() - .nestedScroll(nestedScrollConnection) - ) { - TopAppBar( - title = {}, - modifier = Modifier - .height(ToolbarHeight) - .offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) } - ) - - AndroidView( - { context -> - LayoutInflater.from(context) - .inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply { - with(findViewById(R.id.main_list)) { - layoutManager = LinearLayoutManager(context, VERTICAL, false) - adapter = NestedScrollInteropAdapter() - } - }.also { - // Nested scrolling interop is enabled when - // nested scroll is enabled for the root View - ViewCompat.setNestedScrollingEnabled(it, true) - } - }, - // ... - ) - } -} - -private class NestedScrollInteropAdapter : - Adapter() { - val items = (1..10).map { it.toString() } - - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): NestedScrollInteropViewHolder { - return NestedScrollInteropViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.list_item, parent, false) - ) - } - - override fun onBindViewHolder(holder: NestedScrollInteropViewHolder, position: Int) { - // ... - } - - class NestedScrollInteropViewHolder(view: View) : ViewHolder(view) { - fun bind(item: String) { - // ... - } - } - // [START_EXCLUDE] - override fun getItemCount(): Int = items.size - // [END_EXCLUDE] -} -// [END android_compose_touchinput_scroll_nested_scroll_interop_compose_parent] - -// [START android_compose_touchinput_scroll_view_in_compose_interop] -@Composable -fun ViewInComposeNestedScrollInteropExample() { - Box( - Modifier - .fillMaxSize() - .scrollable( - rememberScrollableState { - // View component deltas should be reflected in Compose - // components that participate in nested scrolling - it - }, - Orientation.Vertical - ) - ) { - AndroidView( - { context -> - LayoutInflater.from(context) - .inflate(R.layout.list_item, null) - .apply { - // Nested scrolling interop is enabled when - // nested scroll is enabled for the root View - ViewCompat.setNestedScrollingEnabled(this, true) - } - } - ) - } -} -// [END android_compose_touchinput_scroll_view_in_compose_interop] - -// [START android_compose_touchinput_scroll_bottom_sheet_interop] -class BottomSheetFragment : BottomSheetDialogFragment() { - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - val rootView: View = inflater.inflate(R.layout.fragment_bottom_sheet, container, false) - - rootView.findViewById(R.id.compose_view).apply { - setContent { - val nestedScrollInterop = rememberNestedScrollInteropConnection() - LazyColumn( - Modifier - .nestedScroll(nestedScrollInterop) - .fillMaxSize() - ) { - item { - Text(text = "Bottom sheet title") - } - items(10) { - Text( - text = "List item number $it", - modifier = Modifier.fillMaxWidth() - ) - } - } - } - return rootView - } - } -} -// [END android_compose_touchinput_scroll_bottom_sheet_interop] - -private val ToolbarHeight = 48.dp