Scrollen

Scroll-Modifikatoren

Mit den Modifikatoren verticalScroll und horizontalScroll können Nutzer ganz einfach durch ein Element scrollen, wenn die Begrenzungen des Inhalts größer als die maximal zulässige Größe sind. Mit den Modifikatoren verticalScroll und horizontalScroll müssen Sie den Inhalt 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 Wischbewegungen reagiert

Mit ScrollState können Sie die Scrollposition ändern oder den aktuellen Status abrufen. Wenn Sie sie mit Standardparametern erstellen möchten, verwenden Sie 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))
        }
    }
}

Modifizierer für scrollbare Inhalte

Der Modifikator scrollable unterscheidet sich von den Scrollmodifikatoren dadurch, dass scrollable die Scrollgesten erkennt und die Deltas erfasst, den Inhalt aber nicht automatisch verschiebt. Dies wird stattdessen über ScrollableState an den Nutzer delegiert, was für die korrekte Funktion dieses Modifikators erforderlich ist.

Wenn Sie ScrollableState erstellen, müssen Sie eine consumeScrollDelta-Funktion angeben, die bei jedem Scrollschritt (durch Touch-Eingabe, gleitendes Scrollen oder Wischen) mit dem Delta in Pixeln aufgerufen wird. Diese Funktion muss die zurückgelegte Scrolldistanz zurückgeben, damit das Ereignis in Fällen mit verschachtelten Elementen mit dem Modifikator scrollable ordnungsgemäß weitergegeben wird.

Im folgenden Snippet werden die Touch-Gesten erkannt und ein numerischer Wert für einen Versatz angezeigt, es werden jedoch keine Elemente verschoben:

@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 den Fingerdruck erkennt und den numerischen Wert für die Position des Fingers anzeigt

Verschachtelte Scrollbalken

Verschachteltes Scrollen ist ein System, bei dem mehrere ineinander enthaltene Scrollkomponenten zusammenarbeiten, indem sie auf eine einzelne Scrollgeste reagieren und ihre Scroll-Deltas (Änderungen) kommunizieren.

Das verschachtelte Scrollsystem ermöglicht die Koordination von Komponenten, die scrollbar und hierarchisch verknüpft sind (meistens durch gemeinsames übergeordnetes Element). Dieses System verknüpft scrollende Container und ermöglicht die Interaktion mit den Scroll-Deltas, die weitergegeben und geteilt werden.

Compose bietet mehrere Möglichkeiten, verschachtelte Scrollvorgänge zwischen Composables zu verarbeiten. Ein typisches Beispiel für verschachtelte Scrollbalken ist eine Liste in einer anderen Liste. Ein komplexerer Fall ist eine minimierbare Symbolleiste.

Automatisches verschachteltes Scrollen

Bei einfachem verschachtelten Scrollen sind keine Maßnahmen von Ihnen erforderlich. Touch-Gesten, die eine Scrollaktion initiieren, werden automatisch von untergeordneten Elementen an übergeordnete Elemente weitergegeben. Wenn das untergeordnete Element nicht mehr weiter scrollen kann, wird die Geste vom übergeordneten Element verarbeitet.

Das automatische verschachtelte Scrollen wird von einigen Komponenten und Modifikatoren von Compose unterstützt und ist standardmäßig verfügbar: verticalScroll, horizontalScroll, scrollable, Lazy APIs und TextField. Das bedeutet, dass beim Scrollen durch ein inneres untergeordnetes Element verschachtelter Komponenten die vorherigen Modifikatoren die Scroll-Deltas an die übergeordneten Elemente weitergeben, die verschachtelte Scrollfunktionen unterstützen.

Das folgende Beispiel zeigt Elemente mit dem Modifikator verticalScroll in einem Container, auf den ebenfalls der Modifikator verticalScroll angewendet wurde.

@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 vertikal scrollbare UI-Elemente, die auf Touch-Gesten innerhalb und außerhalb des inneren Elements reagieren

nestedScroll-Modifikator verwenden

Wenn Sie eine erweiterte, koordinierte Bildlauffunktion zwischen mehreren Elementen erstellen möchten, bietet Ihnen der Modifikator nestedScroll mehr Flexibilität, da Sie eine verschachtelte Bildlaufhierarchie definieren können. Wie im vorherigen Abschnitt erwähnt, haben einige Komponenten eine integrierte Unterstützung für verschachtelte Scrollbalken. Bei Composables, die nicht automatisch scrollbar sind, z. B. Box oder Column, werden Scroll-Deltas für diese Komponenten nicht in das verschachtelte Scrollsystem übertragen und erreichen weder die NestedScrollConnection noch die übergeordnete Komponente. Sie können dieses Problem beheben, indem Sie mit nestedScroll anderen Komponenten, einschließlich benutzerdefinierten Komponenten, diese Unterstützung zuweisen.

Verschachtelter Scrollzyklus

Ein verschachtelter Scrollzyklus ist der Fluss von Scroll-Deltas, die durch den Hierarchiebaum hinauf und hinunter durch alle Komponenten (oder Knoten) gesendet werden, die Teil des verschachtelten Scrollsystems sind, z. B. durch Scrollkomponenten und Modifikatoren oder nestedScroll.

Phasen des verschachtelten Scrollzyklus

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 Scrollsystem gesendet und durchlaufen drei Phasen: Vor dem Scrollen, Knotenverbrauch und Nach dem Scrollen.

Phasen des verschachtelten Scrollzyklus

In der ersten Phase vor dem Scrollen sendet die Komponente, die die Triggerereignisdeltas empfangen hat, diese Ereignisse durch den Hierarchiebaum an das oberste übergeordnete Element. Die Deltaereignisse werden dann nach unten weitergegeben, d. h., Deltas werden vom übergeordneten Element ganz oben nach unten zum untergeordneten Element weitergegeben, das den verschachtelten Scrollzyklus gestartet hat.

Vor dem Scrollen – Abruf

So haben die verschachtelten übergeordneten Elemente für die Bildlaufsteuerung (mit nestedScroll oder scrollbaren Modifikatoren) die Möglichkeit, etwas mit dem Delta zu tun, bevor der Knoten selbst es verwenden kann.

Vor dem Scrollen – Bubble-down-Effekt

In der Phase der Knotennutzung verwendet der Knoten selbst das Delta, das von seinen übergeordneten Knoten nicht verwendet wurde. In diesem Fall wird die Scrollbewegung tatsächlich ausgeführt und ist sichtbar.

Knotenverbrauchsphase

Während dieser Phase kann das Kind entscheiden, ob es den gesamten oder einen Teil des verbleibenden Scrollbereichs nutzen möchte. Alles, was übrig bleibt, wird zurückgesendet, um die Phase nach dem Scrollen zu durchlaufen.

In der Phase nach dem Scrollen werden alle Daten, die der Knoten selbst nicht verbraucht hat, noch einmal an seine Vorfahren gesendet.

Phase nach dem Scrollen – Auslieferung

Die Phase nach dem Scrollen funktioniert ähnlich wie die Phase vor dem Scrollen. Dabei kann jedes übergeordnete Element ausgewählt werden.

Phase nach dem Scrollen – Kaskadeneffekt

Ähnlich wie beim Scrollen kann die Absicht des Nutzers nach Abschluss einer Ziegesten in eine Geschwindigkeit umgewandelt werden, mit der der scrollbare Container „flingt“ (mit einer Animation scrollt). Der Wisch ist auch Teil des verschachtelten Scrollzyklus und die Geschwindigkeiten, die durch das Drag-Ereignis generiert werden, durchlaufen ähnliche Phasen: Vor dem Wischen, Knotenverbrauch und Nach dem Wischen. Die Wischanimation ist nur mit Touch-Gesten verknüpft und wird nicht durch andere Ereignisse wie a11y oder Hardware-Scrollen ausgelöst.

Am verschachtelten Scrollzyklus teilnehmen

Die Teilnahme am Zyklus bedeutet, dass Deltas entlang der Hierarchie abgefangen, verwendet und erfasst werden. Compose bietet eine Reihe von Tools, mit denen Sie die Funktionsweise des verschachtelten Scrollsystems beeinflussen und direkt damit interagieren können. Das ist beispielsweise dann nützlich, wenn Sie etwas mit den Scroll-Deltas tun müssen, bevor eine scrollbare Komponente überhaupt scrollt.

Wenn der verschachtelte Scrollzyklus ein System ist, das auf eine Kette von Knoten wirkt, können Sie mit dem Modifikator nestedScroll diese Änderungen abfangen und einfügen und so die Daten (Scroll-Deltas) beeinflussen, die in der Kette weitergegeben werden. Dieser Modifikator kann überall in der Hierarchie platziert werden und kommuniziert mit verschachtelten Scroll-Modifikatorinstanzen im Baum, um Informationen über diesen Kanal zu teilen. Die Bausteine dieses Modifiers 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 Nutzungsphasen repräsentieren: Vor-/Nach-Scrollen und Vor-/Nach-Wischen:

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 Rückruf enthält auch Informationen zum übertragenen Delta: available Delta für diese bestimmte Phase und consumed Delta, das in den vorherigen Phasen verbraucht wurde. Wenn Sie die Weiterleitung von Deltas durch die Hierarchie nicht mehr zulassen 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.

NestedScrollDispatcher initialisiert den verschachtelten Scrollzyklus. Durch die Verwendung eines Dispatchers und das Aufrufen seiner Methoden wird der Zyklus ausgelöst. Scrollbare Container haben einen integrierten Dispatcher, der während von Touch-Gesten erfasste Deltas an das System sendet. Aus diesem Grund wird bei den meisten Anwendungsfällen für die Anpassung verschachtelten Scrollens NestedScrollConnection anstelle eines Dispatchers verwendet, um auf bereits vorhandene Deltas zu reagieren, anstatt neue zu senden. Weitere Verwendungsmöglichkeiten finden Sie unter NestedScrollDispatcherSample.

Bildgröße beim Scrollen anpassen

Wenn der Nutzer scrollt, können Sie einen dynamischen visuellen Effekt erstellen, bei dem sich das Bild je nach Scrollposition ändert.

Größe eines Bildes anhand der Scrollposition anpassen

In diesem Snippet wird gezeigt, wie die Größe eines Bildes in einem LazyColumn basierend auf der vertikalen Scrollposition geändert wird. Das Bild schrumpft, wenn der Nutzer nach unten scrollt, und wird 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 Scrollereignisse abzufangen.
  • onPreScroll berechnet die Änderung der Bildgröße basierend auf dem Scroll-Delta.
  • Die Statusvariable currentImageSize speichert die aktuelle Größe des Bildes, die zwischen minImageSize und maxImageSize. imageScale liegen muss und von currentImageSize abgeleitet wird.
  • Die LazyColumn-Abweichungen basieren auf dem currentImageSize.
  • Für Image wird ein graphicsLayer-Modifikator verwendet, um die berechnete Skala anzuwenden.
  • Durch das translationY in graphicsLayer bleibt das Bild beim Skalieren vertikal zentriert.

Ergebnis

Das vorherige Snippet führt beim Scrollen zu einem Bildskalierungseffekt:

Abbildung 1. Ein Bildeffekt, bei dem das Bild beim Scrollen skaliert wird.

Interoperabilität für verschachteltes Scrollen

Wenn Sie scrollbare View-Elemente in scrollbare Composeables verschachteln oder umgekehrt, können Probleme auftreten. Am auffälligsten sind sie, wenn Sie das untergeordnete Element scrollen und dessen Anfang oder Ende erreichen und erwarten, dass das übergeordnete Element das Scrollen übernimmt. Dieses erwartete Verhalten tritt jedoch möglicherweise nicht auf oder funktioniert nicht wie erwartet.

Dieses Problem ist auf die Erwartungen zurückzuführen, die in scrollbaren Komponenten geweckt werden. Für scrollbare Elemente gilt die Regel „nested-scroll-by-default“. Das bedeutet, dass jeder scrollbare Container an der verschachtelten Scrollreihenfolge teilnehmen muss, sowohl als übergeordnetes Element über NestedScrollConnection als auch als untergeordnetes Element über NestedScrollDispatcher. Das untergeordnete Element würde dann ein verschachteltes Scrollen für das übergeordnete Element auslösen, wenn es sich an der Grenze befindet. Mit dieser Regel können beispielsweise „Komponieren Pager“ und „Komponieren LazyRow“ gut zusammenarbeiten. Wenn das Scrollen für die Interoperabilität jedoch mit ViewPager2 oder RecyclerView erfolgt, ist ein kontinuierliches Scrollen von untergeordnet zu übergeordnet nicht möglich, da diese Tasten NestedScrollingParent3 nicht implementieren.

Wenn Sie die Nested Scrolling Interop API zwischen scrollbaren View-Elementen und scrollbaren Composeables aktivieren möchten, die in beide Richtungen verschachtelt sind, können Sie diese Probleme in den folgenden Fällen mithilfe der Nested Scrolling Interop API beheben.

Ein übergeordnetes Element View mit einem untergeordneten Element ComposeView

Ein kooperatives übergeordnetes Element View ist ein Element, das bereits NestedScrollingParent3 implementiert hat und daher Scroll-Deltas von einem kooperativen verschachtelten untergeordneten Element empfangen kann. ComposeView würde in diesem Fall als untergeordnetes Element fungieren und NestedScrollingChild3 (indirekt) implementieren müssen. Ein Beispiel für ein kooperierendes Elternteil ist androidx.coordinatorlayout.widget.CoordinatorLayout.

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

rememberNestedScrollInteropConnection() ermöglicht und speichert den Wert NestedScrollConnection, der die Interoperabilität zwischen einem übergeordneten View-Element, das NestedScrollingParent3 implementiert, und einem untergeordneten Compose-Element ermöglicht. Dieser sollte in Verbindung mit einem Modifier vom Typ nestedScroll verwendet werden. Da verschachteltes Scrollen auf der Compose-Seite standardmäßig aktiviert ist, können Sie mit dieser Verbindung sowohl das verschachtelte Scrollen auf der View-Seite aktivieren als auch die erforderliche Logik zwischen Views und Compose-Elementen hinzufügen.

Ein häufiger Anwendungsfall ist die Verwendung von CoordinatorLayout, CollapsingToolbarLayout und einem untergeordneten Composeable, 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 Composeable und die erforderlichen 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 Composeable mit einem untergeordneten AndroidView

In diesem Szenario wird die Implementierung der verschachtelten Scrolling Interop API auf der Compose-Seite behandelt, wenn ein übergeordnetes Compose-Element ein untergeordnetes AndroidView enthält. Das Element AndroidView implementiert NestedScrollDispatcher, da es als untergeordnetes Element eines übergeordneten Elements mit scrollbarem Inhalt von Compose dient, sowie NestedScrollingParent3, da es als übergeordnetes Element eines scrollbaren untergeordneten Elements von View dient. Das übergeordnete Element kann dann verschachtelte Scroll-Deltas von einem verschachtelten scrollbaren untergeordneten Element View empfangen.

Im folgenden Beispiel wird gezeigt, wie Sie in diesem Szenario die verschachtelte Bildlauf-Interoperabilität sowie eine minimierbare Symbolleiste für die Erstellung von Inhalten erreichen:

@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 du die API mit einem scrollable-Modifikator verwendest:

@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 Nested Scrolling Interop API mit BottomSheetDialogFragment verwendet wird, um ein erfolgreiches Ziehen und Schließen zu ermöglichen:

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 im Element installiert wird, an das Sie es anhängen. NestedScrollConnection ist für die Übertragung der Deltas von der Compose-Ebene an die View-Ebene verantwortlich. Dadurch kann das Element am verschachtelten Scrollen teilnehmen, aber es wird nicht automatisch gescrollt. Bei Composables, die nicht automatisch scrollbar sind, z. B. Box oder Column, werden Scroll-Deltas dieser Komponenten nicht im verschachtelten Scrollsystem weitergegeben und erreichen nicht die von rememberNestedScrollInteropConnection() bereitgestellte NestedScrollConnection. Daher erreichen diese Deltas auch nicht die übergeordnete View-Komponente. Um dieses Problem zu beheben, müssen Sie für diese Arten von verschachtelten Composeable-Elementen auch scrollbare Modifikatoren festlegen. Weitere Informationen finden Sie im vorherigen Abschnitt zum verschachtelten Scrollen.

Ein nicht kooperatives übergeordnetes Element View mit einem untergeordneten Element ComposeView

Eine nicht kooperative Ansicht ist eine Ansicht, die die erforderlichen NestedScrolling-Schnittstellen auf der View-Seite nicht implementiert. Das bedeutet, dass die Interoperabilität von verschachteltem Scrollen mit diesen Views nicht standardmäßig funktioniert. Nicht kooperative Views sind RecyclerView und ViewPager2.