Desplázate

Modificadores de desplazamiento

Los selectores verticalScroll y horizontalScroll proporcionan la forma más sencilla de permitir que el usuario se desplace por un elemento cuando los límites del contenido sean más grandes que las restricciones de tamaño máximo. Con los modificadores verticalScroll y horizontalScroll, no necesitas traducir ni compensar el contenido.

@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))
        }
    }
}

Una lista vertical simple que responde a los gestos de desplazamiento

ScrollState te permite cambiar la posición del desplazamiento u obtener su estado actual. Para crearlo con los parámetros predeterminados, usa 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))
        }
    }
}

Modificador desplazable

El modificador scrollable difiere de los modificadores de desplazamiento porque scrollable detecta los gestos de desplazamiento y captura los deltas, pero no desplaza su contenido automáticamente. En cambio, se delega al usuario a través de ScrollableState, que es necesario para que este modificador funcione correctamente.

Cuando construyas ScrollableState, debes proporcionar una función consumeScrollDelta que se invoque en cada paso de desplazamiento (con entrada de gestos, desplazamiento suave o arrastrar y soltar) con el delta en píxeles. Esta función debe devolver la cantidad de distancia de desplazamiento consumida para garantizar que el evento se propague correctamente en los casos en que haya elementos anidados que tengan el modificador scrollable.

En el siguiente fragmento, se detectan los gestos y se muestra un valor numérico para un desplazamiento, pero no se desplaza ningún elemento:

@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())
    }
}

Un elemento de IU que detecta la presión del dedo y muestra el valor numérico de la ubicación del dedo

Desplazamiento anidado

El desplazamiento anidado es un sistema en el que varios componentes de desplazamiento contenidos entre sí funcionan en conjunto reaccionando a un solo gesto de desplazamiento y comunicando sus deltas (cambios) de desplazamiento.

El sistema de desplazamiento anidado permite la coordinación entre los componentes que se pueden desplazar y están vinculados de forma jerárquica (la mayoría de las veces, porque comparten el mismo elemento superior). Este sistema vincula los contenedores de desplazamiento y permite la interacción con los deltas de desplazamiento que se propagan y comparten entre ellos.

Compose proporciona varias formas de controlar el desplazamiento anidado entre elementos componibles. Un ejemplo típico de desplazamiento anidado es una lista dentro de otra, y un caso más complejo es una barra de herramientas que se puede contraer.

Desplazamiento anidado automático

El desplazamiento anidado simple no requiere ninguna acción de tu parte. Los gestos que inician una acción de desplazamiento se propagan de elementos secundarios a superiores de forma automática, de modo que cuando el elemento secundario no puede desplazarse más, se controla el gesto con el elemento superior.

El desplazamiento automático anidado es compatible y proporcionado de forma predeterminada por algunos de los componentes y modificadores de Compose: verticalScroll, horizontalScroll, scrollable, las APIs de Lazy y TextField. Esto significa que cuando el usuario se desplaza por un elemento secundario interno de componentes anidados, los modificadores anteriores propagan los deltas de desplazamiento a los elementos superiores que son compatibles con el desplazamiento anidado.

En el siguiente ejemplo, se muestran los elementos con un modificador verticalScroll aplicado dentro de un contenedor que también tiene un modificador verticalScroll aplicado.

@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)
                    )
                }
            }
        }
    }
}

Dos elementos de la IU de desplazamiento vertical anidados que responden a gestos dentro y fuera del elemento interno

Cómo usar el modificador nestedScroll

Si necesitas crear un desplazamiento coordinado avanzado entre varios elementos, el modificador nestedScroll te brinda más flexibilidad definiendo una jerarquía de desplazamiento anidada. Como se mencionó en la sección anterior, algunos componentes tienen compatibilidad integrada con el desplazamiento anidado. Sin embargo, en el caso de los elementos componibles que no se pueden desplazar automáticamente, como Box o Column, los deltas de desplazamiento en esos componentes no se propagarán en el sistema de desplazamiento anidado y los deltas no llegarán al NestedScrollConnection ni al componente superior. Para resolver este problema, puedes usar nestedScroll para conferir esa compatibilidad a otros componentes, incluidos los personalizados.

Ciclo de desplazamiento anidado

El ciclo de desplazamiento anidado es el flujo de deltas de desplazamiento que se envían hacia arriba y hacia abajo en el árbol de jerarquía a través de todos los componentes (o nodos) que forman parte del sistema de desplazamiento anidado, por ejemplo, con componentes y modificadores desplazables, o nestedScroll.

Fases del ciclo de desplazamiento anidado

Cuando un componente desplazable detecta un evento de activación (por ejemplo, un gesto), antes de que se active la acción de desplazamiento real, los deltas generados se envían al sistema de desplazamiento anidado y pasan por tres fases: predesplazamiento, consumo de nodos y posdesplazamiento.

Fases del ciclo de desplazamiento anidado

En la primera fase previa al desplazamiento, el componente que recibió los deltas del evento de activación enviará esos eventos hacia arriba, a través del árbol de jerarquía, hasta el elemento superior más alto. Luego, los eventos delta se propagarán hacia abajo, lo que significa que los deltas se propagarán desde el elemento superior más raíz hacia el elemento secundario que inició el ciclo de desplazamiento anidado.

Fase previa al desplazamiento: Envío

Esto les da a los elementos superiores de desplazamiento anidado (elementos componibles que usan nestedScroll o modificadores desplazables) la oportunidad de hacer algo con el delta antes de que el nodo en sí pueda consumirlo.

Fase previa al desplazamiento: propagación hacia abajo

En la fase de consumo del nodo, el nodo en sí usará cualquier delta que no hayan usado sus nodos superiores. Este es el momento en que se realiza y se ve el movimiento de desplazamiento.

Fase de consumo de nodos

Durante esta fase, el niño o la niña puede elegir consumir todo el desplazamiento restante o solo una parte. Todo lo que quede se enviará de nuevo para que pase por la fase posterior al desplazamiento.

Por último, en la fase posterior al desplazamiento, todo lo que el nodo en sí no consumió se enviará nuevamente a sus ancestros para que lo consuman.

Fase posterior al desplazamiento: Envío

La fase posterior al desplazamiento funciona de manera similar a la fase previa al desplazamiento, en la que cualquiera de los elementos superiores puede optar por consumir o no.

Fase posterior al desplazamiento: burbujeo hacia abajo

De manera similar al desplazamiento, cuando finaliza un gesto de arrastre, la intención del usuario puede traducirse en una velocidad que se usa para desplazar (desplazarse con una animación) el contenedor desplazable. El movimiento de arrastre también forma parte del ciclo de desplazamiento anidado, y las velocidades generadas por el evento de arrastre pasan por fases similares: previa al movimiento de arrastre, consumo de nodos y posterior al movimiento de arrastre. Ten en cuenta que la animación de lanzamiento solo se asocia con el gesto táctil y no se activará con otros eventos, como el desplazamiento de hardware o de accesibilidad.

Participa en el ciclo de desplazamiento anidado

La participación en el ciclo implica interceptar, consumir y registrar el consumo de deltas a lo largo de la jerarquía. Compose proporciona un conjunto de herramientas para influir en el funcionamiento del sistema de desplazamiento anidado y en cómo interactuar directamente con él, por ejemplo, cuando necesitas hacer algo con los deltas de desplazamiento antes de que un componente desplazable comience a desplazarse.

Si el ciclo de desplazamiento anidado es un sistema que actúa sobre una cadena de nodos, el modificador nestedScroll es una forma de interceptar e insertar estos cambios, y de influir en los datos (deltas de desplazamiento) que se propagan en la cadena. Este modificador se puede colocar en cualquier lugar de la jerarquía y se comunica con las instancias de modificadores de desplazamiento anidados hacia arriba en el árbol para poder compartir información a través de este canal. Los componentes básicos de este modificador son NestedScrollConnection y NestedScrollDispatcher.

NestedScrollConnection proporciona una forma de responder a las fases del ciclo de desplazamiento anidado y de influir en el sistema de desplazamiento anidado. Se compone de cuatro métodos de devolución de llamada, cada uno de los cuales representa una de las fases de consumo: antes y después del desplazamiento, y antes y después del deslizamiento:

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
    }
}

Cada devolución de llamada también proporciona información sobre el delta que se propaga: delta de available para esa fase en particular y delta de consumed consumido en las fases anteriores. Si en algún momento quieres dejar de propagar deltas hacia arriba en la jerarquía, puedes usar la conexión de desplazamiento anidado para hacerlo:

val disabledNestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPostScroll(
            consumed: Offset,
            available: Offset,
            source: NestedScrollSource
        ): Offset {
            return if (source == NestedScrollSource.SideEffect) {
                available
            } else {
                Offset.Zero
            }
        }
    }
}

Todas las devoluciones de llamada proporcionan información sobre el tipo de NestedScrollSource.

NestedScrollDispatcher inicializa el ciclo de desplazamiento anidado. El ciclo se activa cuando se usa un dispatcher y se llaman sus métodos. Los contenedores desplazables tienen un distribuidor integrado que envía al sistema los deltas capturados durante los gestos. Por este motivo, la mayoría de los casos de uso de personalización del desplazamiento anidado implican el uso de NestedScrollConnection en lugar de un dispatcher, para reaccionar a los deltas ya existentes en lugar de enviar otros nuevos. Consulta NestedScrollDispatcherSample para obtener más usos.

Cambiar el tamaño de una imagen cuando se desplaza

A medida que el usuario se desplaza, puedes crear un efecto visual dinámico en el que la imagen cambia de tamaño según la posición de desplazamiento.

Cómo cambiar el tamaño de una imagen según la posición de desplazamiento

En este fragmento, se muestra cómo cambiar el tamaño de una imagen dentro de un LazyColumn según la posición de desplazamiento vertical. La imagen se reduce a medida que el usuario se desplaza hacia abajo y se agranda a medida que se desplaza hacia arriba, y permanece dentro de los límites de tamaño mínimo y máximo definidos:

@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
                }
        )
    }
}

Puntos clave sobre el código

  • Este código usa un NestedScrollConnection para interceptar eventos de desplazamiento.
  • onPreScroll calcula el cambio en el tamaño de la imagen en función del delta de desplazamiento.
  • La variable de estado currentImageSize almacena el tamaño actual de la imagen, que se limita entre minImageSize y maxImageSize. imageScale, y se deriva de currentImageSize.
  • El desplazamiento de LazyColumn se basa en currentImageSize.
  • El Image usa un modificador graphicsLayer para aplicar la escala calculada.
  • El translationY dentro del graphicsLayer garantiza que la imagen permanezca centrada verticalmente a medida que se ajusta su escala.

Resultado

El fragmento anterior genera un efecto de imagen de escalamiento al desplazarse:

Figura 1. Efecto de ajuste de escala de la imagen al desplazarse.

Interoperabilidad de desplazamiento anidado

Cuando intentas anidar elementos View desplazables en elementos componibles desplazables, o viceversa, es posible que encuentres problemas. Los más notables suceden cuando te desplazas por el elemento secundario y alcanzas sus límites de inicio o finalización, y esperas que el elemento superior se desplace por él. Sin embargo, es posible que este comportamiento esperado no suceda o no funcione como se espera.

Este problema es el resultado de las expectativas que se compilan en elementos componibles desplazables. Los elementos componibles desplazables tienen una regla de "desplazamiento anidado predeterminado", lo que significa que cualquier contenedor desplazable debe participar en la cadena de desplazamiento anidado, ambos como elemento superior mediante NestedScrollConnection y como elemento secundario a través de NestedScrollDispatcher. Luego, el elemento secundario generaría un desplazamiento anidado para el elemento superior cuando el secundario esté en el límite. A modo de ejemplo, esta regla permite que Compose Pager y Compose LazyRow funcionen bien en conjunto. Sin embargo, cuando el desplazamiento de interoperabilidad se realiza con ViewPager2 o RecyclerView, ya que no se implementa NestedScrollingParent3, el desplazamiento continuo del elemento secundario al superior no es posible.

A fin de habilitar la API de interoperabilidad de desplazamiento anidada entre elementos View desplazables y elementos componibles desplazables, anidados en ambas direcciones, puedes usar la API de interoperabilidad de desplazamiento anidada para mitigar estos problemas en las siguientes situaciones.

Un elemento superior View cooperativo que contiene un elemento secundario ComposeView

Un elemento superior View cooperativo es el que ya implementa NestedScrollingParent3 y, por lo tanto, puede recibir deltas de desplazamiento de un elemento secundario cooperativo, anidado y componible. En este caso, ComposeView actuaría como elemento secundario y tendría que implementar NestedScrollingChild3 (de forma indirecta). Un ejemplo de un elemento superior cooperativo es androidx.coordinatorlayout.widget.CoordinatorLayout.

Si necesitas interoperabilidad de desplazamiento anidado entre contenedores superiores View desplazables y elementos componibles desplazables secundarios, puedes usar rememberNestedScrollInteropConnection().

rememberNestedScrollInteropConnection() permite y recuerda el NestedScrollConnection que permite la interoperabilidad de desplazamiento anidada entre un elemento superior de View que implementa NestedScrollingParent3 y un elemento secundario de Compose. Se debe usar junto con un modificador nestedScroll. Dado que el desplazamiento anidado está habilitado de forma predeterminada en Compose, puedes usar esta conexión para habilitar el desplazamiento anidado en el lado View y agregar la lógica de unión necesaria entre Views y los elementos componibles.

Un caso de uso frecuente consiste en implementar CoordinatorLayout, CollapsingToolbarLayout y un elemento secundario componible, como se muestra en este ejemplo:

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

En tu actividad o fragmento, debes configurar el elemento secundario que admite composición y el elemento NestedScrollConnection solicitado:

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())
                        }
                    }
                }
            }
        }
    }
}

Un elemento superior componible que contiene un elemento secundario AndroidView

En esta situación, se trata la implementación de la API de interoperabilidad de desplazamiento anidada en el lado de Compose, cuando tienes un elemento superior que admite composición y que contiene un AndroidView secundario. El AndroidView implementa NestedScrollDispatcher, ya que actúa como un elemento secundario de un elemento superior de desplazamiento de Compose, así como NestedScrollingParent3 porque actúa como superior para unView elemento secundario de desplazamiento. Luego, el elemento superior de Compose podrá recibir deltas de desplazamiento anidados desde un View secundario de desplazamiento anidado.

En el siguiente ejemplo, se muestra cómo lograr una interoperabilidad de desplazamiento anidada, en este caso, junto con una barra de herramientas que se contrae de 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) {
            // ...
        }
    }
    // ...
}

En este ejemplo, se muestra cómo usar la API con un modificador scrollable:

@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)
                    }
            }
        )
    }
}

Por último, en este ejemplo, se muestra cómo se usa la API de interoperabilidad de desplazamiento anidada con BottomSheetDialogFragment para lograr un comportamiento exitoso de arrastre y descarte:

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
        }
    }
}

Ten en cuenta que rememberNestedScrollInteropConnection() instalará un NestedScrollConnection en el elemento al que lo adjuntes. NestedScrollConnection es responsable de transmitir los deltas del nivel de Compose al nivel de View. Esto permite que el elemento participe en el desplazamiento anidado, pero no habilita el desplazamiento de elementos automáticamente. Para los elementos componibles que no se pueden desplazar automáticamente, como Box o Column, los deltas de desplazamiento en esos componentes no se propagarán en el sistema de desplazamiento anidado, y los deltas no llegarán al NestedScrollConnection proporcionado por rememberNestedScrollInteropConnection(), por lo tanto, esos deltas no llegarán al componente View principal. Para solucionar este problema, asegúrate de establecer modificadores desplazables en estos tipos de elementos componibles anidados. Puedes consultar la sección anterior sobre el desplazamiento anidado para obtener información más detallada.

Un View principal no cooperativo que contiene un ComposeView secundario

Una vista no cooperativa es la que no implementa las interfaces NestedScrolling necesarias del lado de View. Ten en cuenta que esto significa que la interoperabilidad de desplazamiento anidado con estos Views no funciona de forma inmediata. Los Views que no cooperativos son RecyclerView y ViewPager2.

Recursos adicionales