Scrollen

Scroll-Modifikatoren

Die Modifikatoren verticalScroll und horizontalScroll sind die einfachste Möglichkeit, dem Nutzer das Scrollen eines Elements zu ermöglichen, wenn die Grenzen des Inhalts größer sind als die maximalen Größenbeschränkungen. Mit den Modifikatoren verticalScroll und horizontalScroll müssen Sie die Inhalte nicht übersetzen oder versetzen.

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

Eine einfache vertikale Liste, die auf Scrollgesten reagiert

Mit ScrollState können Sie die Scrollposition ändern oder den aktuellen Status abrufen. Verwenden Sie zum Erstellen mit Standardparametern 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-Modifikator

Der Modifier scrollable unterscheidet sich von den Scroll-Modifikatoren dadurch, dass scrollable die Scroll-Gesten erkennt und die Deltas erfasst, aber den Inhalt nicht automatisch verschiebt. Stattdessen wird die Berechtigung über ScrollableState an den Nutzer delegiert. Das ist erforderlich, damit der Modifier richtig funktioniert.

Beim Erstellen von ScrollableState müssen Sie eine consumeScrollDelta-Funktion angeben, die bei jedem Scrollschritt (durch Gesteneingabe, sanftes Scrollen oder schnelles Wischen) mit dem Delta in Pixeln aufgerufen wird. Diese Funktion muss die zurückgelegte Scrollstrecke zurückgeben, damit das Ereignis richtig weitergegeben wird, wenn verschachtelte Elemente mit dem Modifikator scrollable vorhanden sind.

Im folgenden Snippet werden die Gesten erkannt und ein numerischer Wert für einen Offset angezeigt, es werden jedoch keine Elemente versetzt:

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

Ein UI-Element, das das Drücken des Fingers erkennt und den numerischen Wert für die Position des Fingers anzeigt.

Verschachteltes Scrollen

Beim verschachtelten Scrollen reagieren mehrere ineinander verschachtelte Scrollkomponenten auf eine einzelne Scrollbewegung und kommunizieren ihre Scroll-Deltas (Änderungen).

Das System für verschachteltes Scrollen ermöglicht die Koordination zwischen scrollbaren und hierarchisch verknüpften Komponenten (meistens durch die gemeinsame Nutzung desselben übergeordneten Elements). Dieses System verknüpft Scrolling-Container und ermöglicht die Interaktion mit den Scrolling-Deltas, die weitergegeben und geteilt werden.

Compose bietet mehrere Möglichkeiten, um verschachteltes Scrollen zwischen Composables zu verarbeiten. Ein typisches Beispiel für das verschachtelte Scrollen ist eine Liste in einer anderen Liste. Ein komplexeres Beispiel ist eine minimierbare Symbolleiste.

Automatisches verschachteltes Scrollen

Für einfaches verschachteltes Scrollen ist kein Handlungsbedarf Ihrerseits erforderlich. Gesten, die eine Scrollaktion auslösen, werden automatisch von untergeordneten an übergeordnete Elemente weitergegeben. Wenn das untergeordnete Element also nicht weiter gescrollt werden kann, wird die Geste vom übergeordneten Element verarbeitet.

Automatisches verschachteltes Scrollen wird von einigen Compose-Komponenten und ‑Modifikatoren unterstützt und ist standardmäßig verfügbar, z. B. bei verticalScroll, horizontalScroll, scrollable, Lazy-APIs und TextField. Wenn der Nutzer also ein untergeordnetes Element einer verschachtelten Komponente scrollt, werden die Scroll-Deltas durch die vorherigen Modifizierer an die übergeordneten Elemente weitergegeben, die verschachteltes Scrollen unterstützen.

Im folgenden Beispiel werden Elemente mit dem Modifikator verticalScroll in einem Container dargestellt, auf den ebenfalls ein verticalScroll-Modifikator angewendet wird.

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

Zwei verschachtelte vertikale UI-Elemente, die auf Touch-Gesten innerhalb und außerhalb des inneren Elements reagieren

nestedScroll-Modifikator verwenden

Wenn Sie einen erweiterten koordinierten Scrollvorgang zwischen mehreren Elementen erstellen müssen, bietet der Modifier nestedScroll mehr Flexibilität, da er eine verschachtelte Scrollhierarchie definiert. Wie im vorherigen Abschnitt erwähnt, unterstützen einige Komponenten das verschachtelte Scrollen. Bei Composables, die nicht automatisch gescrollt werden können, z. B. Box oder Column, werden die Scroll-Deltas für solche Komponenten jedoch nicht im verschachtelten Scrollsystem weitergegeben und erreichen weder NestedScrollConnection noch die übergeordnete Komponente. Um dieses Problem zu beheben, können Sie nestedScroll verwenden, um anderen Komponenten, einschließlich benutzerdefinierten Komponenten, diese Unterstützung zu gewähren.

Verschachtelter Scrollzyklus

Der verschachtelte Scrollzyklus ist der Fluss von Scroll-Deltas, die über alle Komponenten (oder Knoten) des verschachtelten Scrollsystems hinweg nach oben und unten im Hierarchiestrukturbaum verteilt werden, z. B. mithilfe von scrollbaren Komponenten und Modifizierern oder nestedScroll.

Phasen des verschachtelten Scrollvorgangs

Wenn ein Triggerereignis (z. B. eine Geste) von einer scrollbaren Komponente erkannt wird, bevor die eigentliche Scrollaktion ausgelöst wird, werden die generierten Deltas an das verschachtelte Scrollsytem gesendet und durchlaufen drei Phasen: Pre-Scroll, Node Consumption und Post-Scroll.

Phasen des verschachtelten Scrollvorgangs

In der ersten Phase vor dem Scrollen werden die Triggerereignis-Deltas von der Komponente, die das Triggerereignis empfangen hat, über den Hierarchiebaum an das oberste übergeordnete Element gesendet. Die Delta-Ereignisse werden dann nach unten weitergegeben, d. h., Deltas werden vom übergeordneten Element der obersten Ebene bis zum untergeordneten Element weitergegeben, das den verschachtelten Scrollzyklus gestartet hat.

Phase vor dem Scrollen – Dispatching nach oben

So können die verschachtelten Scroll-Parents (Composables mit nestedScroll oder scrollbaren Modifikatoren) etwas mit dem Delta tun, bevor der Knoten selbst es verarbeiten kann.

Phase vor dem Scrollen – Bubbling Down

In der Phase des Knotenverbrauchs verwendet der Knoten selbst das Delta, das nicht von seinen übergeordneten Elementen verwendet wurde. Hier wird die Scrollbewegung tatsächlich ausgeführt und ist sichtbar.

Phase der Knotennutzung

In dieser Phase kann das Kind den verbleibenden Scrollbereich ganz oder teilweise nutzen. Alles, was übrig bleibt, wird wieder nach oben gesendet, um die Phase nach dem Scrollen zu durchlaufen.

In der Phase nach dem Scrollen wird alles, was der Knoten selbst nicht verarbeitet hat, wieder an seine übergeordneten Elemente gesendet.

Phase nach dem Scrollen – Dispatching
nach oben

Die Post-Scroll-Phase funktioniert ähnlich wie die Pre-Scroll-Phase. Hier kann jedes der übergeordneten Elemente entscheiden, ob es das Signal verarbeitet oder nicht.

Phase nach dem Scrollen – Bubbling Down

Ähnlich wie beim Scrollen kann die Intention des Nutzers nach Abschluss einer Ziehbewegung in eine Geschwindigkeit umgewandelt werden, mit der der scrollbare Container animiert wird. Der Fling ist auch Teil des verschachtelten Scrollzyklus und die durch das Drag-Ereignis generierten Geschwindigkeiten durchlaufen ähnliche Phasen: Pre-Fling, Node Consumption und Post-Fling. Die Fling-Animation ist nur mit der Touch-Geste verknüpft und wird nicht durch andere Ereignisse wie A11Y oder Hardware-Scrollen ausgelöst.

Am Zyklus für verschachteltes Scrollen teilnehmen

Die Teilnahme am Zyklus bedeutet, dass Sie die Nutzung von Deltas entlang der Hierarchie abfangen, nutzen und darüber berichten. Compose bietet eine Reihe von Tools, mit denen Sie beeinflussen können, wie das System für verschachteltes Scrollen funktioniert und wie Sie direkt damit interagieren können. Das ist beispielsweise nützlich, wenn Sie etwas mit den Scroll-Deltas tun müssen, bevor eine scrollbare Komponente überhaupt mit dem Scrollen beginnt.

Wenn der verschachtelte Scrollzyklus ein System ist, das auf eine Kette von Knoten wirkt, ist der Modifier nestedScroll eine Möglichkeit, diese Änderungen abzufangen und einzufügen und die Daten (Scroll-Deltas) zu beeinflussen, die in der Kette weitergegeben werden. Dieser Modifikator kann an einer beliebigen Stelle in der Hierarchie platziert werden. Er kommuniziert mit verschachtelten Scrollmodifikator-Instanzen im Baum, um Informationen über diesen Kanal weiterzugeben. Die Bausteine dieses Modifikators sind NestedScrollConnection und NestedScrollDispatcher.

NestedScrollConnection bietet eine Möglichkeit, auf die Phasen des verschachtelten Scrollzyklus zu reagieren und das verschachtelte Scrollsystem zu beeinflussen. Sie besteht aus vier Callback-Methoden, die jeweils eine der Verbrauchsphasen darstellen: vor/nach dem Scrollen und vor/nach dem Fling:

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

Jeder Callback enthält auch Informationen zum weitergegebenen Delta: available-Delta für die jeweilige Phase und consumed-Delta, das in den vorherigen Phasen verwendet wurde. Wenn Sie die Weitergabe von Deltas in der Hierarchie beenden möchten, können Sie die verschachtelte Scrollverbindung verwenden:

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

Alle Callbacks enthalten Informationen zum NestedScrollSource-Typ.

Mit NestedScrollDispatcher wird der verschachtelte Scrollzyklus initialisiert. Der Zyklus wird durch die Verwendung eines Dispatchers und den Aufruf seiner Methoden ausgelöst. Scrollbare Container haben einen integrierten Dispatcher, der Deltas, die während Gesten erfasst werden, an das System sendet. Aus diesem Grund wird in den meisten Anwendungsfällen zum Anpassen des verschachtelten Scrollens NestedScrollConnection anstelle eines Dispatchers verwendet, um auf bereits vorhandene Deltas zu reagieren, anstatt neue zu senden. Weitere Anwendungsbeispiele finden Sie unter NestedScrollDispatcherSample.

Bildgröße beim Scrollen anpassen

Wenn der Nutzer scrollt, können Sie einen dynamischen visuellen Effekt erstellen, bei dem sich die Größe des Bildes je nach Scrollposition ändert.

Größe eines Bildes basierend auf der Scrollposition anpassen

In diesem Snippet wird gezeigt, wie ein Bild in einem LazyColumn basierend auf der vertikalen Scrollposition skaliert wird. Das Bild wird kleiner, wenn der Nutzer nach unten scrollt, und größer, wenn er nach oben scrollt. Dabei bleibt es innerhalb der festgelegten Mindest- und Höchstgröße:

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

Wichtige Punkte zum Code

  • In diesem Code wird ein NestedScrollConnection verwendet, um Scroll-Ereignisse abzufangen.
  • Mit onPreScroll wird die Änderung der Bildgröße basierend auf dem Scroll-Delta berechnet.
  • Die Statusvariable currentImageSize speichert die aktuelle Größe des Bildes, die zwischen minImageSize und maxImageSize. imageScale liegt und aus currentImageSize abgeleitet wird.
  • Die LazyColumn-Korrekturen basieren auf dem currentImageSize.
  • Für die Image wird der Modifikator graphicsLayer verwendet, um die berechnete Skalierung anzuwenden.
  • Durch die translationY innerhalb der graphicsLayer bleibt das Bild beim Skalieren vertikal zentriert.

Ergebnis

Das oben stehende Snippet führt zu einem Skalierungseffekt beim Scrollen:

Abbildung 1. Ein Skalierungsbildeffekt beim Scrollen.

Interop für verschachteltes Scrollen

Wenn Sie versuchen, scrollbare View-Elemente in scrollbaren Composables zu verschachteln oder umgekehrt, können Probleme auftreten. Am deutlichsten wird das, wenn Sie das untergeordnete Element scrollen und die Start- oder Endbegrenzung erreichen und erwarten, dass das übergeordnete Element das Scrollen übernimmt. Es kann jedoch sein, dass dieses erwartete Verhalten nicht eintritt oder nicht wie erwartet funktioniert.

Dieses Problem ist auf die Erwartungen zurückzuführen, die in scrollbaren Composables enthalten sind. Für scrollbare Composables gilt die Regel „nested-scroll-by-default“. Das bedeutet, dass jeder scrollbare Container an der verschachtelten Scrollkette beteiligt sein muss, sowohl als übergeordnetes Element über NestedScrollConnection als auch als untergeordnetes Element über NestedScrollDispatcher. Das untergeordnete Element würde dann einen verschachtelten Scrollvorgang für das übergeordnete Element auslösen, wenn es sich an der Grenze befindet. Mit dieser Regel können beispielsweise Compose Pager und Compose LazyRow gut zusammenarbeiten. Wenn jedoch mit ViewPager2 oder RecyclerView gescrollt wird, ist das kontinuierliche Scrollen vom untergeordneten zum übergeordneten Element nicht möglich, da diese NestedScrollingParent3 nicht implementieren.

Wenn Sie die API für die Interoperabilität von verschachteltem Scrollen zwischen scrollbaren View-Elementen und scrollbaren Composables aktivieren möchten, die in beide Richtungen verschachtelt sind, können Sie die API für die Interoperabilität von verschachteltem Scrollen in den folgenden Szenarien verwenden, um diese Probleme zu beheben.

Ein kooperierender Elternteil View mit einem Kind ComposeView

Ein kooperierendes Eltern-Composable View implementiert bereits NestedScrollingParent3 und kann daher Scrolling-Deltas von einem kooperierenden verschachtelten untergeordneten Composable empfangen. ComposeView würde in diesem Fall als Kind agieren und NestedScrollingChild3 (indirekt) implementieren müssen. Ein Beispiel für einen kooperierenden Elternteil ist androidx.coordinatorlayout.widget.CoordinatorLayout.

Wenn Sie die Interoperabilität von verschachteltem Scrolling zwischen scrollbaren View-Übergeordneten-Containern und verschachtelten scrollbaren untergeordneten Composables benötigen, können Sie rememberNestedScrollInteropConnection() verwenden.

rememberNestedScrollInteropConnection() ermöglicht und speichert die NestedScrollConnection, die die Interoperabilität von verschachtelten Scrollvorgängen zwischen einem View-Übergeordneten, der NestedScrollingParent3 und einem Compose-Untergeordneten implementiert, ermöglicht. Dieser Parameter sollte in Verbindung mit dem Modifikator nestedScroll verwendet werden. Da das verschachtelte Scrollen auf der Compose-Seite standardmäßig aktiviert ist, können Sie diese Verbindung verwenden, um das verschachtelte Scrollen auf der View-Seite zu aktivieren und die erforderliche Glue-Logik zwischen Views und Composables hinzuzufügen.

Ein häufiger Anwendungsfall ist die Verwendung von CoordinatorLayout, CollapsingToolbarLayout und einer untergeordneten Composable-Funktion, wie in diesem Beispiel gezeigt:

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

In Ihrer Aktivität oder Ihrem Fragment müssen Sie das untergeordnete Composable und die erforderliche NestedScrollConnection einrichten:

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

Ein übergeordnetes Composable, das ein untergeordnetes AndroidView enthält

In diesem Szenario geht es um die Implementierung der Nested Scrolling Interop API auf der Compose-Seite, wenn Sie eine übergeordnete Composable mit einem untergeordneten AndroidView haben. Die AndroidView implementiert NestedScrollDispatcher, da sie als untergeordnetes Element eines Compose-Scrolling-übergeordneten Elements fungiert, sowie NestedScrollingParent3, da sie als übergeordnetes Element eines View-Scrolling-untergeordneten Elements fungiert. Der übergeordnete Compose-Container kann dann verschachtelte Scroll-Deltas von einem verschachtelten scrollbaren untergeordneten View empfangen.

Das folgende Beispiel zeigt, wie Sie in diesem Szenario die Interoperabilität von verschachteltem Scrollen zusammen mit einer zusammenklappbaren Toolbar in Compose erreichen können:

@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) {
            // ...
        }
    }
    // ...
}

In diesem Beispiel wird gezeigt, wie Sie die API mit dem Modifikator scrollable verwenden können:

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

Dieses Beispiel zeigt, wie die API für die Interoperabilität von verschachteltem Scrollen mit BottomSheetDialogFragment verwendet wird, um ein erfolgreiches Drag-and-Dismiss-Verhalten zu erzielen:

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

Beachten Sie, dass mit rememberNestedScrollInteropConnection() ein NestedScrollConnection in das Element eingefügt wird, an das Sie es anhängen. NestedScrollConnection ist für die Übertragung der Deltas von der Compose-Ebene auf die View-Ebene verantwortlich. Dadurch kann das Element am verschachtelten Scrollen teilnehmen, aber das Scrollen von Elementen wird nicht automatisch aktiviert. Bei Composables, die nicht automatisch gescrollt werden, z. B. Box oder Column, werden Scroll-Deltas für solche Komponenten nicht im verschachtelten Scrollsystem weitergegeben und erreichen nicht die von rememberNestedScrollInteropConnection() bereitgestellte NestedScrollConnection. Daher erreichen diese Deltas nicht die übergeordnete View-Komponente. Um dieses Problem zu beheben, müssen Sie auch für diese Arten von verschachtelten Composables scrollbare Modifikatoren festlegen. Weitere Informationen finden Sie im vorherigen Abschnitt zum verschachtelten Scrollen.

Ein nicht kooperierender Elternteil View mit einem Kind ComposeView

Eine nicht kooperierende View implementiert die erforderlichen NestedScrolling-Schnittstellen auf der View-Seite nicht. Das bedeutet, dass die Interoperabilität von verschachteltem Scrollen mit diesen Views nicht sofort funktioniert. Nicht kooperierende Views sind RecyclerView und ViewPager2.

Zusätzliche Ressourcen