스크롤 수정자
verticalScroll
및 horizontalScroll
수정자는 콘텐츠의 경계가 최대 크기 제약 조건보다 클 때 사용자가 요소를 스크롤할 수 있는 가장 간단한 방법을 제공합니다. verticalScroll
및 horizontalScroll
수정자를 사용하면 콘텐츠를 변환하거나 오프셋할 필요가 없습니다.
@Composable private fun ScrollBoxes() { Column( modifier = Modifier .background(Color.LightGray) .size(100.dp) .verticalScroll(rememberScrollState()) ) { repeat(10) { Text("Item $it", modifier = Modifier.padding(2.dp)) } } }
ScrollState
를 사용하면 스크롤 위치를 변경하거나 현재 상태를 가져올 수 있습니다. 기본 매개변수를 사용하여 만들려면 rememberScrollState()
를 사용하세요.
@Composable private fun ScrollBoxesSmooth() { // Smoothly scroll 100px on first composition val state = rememberScrollState() LaunchedEffect(Unit) { state.animateScrollTo(100) } Column( modifier = Modifier .background(Color.LightGray) .size(100.dp) .padding(horizontal = 8.dp) .verticalScroll(state) ) { repeat(10) { Text("Item $it", modifier = Modifier.padding(2.dp)) } } }
스크롤 가능한 수정자
scrollable
수정자는 스크롤 수정자와는 다릅니다. 즉, scrollable
은 스크롤 동작을 감지하고 대수를 캡처하지만 콘텐츠를 자동으로 오프셋하지 않습니다. 대신 이 수정자가 올바르게 작동하는 데 필요한 ScrollableState
를 통해 사용자에게 위임됩니다.
ScrollableState
를 구성할 때는 각 스크롤 단계에서 픽셀 단위 델타를 사용하여 (동작 입력, 부드러운 스크롤 또는 플링으로) 호출할 consumeScrollDelta
함수를 제공해야 합니다. 이 함수는 scrollable
수정자가 있는 중첩 요소가 있는 경우 이벤트가 올바르게 전파되도록 하기 위해 사용된 스크롤 거리를 반환해야 합니다.
다음 스니펫은 동작을 감지하고 오프셋의 숫자 값을 표시하지만 아무 요소도 오프셋하지 않습니다.
@Composable private fun ScrollableSample() { // actual composable state var offset by remember { mutableStateOf(0f) } Box( Modifier .size(150.dp) .scrollable( orientation = Orientation.Vertical, // Scrollable state: describes how to consume // scrolling delta and update offset state = rememberScrollableState { delta -> offset += delta delta } ) .background(Color.LightGray), contentAlignment = Alignment.Center ) { Text(offset.toString()) } }
중첩 스크롤
중첩 스크롤은 서로 내에 포함된 여러 스크롤 구성요소가 단일 스크롤 동작에 반응하고 스크롤 변화량 (변경사항)을 전달하여 함께 작동하는 시스템입니다.
중첩 스크롤 시스템을 사용하면 스크롤 가능하고 계층적으로 연결된 구성요소 간에 조정을 할 수 있습니다 (대부분 동일한 상위 요소를 공유함). 이 시스템은 스크롤 컨테이너를 연결하고 전파되고 공유되는 스크롤 델타와 상호작용할 수 있도록 합니다.
Compose는 컴포저블 간에 중첩 스크롤을 처리하는 여러 가지 방법을 제공합니다. 중첩 스크롤의 일반적인 예는 다른 목록 안에 있는 목록이며 더 복잡한 경우는 접기 방식 툴바입니다.
자동 중첩 스크롤
단순한 중첩 스크롤의 경우 개발자가 아무 조치를 취하지 않아도 됩니다. 스크롤 작업을 시작하는 동작은 하위 요소에서 상위 요소로 자동 전파됩니다. 따라서 하위 요소가 더 이상 스크롤할 수 없는 경우 상위 요소에 의해 동작이 처리됩니다.
자동 중첩 스크롤은 verticalScroll
, horizontalScroll
, scrollable
, Lazy
API 및 TextField
등 Compose의 일부 구성요소 및 수정자에 의해 즉시 지원 및 제공됩니다. 즉, 사용자가 중첩된 구성요소의 내부 하위 요소를 스크롤하면 이전 수정자가 중첩된 스크롤을 지원하는 상위 요소에 스크롤 델타를 전파합니다.
다음 예에서는 verticalScroll
수정자가 적용된 컨테이너 내부에 있는 verticalScroll
수정자가 적용된 요소를 보여줍니다.
@Composable private fun AutomaticNestedScroll() { val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White) Box( modifier = Modifier .background(Color.LightGray) .verticalScroll(rememberScrollState()) .padding(32.dp) ) { Column { repeat(6) { Box( modifier = Modifier .height(128.dp) .verticalScroll(rememberScrollState()) ) { Text( "Scroll here", modifier = Modifier .border(12.dp, Color.DarkGray) .background(brush = gradient) .padding(24.dp) .height(150.dp) ) } } } } }
nestedScroll
수정자 사용
여러 요소 간에 조정된 고급 스크롤을 만들어야 하는 경우 nestedScroll
수정자를 사용하면 중첩된 스크롤 계층 구조를 정의하여 더 유연하게 만들 수 있습니다. 이전 섹션에서 언급했듯이 일부 구성요소에는 중첩 스크롤 지원이 내장되어 있습니다. 그러나 Box
또는 Column
과 같이 자동으로 스크롤되지 않는 컴포저블의 경우 스크롤 델타가 중첩된 스크롤 시스템에서 전파되지 않고 델타가 NestedScrollConnection
또는 상위 구성요소에 도달하지 않습니다. 이 문제를 해결하려면 nestedScroll
을 사용하여 맞춤 구성요소 등 다른 구성요소에 이러한 지원을 부여할 수 있습니다.
중첩 스크롤 주기
중첩 스크롤 주기는 스크롤 가능한 구성요소 및 수정자 또는 nestedScroll
를 사용하여 중첩 스크롤 시스템의 일부인 모든 구성요소 (또는 노드)를 통해 계층 구조 트리를 위아래로 전달되는 스크롤 델타의 흐름입니다.
중첩 스크롤 주기의 단계
스크롤 가능한 구성요소에서 트리거 이벤트 (예: 동작)가 감지되면 실제 스크롤 작업이 트리거되기 전에 생성된 델타가 중첩 스크롤 시스템으로 전송되고 스크롤 전, 노드 사용, 스크롤 후의 세 단계를 거칩니다.
첫 번째 스크롤 전 단계에서 트리거 이벤트 델타를 수신한 구성요소는 계층 구조 트리를 통해 최상위 상위 요소로 이러한 이벤트를 전달합니다. 그러면 델타 이벤트가 아래로 번들링됩니다. 즉, 델타가 가장 루트인 상위 요소에서 중첩된 스크롤 주기를 시작한 하위 요소로 전파됩니다.
이렇게 하면 중첩된 스크롤 상위 요소 (nestedScroll
또는 스크롤 가능한 수정자를 사용하는 컴포저블)가 노드 자체에서 이를 사용할 수 있기 전에 델타로 작업할 수 있습니다.
노드 소비 단계에서 노드 자체는 상위 요소에서 사용하지 않은 모든 델타를 사용합니다. 스크롤 동작이 실제로 완료되고 표시되는 시점입니다.
이 단계에서 하위 요소는 남은 스크롤의 전부 또는 일부를 소비할 수 있습니다. 남은 부분은 스크롤 후 단계를 거치기 위해 다시 위로 전송됩니다.
마지막으로 스크롤 후 단계에서 노드 자체가 소비하지 않은 모든 항목이 소비를 위해 상위 요소로 다시 전송됩니다.
스크롤 후 단계는 스크롤 전 단계와 비슷한 방식으로 작동하며, 여기서 상위 요소는 소비 여부를 선택할 수 있습니다.
스크롤과 마찬가지로 드래그 동작이 완료되면 사용자의 의도가 스크롤 가능한 컨테이너를 플링 (애니메이션을 사용하여 스크롤)하는 데 사용되는 속도로 변환될 수 있습니다. 플링은 중첩된 스크롤 주기의 일부이기도 하며 드래그 이벤트에서 생성된 속도는 플링 전, 노드 소비, 플링 후와 같은 유사한 단계를 거칩니다. 플링 애니메이션은 터치 동작과만 연결되며 a11y 또는 하드웨어 스크롤과 같은 다른 이벤트에 의해 트리거되지 않습니다.
중첩 스크롤 주기에 참여
주기에 참여한다는 것은 계층 구조를 따라 델타의 소비를 가로채고 소비하고 보고하는 것을 의미합니다. Compose는 중첩 스크롤 시스템의 작동 방식과 이 시스템과 직접 상호작용하는 방법에 영향을 미치는 도구 모음을 제공합니다. 예를 들어 스크롤 가능한 구성요소가 스크롤을 시작하기 전에 스크롤 델타로 작업을 해야 하는 경우를 들 수 있습니다.
중첩된 스크롤 주기가 노드 체인에서 작동하는 시스템인 경우 nestedScroll
수정자는 이러한 변경사항을 가로채고 삽입하며 체인에서 전파되는 데이터 (스크롤 델타)에 영향을 미치는 방법입니다. 이 수정자는 계층 구조의 어느 위치에나 배치할 수 있으며, 이 채널을 통해 정보를 공유할 수 있도록 트리 위로 중첩된 스크롤 수정자 인스턴스와 통신합니다. 이 수정자의 구성요소는 NestedScrollConnection
및 NestedScrollDispatcher
입니다.
NestedScrollConnection
는 중첩 스크롤 주기의 단계에 응답하고 중첩 스크롤 시스템에 영향을 미치는 방법을 제공합니다. 4개의 콜백 메서드로 구성되며, 각각 소비 단계 중 하나인 스크롤 전/후 및 플링 전/후를 나타냅니다.
val nestedScrollConnection = object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { println("Received onPreScroll callback.") return Offset.Zero } override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { println("Received onPostScroll callback.") return Offset.Zero } }
각 콜백은 전파되는 델타에 관한 정보(특정 단계의 available
델타, 이전 단계에서 소비된 consumed
델타)도 제공합니다. 언제든지 계층 구조 위로 대수의 전파를 중지하려면 중첩된 스크롤 연결을 사용하면 됩니다.
val disabledNestedScrollConnection = remember { object : NestedScrollConnection { override fun onPostScroll( consumed: Offset, available: Offset, source: NestedScrollSource ): Offset { return if (source == NestedScrollSource.SideEffect) { available } else { Offset.Zero } } } }
모든 콜백은 NestedScrollSource
유형에 관한 정보를 제공합니다.
NestedScrollDispatcher
는 중첩된 스크롤 주기를 초기화합니다. 디스패처를 사용하고 메서드를 호출하면 주기가 트리거됩니다. 스크롤 가능한 컨테이너에는 동작 중에 캡처된 델타를 시스템으로 전송하는 디스패처가 내장되어 있습니다. 이러한 이유로 중첩 스크롤을 맞춤설정하는 대부분의 사용 사례에서는 디스패처 대신 NestedScrollConnection
를 사용하여 새 델타를 전송하는 대신 이미 존재하는 델타에 반응합니다.
자세한 사용법은 NestedScrollDispatcherSample
를 참고하세요.
스크롤 시 이미지 크기 조절
사용자가 스크롤할 때 스크롤 위치에 따라 이미지 크기가 변경되는 동적 시각 효과를 만들 수 있습니다.
스크롤 위치에 따라 이미지 크기 조절
이 스니펫은 세로 스크롤 위치를 기반으로 LazyColumn
내에서 이미지 크기를 조절하는 방법을 보여줍니다. 사용자가 아래로 스크롤하면 이미지가 줄어들고 위로 스크롤하면 커지면서 정의된 최소 및 최대 크기 경계 내에 유지됩니다.
@Composable fun ImageResizeOnScrollExample( modifier: Modifier = Modifier, maxImageSize: Dp = 300.dp, minImageSize: Dp = 100.dp ) { var currentImageSize by remember { mutableStateOf(maxImageSize) } var imageScale by remember { mutableFloatStateOf(1f) } val nestedScrollConnection = remember { object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { // Calculate the change in image size based on scroll delta val delta = available.y val newImageSize = currentImageSize + delta.dp val previousImageSize = currentImageSize // Constrain the image size within the allowed bounds currentImageSize = newImageSize.coerceIn(minImageSize, maxImageSize) val consumed = currentImageSize - previousImageSize // Calculate the scale for the image imageScale = currentImageSize / maxImageSize // Return the consumed scroll amount return Offset(0f, consumed.value) } } } Box(Modifier.nestedScroll(nestedScrollConnection)) { LazyColumn( Modifier .fillMaxWidth() .padding(15.dp) .offset { IntOffset(0, currentImageSize.roundToPx()) } ) { // Placeholder list items items(100, key = { it }) { Text( text = "Item: $it", style = MaterialTheme.typography.bodyLarge ) } } Image( painter = ColorPainter(Color.Red), contentDescription = "Red color image", Modifier .size(maxImageSize) .align(Alignment.TopCenter) .graphicsLayer { scaleX = imageScale scaleY = imageScale // Center the image vertically as it scales translationY = -(maxImageSize.toPx() - currentImageSize.toPx()) / 2f } ) } }
코드 관련 핵심 사항
- 이 코드는
NestedScrollConnection
를 사용하여 스크롤 이벤트를 가로챕니다. onPreScroll
는 스크롤 변화량을 기반으로 이미지 크기의 변화를 계산합니다.currentImageSize
상태 변수는 이미지의 현재 크기를 저장하며,minImageSize
과maxImageSize. imageScale
사이에서 제약되며currentImageSize
에서 파생됩니다.currentImageSize
를 기반으로 하는LazyColumn
오프셋입니다.Image
는graphicsLayer
수정자를 사용하여 계산된 크기를 적용합니다.graphicsLayer
내의translationY
는 이미지가 크기가 조정될 때 세로로 중앙에 유지되도록 합니다.
결과
위 스니펫을 사용하면 스크롤 시 이미지가 확대/축소됩니다.
중첩 스크롤 상호 운용성
스크롤 가능한 컴포저블에 스크롤 가능한 View
요소를 중첩하려고 시도하거나 그 반대를 시도하는 경우 문제가 발생할 수 있습니다. 하위 요소를 스크롤하여 시작 또는 끝 경계에 도달한 후 상위 요소가 스크롤을 넘겨받을 것으로 예상하는 시점에 가장 눈에 띄는 문제가 발생합니다. 이 예상 동작은 발생하지 않거나 예상대로 작동하지 않을 수 있습니다.
이 문제는 스크롤 가능한 컴포저블에 내장된 예상 동작의 결과입니다.
스크롤 가능한 컴포저블에는 'nested-scroll-by-default' 규칙이 있습니다. 이 규칙은 모든 스크롤 가능한 컨테이너는 NestedScrollConnection
을 통해 상위 요소로서, 그리고 동시에 NestedScrollDispatcher
를 통해 하위 요소로서 중첩된 스크롤 체인에 참여해야 한다는 것입니다.
하위 요소는 하위 요소가 경계에 있을 때 상위 요소를 위해 중첩된 스크롤을 실행합니다. 일례로 이 규칙은 Compose Pager
와 Compose LazyRow
가 함께 작동할 수 있도록 지원합니다. 그러나 ViewPager2
또는 RecyclerView
를 사용하여 상호 운용성 스크롤이 실행되는 경우 이 둘은 NestedScrollingParent3
을 구현하지 않으므로 하위 요소에서 상위 요소로의 연속 스크롤이 가능하지 않습니다.
스크롤 가능한 View
요소와 스크롤 가능한 컴포저블 간에 양쪽 방향으로 중첩된 중첩 스크롤 상호 운용성 API를 사용하도록 설정하려면 다음과 같은 시나리오에서 중첩 스크롤 상호 운용성 API를 사용하여 이러한 문제를 완화할 수 있습니다.
하위 ComposeView
를 포함하는 협력 상위 View
협력 상위 View
란 이미 NestedScrollingParent3
를 구현하고 있기 때문에 중첩된 협력 하위 컴포저블에서 스크롤 델타를 수신할 수 있는 뷰입니다. 이때 ComposeView
가 하위 요소로 기능하므로 (간접적으로) NestedScrollingChild3
를 구현해야 합니다.
협력 상위 요소의 한 가지 예로 androidx.coordinatorlayout.widget.CoordinatorLayout
을 들 수 있습니다.
스크롤 가능한 View
상위 컨테이너와 중첩된 스크롤 가능 하위 컴포저블 간에 중첩 스크롤 상호 운용성이 필요한 경우 rememberNestedScrollInteropConnection()
을 사용할 수 있습니다.
rememberNestedScrollInteropConnection()
은 NestedScrollingParent3
를 구현하는 View
상위 요소와 Compose 하위 요소 간의 중첩 스크롤 상호 운용성을 지원하는 NestedScrollConnection
을 허용하고 기억합니다. 이는 nestedScroll
수정자와 함께 사용해야 합니다. 중첩 스크롤이 Compose 쪽에서 기본적으로 사용 설정되므로 이 연결을 사용하여 View
쪽에서 중첩 스크롤을 사용 설정하고 Views
와 컴포저블 간에 필요한 연결 로직을 추가할 수 있습니다.
자주 사용되는 사용 사례로 다음 예에서와 같이 CoordinatorLayout
, CollapsingToolbarLayout
, 하위 컴포저블을 사용하는 경우를 들 수 있습니다.
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="100dp" android:fitsSystemWindows="true"> <com.google.android.material.appbar.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <!--...--> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view" app:layout_behavior="@string/appbar_scrolling_view_behavior" android:layout_width="match_parent" android:layout_height="match_parent"/> </androidx.coordinatorlayout.widget.CoordinatorLayout>
활동 또는 프래그먼트에서 하위 컴포저블과 필수 NestedScrollConnection
을 설정해야 합니다.
open class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<ComposeView>(R.id.compose_view).apply { setContent { val nestedScrollInterop = rememberNestedScrollInteropConnection() // Add the nested scroll connection to your top level @Composable element // using the nestedScroll modifier. LazyColumn(modifier = Modifier.nestedScroll(nestedScrollInterop)) { items(20) { item -> Box( modifier = Modifier .padding(16.dp) .height(56.dp) .fillMaxWidth() .background(Color.Gray), contentAlignment = Alignment.Center ) { Text(item.toString()) } } } } } } }
하위 AndroidView
를 포함하는 상위 컴포저블
이 시나리오에서는 하위 AndroidView
를 포함하는 상위 컴포저블이 있는 경우 Compose 쪽에서 중첩 스크롤 상호 운용성 API가 구현되는 사례를 다룹니다. AndroidView
는 Compose 스크롤 상위 요소의 하위 요소로 기능하는 NestedScrollDispatcher
와 View
스크롤 하위 요소의 상위 요소로 기능하는 NestedScrollingParent3
를 구현합니다. 그러면 Compose 상위 요소가 스크롤 가능한 중첩 하위 View
로부터 중첩 스크롤 델타를 수신할 수 있습니다.
다음 예에서는 Compose 접기 툴바와 함께 이 시나리오에서 중첩 스크롤 상호 운용성을 달성하는 방법을 보여줍니다.
@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(
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<RecyclerView>(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<NestedScrollInteropAdapter.NestedScrollInteropViewHolder>() {
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) {
// ...
}
}
// ...
}
다음 예에서는 scrollable
수정자를 사용하여 API를 사용하는 방법을 보여줍니다.
@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(android.R.layout.list_item, null)
.apply {
// Nested scrolling interop is enabled when
// nested scroll is enabled for the root View
ViewCompat.setNestedScrollingEnabled(this, true)
}
}
)
}
}
마지막으로, 다음 예에서는 BottomSheetDialogFragment
와 함께 중첩 스크롤 상호 운용성 API를 사용하여 성공적인 드래그 및 닫기 동작을 달성하는 방법을 보여줍니다.
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<ComposeView>(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
}
}
}
rememberNestedScrollInteropConnection()
은 사용자가 이 연결을 적용한 요소에 NestedScrollConnection
을 설치합니다. NestedScrollConnection
는 Compose 수준에서 View
수준으로 델타를 전송하는 작업을 담당합니다. 이에 따라 요소가 중첩 스크롤에 참여할 수 있게 되나 요소의 스크롤을 자동으로 사용하도록 설정하지 않습니다. Box
또는 Column
과 같이 자동으로 스크롤되지 않는 컴포저블의 경우 스크롤 델타가 중첩된 스크롤 시스템에서 전파되지 않고 델타가 rememberNestedScrollInteropConnection()
에 의해 제공된 NestedScrollConnection
에 도달하지 않으므로 상위 View
구성요소에 도달하지 않습니다. 이 문제를 해결하려면 스크롤 가능한 수정자 또한 이러한 유형의 중첩된 컴포저블로 설정해야 합니다. 자세한 내용은 중첩 스크롤에 관한 위의 섹션을 참고하세요.
하위 ComposeView
를 포함하는 비협력 상위 View
비협력 뷰란 View
쪽에서 필요한 NestedScrolling
인터페이스를 구현하지 않는 뷰를 가리킵니다. 즉, 이 Views
와의 중첩 스크롤 상호 운용성은 추가 설정 없이는 작동하지 않습니다. 비협력 Views
는 RecyclerView
와 ViewPager2
입니다.
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- 동작 이해하기
CoordinatorLayout
를 Compose로 이전- Compose에서 뷰 사용