Modyfikatory zagnieżdżonego przewijania

Przewijanie zagnieżdżone to system, w którym wiele komponentów przewijania zawartych w sobie współpracuje ze sobą, reagując na pojedynczy gest przewijania i przekazując sobie nawzajem informacje o zmianach w przewijaniu.

Zagnieżdżony system przewijania umożliwia koordynację między komponentami, które można przewijać i które są połączone hierarchicznie (najczęściej przez udostępnianie tego samego elementu nadrzędnego). Ten system łączy kontenery przewijania i umożliwia interakcję z delta przewijania, które są propagowane i udostępniane między nimi.

Compose udostępnia kilka sposobów obsługi zagnieżdżonego przewijania między elementami kompozytowymi. Typowym przykładem zagnieżdżonego przewijania jest lista wewnątrz innej listy, a bardziej złożonym przypadkiem jest zwijany pasek narzędzi.

Automatyczne przewijanie zagnieżdżone

Proste przewijanie zagnieżdżone nie wymaga żadnych działań z Twojej strony. Gesty, które inicjują działanie przewijania, są automatycznie przekazywane z elementów podrzędnych do nadrzędnych, dzięki czemu gdy element podrzędny nie może już przewijać, gest jest obsługiwany przez element nadrzędny.

Automatyczne zagnieżdżone przewijanie jest obsługiwane i zapewniane od razu przez niektóre komponenty i modyfikatory Compose: verticalScroll, horizontalScroll, scrollable, interfejsy API Lazy i TextField. Oznacza to, że gdy użytkownik przewija wewnętrzny element podrzędny komponentów zagnieżdżonych, poprzednie modyfikatory przekazują różnice przewijania do elementów nadrzędnych, które obsługują zagnieżdżone przewijanie.

Poniższy przykład pokazuje elementy z modyfikatorem verticalScroll zastosowanym w kontenerze, który również ma zastosowany modyfikator 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)
                    )
                }
            }
        }
    }
}

2 zagnieżdżone elementy interfejsu z przewijaniem pionowym, które reagują na gesty wewnątrz i na zewnątrz elementu wewnętrznego
Rysunek 1. Dwa zagnieżdżone elementy interfejsu z przewijaniem pionowym, które reagują na gesty wewnątrz i na zewnątrz elementu wewnętrznego.

Używanie modyfikatora nestedScroll

Jeśli chcesz utworzyć zaawansowane skoordynowane przewijanie między wieloma elementami, modyfikator nestedScroll zapewnia większą elastyczność dzięki zdefiniowaniu hierarchii zagnieżdżonego przewijania. Jak wspomnieliśmy w poprzedniej sekcji, niektóre komponenty mają wbudowaną obsługę zagnieżdżonego przewijania. Jednak w przypadku funkcji kompozycyjnych, które nie są automatycznie przewijane, takich jak Box lub Column, zmiany przewijania w takich komponentach nie będą propagowane w systemie zagnieżdżonego przewijania i nie dotrą do komponentu NestedScrollConnection ani komponentu nadrzędnego. Aby rozwiązać ten problem, możesz użyć nestedScroll, aby zapewnić taką obsługę innym komponentom, w tym komponentom niestandardowym.

Cykl zagnieżdżonego przewijania

Cykl zagnieżdżonego przewijania to przepływ zmian przewijania, które są wysyłane w górę i w dół drzewa hierarchii przez wszystkie komponenty (lub węzły) będące częścią systemu zagnieżdżonego przewijania, na przykład za pomocą komponentów i modyfikatorów przewijania lub nestedScroll.

Fazy cyklu zagnieżdżonego przewijania

Gdy komponent przewijany wykryje zdarzenie aktywujące (np. gest), zanim zostanie wywołane rzeczywiste działanie przewijania, wygenerowane delty są wysyłane do zagnieżdżonego systemu przewijania i przechodzą przez 3 fazy: przed przewijaniem, wykorzystanie węzła i po przewijaniu.

Fazy cyklu zagnieżdżonego przewijania
Rysunek 2. Fazy cyklu zagnieżdżonego przewijania.

W pierwszej fazie, przed przewinięciem, komponent, który otrzymał zmiany zdarzenia aktywującego, wyśle te zdarzenia w górę drzewa hierarchii do najwyższego elementu nadrzędnego. Zdarzenia delta będą następnie przekazywane w dół, co oznacza, że będą propagowane od nadrzędnego elementu głównego do elementu podrzędnego, który rozpoczął cykl zagnieżdżonego przewijania.

Faza przed przewijaniem – wysyłanie
w górę
Rysunek 3. Faza przed przewinięciem: wysyłanie w górę.

Dzięki temu nadrzędne elementy przewijania zagnieżdżonego (komponenty korzystające z modyfikatorów nestedScroll lub modyfikatorów przewijania) mogą wykonać działanie na delcie, zanim węzeł będzie mógł ją wykorzystać.

Faza przed przewinięciem – bąbelkowanie w dół
Rysunek 4. Faza przed przewinięciem – bąbelkowanie w dół.

W fazie wykorzystania węzła sam węzeł użyje różnicy, która nie została wykorzystana przez jego elementy nadrzędne. To jest moment, w którym ruch przewijania jest faktycznie wykonywany i widoczny.

Faza wykorzystania węzła
Rysunek 5. Faza wykorzystania węzła.

W tej fazie dziecko może zdecydować się na wykorzystanie całości lub części pozostałego czasu przewijania. Wszystkie pozostałe wyświetlenia zostaną odesłane z powrotem do fazy po przewinięciu.

Na koniec, w fazie po przewinięciu, wszystko, czego węzeł nie wykorzystał, zostanie ponownie wysłane do jego elementów nadrzędnych do wykorzystania.

Faza po przewinięciu – wysyłanie zdarzenia up
Rysunek 6. Faza po przewinięciu – wysyłanie w górę.

Faza po przewinięciu działa podobnie jak faza przed przewinięciem, w której dowolny element nadrzędny może zdecydować, czy chce wykorzystać zdarzenie, czy nie.

Faza po przewinięciu – bąbelkowanie w dół
Rysunek 7. Faza po przewinięciu – bąbelkowanie w dół.

Podobnie jak w przypadku przewijania, po zakończeniu gestu przeciągania intencja użytkownika może zostać przełożona na szybkość, która jest używana do szybkiego przewijania (przewijania za pomocą animacji) kontenera, który można przewijać. Szybkie przewijanie jest też częścią cyklu zagnieżdżonego przewijania, a prędkości generowane przez zdarzenie przeciągania przechodzą przez podobne fazy: przed szybkim przewijaniem, wykorzystanie węzła i po szybkim przewijaniu. Pamiętaj, że animacja rzutu jest powiązana tylko z gestem dotknięcia i nie będzie wywoływana przez inne zdarzenia, takie jak ułatwienia dostępu czy przewijanie sprzętowe.

Udział w cyklu zagnieżdżonego przewijania

Uczestnictwo w cyklu oznacza przechwytywanie, wykorzystywanie i raportowanie wykorzystania zmian w hierarchii. Compose udostępnia zestaw narzędzi, które wpływają na działanie systemu zagnieżdżonego przewijania i umożliwiają bezpośrednią interakcję z nim, np. gdy musisz coś zrobić z deltami przewijania, zanim komponent przewijany w ogóle zacznie się przewijać.

Jeśli cykl zagnieżdżonego przewijania to system działający na łańcuchu węzłów, modyfikator nestedScroll umożliwia przechwytywanie i wstawianie tych zmian oraz wpływanie na dane (delty przewijania) propagowane w łańcuchu. Ten modyfikator można umieścić w dowolnym miejscu w hierarchii. Komunikuje się on z zagnieżdżonymi instancjami modyfikatora przewijania w górę drzewa, dzięki czemu może udostępniać informacje przez ten kanał. Elementy składowe tego modyfikatora to NestedScrollConnection i NestedScrollDispatcher.

NestedScrollConnection umożliwia reagowanie na fazy cyklu przewijania zagnieżdżonego i wpływanie na system przewijania zagnieżdżonego. Składa się z 4 metod wywołania zwrotnego, z których każda reprezentuje jedną z faz wykorzystania: przed przewijaniem i po nim oraz przed szybkim przewijaniem i po nim:

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

Każde wywołanie zwrotne zawiera też informacje o propagowanej zmianie: zmianie available dla danej fazy i zmianie consumed wykorzystanej w poprzednich fazach. Jeśli w dowolnym momencie chcesz przestać propagować delty w górę hierarchii, możesz użyć połączenia zagnieżdżonego przewijania:

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

Wszystkie wywołania zwrotne zawierają informacje o typie NestedScrollSource.

NestedScrollDispatcher inicjuje cykl zagnieżdżonego przewijania. Użycie dyspozytora i wywołanie jego metod uruchamia cykl. Kontenery z możliwością przewijania mają wbudowany dyspozytor, który wysyła do systemu delty zarejestrowane podczas gestów. Z tego powodu w większości przypadków dostosowywania zagnieżdżonego przewijania używa się NestedScrollConnection zamiast dyspozytora, aby reagować na już istniejące zmiany, a nie wysyłać nowe. Więcej zastosowań znajdziesz w NestedScrollDispatcherSample.

Zmienianie rozmiaru obrazu podczas przewijania

Podczas przewijania możesz utworzyć dynamiczny efekt wizualny, w którym obraz zmienia rozmiar w zależności od pozycji przewijania.

Zmienianie rozmiaru obrazu na podstawie pozycji przewijania

Ten fragment kodu pokazuje, jak zmienić rozmiar obrazu w elemencie LazyColumn na podstawie pozycji przewijania w pionie. Obraz zmniejsza się, gdy użytkownik przewija stronę w dół, i powiększa się, gdy przewija ją w górę, pozostając w określonych granicach minimalnego i maksymalnego rozmiaru:

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

Najważniejsze informacje o kodzie

  • Ten kod używa elementu NestedScrollConnection do przechwytywania zdarzeń przewijania.
  • onPreScroll oblicza zmianę rozmiaru obrazu na podstawie różnicy przewijania.
  • Zmienna stanu currentImageSize przechowuje bieżący rozmiar obrazu, który jest ograniczony do zakresu od minImageSize do maxImageSize. imageScale i pochodzi z currentImageSize.
  • LazyColumn kompensuje emisję na podstawie currentImageSize.
  • Image używa modyfikatora graphicsLayer, aby zastosować obliczoną skalę.
  • Atrybut translationY w elemencie graphicsLayer zapewnia, że obraz pozostaje wyśrodkowany w pionie podczas skalowania.

Wynik

Powyższy fragment kodu powoduje efekt skalowania obrazu podczas przewijania:

Rysunek 8. Efekt skalowania obrazu podczas przewijania.

Interoperacyjność zagnieżdżonego przewijania

Jeśli spróbujesz zagnieździć przewijane elementy View w przewijanych elementach kompozycyjnych lub odwrotnie, mogą wystąpić problemy. Najbardziej zauważalne są te, które występują, gdy przewijasz element podrzędny i docierasz do jego początku lub końca, i oczekujesz, że element nadrzędny przejmie przewijanie. Jednak to oczekiwane działanie może nie nastąpić lub może nie działać zgodnie z oczekiwaniami.

Ten problem wynika z oczekiwań związanych z komponentami funkcyjnymi, które można przewijać. Komponenty możliwe do przewijania mają regułę „nested-scroll-by-default”, co oznacza, że każdy kontener możliwy do przewijania musi uczestniczyć w łańcuchu przewijania zagnieżdżonego, zarówno jako element nadrzędny za pomocą NestedScrollConnection, jak i element podrzędny za pomocą NestedScrollDispatcher. Gdy element podrzędny osiągnie granicę, będzie sterować zagnieżdżonym przewijaniem elementu nadrzędnego. Ta reguła umożliwia na przykład prawidłowe współdziałanie funkcji Compose Pager i Compose LazyRow. Jednak gdy przewijanie interoperacyjne jest wykonywane za pomocą ViewPager2 lub RecyclerView, ponieważ nie implementują one NestedScrollingParent3, ciągłe przewijanie od elementu podrzędnego do nadrzędnego nie jest możliwe.

Aby włączyć interfejs API współdziałania zagnieżdżonego przewijania między przewijanymi elementami View i przewijanymi elementami kompozytowymi, zagnieżdżonymi w obu kierunkach, możesz użyć interfejsu API współdziałania zagnieżdżonego przewijania, aby rozwiązać te problemy w tych scenariuszach.

Współpracujący element nadrzędny View zawierający element podrzędny ComposeView

Współpracujący element nadrzędny View to taki, który już implementuje NestedScrollingParent3, dzięki czemu może otrzymywać wartości delta przewijania ze współpracującego zagnieżdżonego elementu podrzędnego. W tym przypadku ComposeView działałby jako element podrzędny i musiałby (pośrednio) implementować NestedScrollingChild3. Przykładem współpracującego rodzica jest androidx.coordinatorlayout.widget.CoordinatorLayout.

Jeśli potrzebujesz współdziałania przewijania zagnieżdżonego między przewijanymi kontenerami nadrzędnymi View a zagnieżdżonymi przewijanymi elementami podrzędnymi, możesz użyć rememberNestedScrollInteropConnection().

rememberNestedScrollInteropConnection() zezwala na NestedScrollConnection i zapamiętuje go, co umożliwia współdziałanie zagnieżdżonego przewijania między elementem nadrzędnym View, który implementuje NestedScrollingParent3, a elementem podrzędnym Compose. Należy go używać w połączeniu z modyfikatorem nestedScroll. Ponieważ przewijanie zagnieżdżone jest domyślnie włączone po stronie Compose, możesz użyć tego połączenia, aby włączyć przewijanie zagnieżdżone po stronie View i dodać niezbędną logikę łączącą między Views a funkcjami kompozytowymi.

Częstym przypadkiem użycia jest użycie CoordinatorLayout, CollapsingToolbarLayout i podrzędnej funkcji kompozycyjnej, jak w tym przykładzie:

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

W aktywności lub fragmencie musisz skonfigurować element kompozycyjny podrzędny i wymagany obiekt 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())
                        }
                    }
                }
            }
        }
    }
}

Nadrzędny element kompozycyjny zawierający podrzędny element AndroidView

Ten scenariusz obejmuje implementację interfejsu API współdziałania zagnieżdżonego przewijania po stronie Compose, gdy masz nadrzędną funkcję kompozycyjną zawierającą podrzędny element AndroidView. AndroidView implementuje NestedScrollDispatcher, ponieważ działa jako element podrzędny elementu nadrzędnego przewijania w Compose, oraz NestedScrollingParent3, ponieważ działa jako element nadrzędny elementu podrzędnego przewijania w View. Następnie nadrzędny element Compose będzie mógł odbierać zmiany przewijania zagnieżdżonego z zagnieżdżonego elementu podrzędnego, który można przewijać View.

Poniższy przykład pokazuje, jak w tym scenariuszu można osiągnąć współdziałanie zagnieżdżonego przewijania, a także zwijany pasek narzędzi 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(
            title = {},
            modifier = Modifier
                .height(ToolbarHeight)
                .offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) }
        )

        AndroidView(
            { context ->
                LayoutInflater.from(context)
                    .inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply {
                        with(findViewById<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) {
            // ...
        }
    }
    // ...
}

Ten przykład pokazuje, jak używać interfejsu API z modyfikatorem 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(R.layout.list_item, null)
                    .apply {
                        // Nested scrolling interop is enabled when
                        // nested scroll is enabled for the root View
                        ViewCompat.setNestedScrollingEnabled(this, true)
                    }
            }
        )
    }
}

Na koniec ten przykład pokazuje, jak interfejs API współdziałania z zagnieżdżonym przewijaniem jest używany z BottomSheetDialogFragment, aby uzyskać prawidłowe działanie przeciągania i odrzucania:

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

Pamiętaj, że rememberNestedScrollInteropConnection() zainstaluje NestedScrollConnection w elemencie, do którego go dołączysz. NestedScrollConnection odpowiada za przesyłanie zmian z poziomu Compose na poziom View. Dzięki temu element może uczestniczyć w zagnieżdżonym przewijaniu, ale nie umożliwia automatycznego przewijania elementów. W przypadku funkcji kompozycyjnych, które nie są automatycznie przewijane, takich jak Box lub Column, zmiany przewijania w tych komponentach nie będą propagowane w systemie zagnieżdżonego przewijania i nie dotrą do elementu NestedScrollConnection dostarczanego przez rememberNestedScrollInteropConnection(), dlatego nie dotrą do nadrzędnego komponentu View. Aby rozwiązać ten problem, upewnij się, że modyfikatory przewijania są ustawione również w tych typach zagnieżdżonych funkcji kompozycyjnych. Więcej informacji znajdziesz w poprzedniej sekcji dotyczącej zagnieżdżonego przewijania.

Niewspółpracujący rodzic View zawierający dziecko ComposeView

Widok, który nie współpracuje, to taki, który nie implementuje niezbędnych interfejsów NestedScrolling po stronie View. Pamiętaj, że oznacza to, że współdziałanie zagnieżdżonego przewijania z tymi elementami Views nie działa od razu. Niewspółpracujące Views to RecyclerView i ViewPager2.

Dodatkowe materiały