Modyfikatory zagnieżdżonego przewijania

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

System zagnieżdżonego przewijania umożliwia koordynację między komponentami, które można przewijać i które są połączone hierarchicznie (najczęściej przez wspólny element nadrzędny). Ten system łączy kontenery przewijania i umożliwia interakcję z różnicami przewijania, które są propagowane i udostępniane między nimi.

Compose udostępnia wiele sposobów obsługi zagnieżdżonego przewijania między komponentami. Typowym przykładem zagnieżdżonego przewijania jest lista w innej liście, a bardziej złożonym przypadkiem jest zwijany pasek narzędzi.

Automatyczne zagnieżdżone przewijanie

Proste zagnieżdżone przewijanie nie wymaga od Ciebie żadnych działań. Gesty, które inicjują przewijanie, są automatycznie przekazywane z elementów podrzędnych do nadrzędnych. Oznacza to, że gdy element podrzędny nie może już przewijać, gest jest obsługiwany przez jego element nadrzędny.

Automatyczne zagnieżdżone przewijanie jest obsługiwane i dostępne od razu w przypadku niektórych komponentów i modyfikatorów Compose: verticalScroll, horizontalScroll, scrollable, interfejsy API LazyTextField. Oznacza to, że gdy użytkownik przewija wewnętrzny element podrzędny zagnieżdżonych komponentów, poprzednie modyfikatory propagują delty przewijania do elementów nadrzędnych, które obsługują zagnieżdżone przewijanie.

Poniższy przykład przedstawia elementy z zastosowanym modyfikatorem verticalScroll w kontenerze, do którego również zastosowano 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 pionowym przewijaniem, reagujące na gesty wewnątrz i na zewnątrz elementu wewnętrznego
Rysunek 1. 2 zagnieżdżone elementy interfejsu z pionowym przewijaniem, 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 zapewni Ci większą elastyczność dzięki zdefiniowaniu zagnieżdżonej hierarchii przewijania. Jak wspomnieliśmy w poprzedniej sekcji, niektóre komponenty mają wbudowaną obsługę zagnieżdżonego przewijania. Jednak w przypadku komponentów kompozycyjnych, które nie są automatycznie przewijane, np. Box lub Column, delty przewijania w takich komponentach nie będą propagowane w systemie przewijania zagnieżdżonego i nie dotrą do komponentu NestedScrollConnection ani do 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

Zagnieżdżony cykl przewijania to przepływ wartości delta przewijania, które są wysyłane w górę i w dół drzewa hierarchii przez wszystkie komponenty (lub węzły) należące do zagnieżdżonego systemu przewijania, np. za pomocą komponentów i modyfikatorów z możliwością przewijania lub nestedScroll.

Fazy zagnieżdżonego cyklu przewijania

Gdy komponent z możliwością przewijania wykryje zdarzenie wywołujące (np. gest), zanim jeszcze zostanie wywołana rzeczywista czynność przewijania, wygenerowane wartości delta są wysyłane do zagnieżdżonego systemu przewijania i przechodzą przez 3 fazy: przed przewijaniem, wykorzystanie węzła i po przewijaniu.

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

W pierwszej fazie przed przewinięciem komponent, który otrzymał zdarzenie wywołujące, będzie wysyłać te zdarzenia w górę drzewa hierarchii do najwyższego elementu nadrzędnego. Zdarzenia delta będą się wtedy rozprzestrzeniać w dół, co oznacza, że będą przekazywane od nadrzędnego elementu głównego do elementu podrzędnego, który rozpoczął zagnieżdżony cykl przewijania.

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

Dzięki temu zagnieżdżone elementy przewijane (komponenty kompozycyjne korzystające z modyfikatorów nestedScroll lub scrollable) mogą wykonać pewne działania z wartością delta, zanim węzeł będzie mógł ją wykorzystać.

Faza przed przewijaniem – propagacja w dół
Rysunek 4. Faza przed przewijaniem – propagacja w dół.

W fazie zużycia węzła sam węzeł wykorzysta wszystkie wartości delta, które nie zostały użyte przez jego węzły nadrzędne. Wtedy ruch przewijania jest faktycznie wykonywany i widoczny.

Faza zużycia węzła
Rysunek 5. Etap wykorzystania węzła.

W tym czasie dziecko może przewinąć pozostałą część strony lub nie. Pozostałe elementy zostaną przesłane z powrotem, aby przejść etap po przewinięciu.

W fazie po przewinięciu wszystko, co nie zostało wykorzystane przez węzeł, zostanie ponownie przesłane do jego elementów nadrzędnych.

Faza po przewinięciu – wysyłanie
w górę
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 każdy z elementów nadrzędnych może zdecydować, czy chce wykorzystać zdarzenie.

Faza po przewinięciu – przenoszenie w dół
Rysunek 7. Faza po przewinięciu – przewijanie w dół.

Podobnie jak w przypadku przewijania, gdy gest przeciągnięcia się zakończy, intencja użytkownika może zostać przełożona na szybkość, która jest używana do przesunięcia (przewijania za pomocą animacji) kontenera z możliwością przewijania. Szybkie przesunięcie jest też częścią zagnieżdżonego cyklu przewijania, a prędkości generowane przez zdarzenie przeciągania przechodzą przez podobne fazy: przed szybkim przesunięciem, wykorzystanie węzła i po szybkim przesunięciu. Pamiętaj, że animacja przesunięcia jest powiązana tylko z gestem dotykowym i nie jest wywoływana przez inne zdarzenia, takie jak przewijanie za pomocą funkcji ułatwień dostępu lub sprzętu.

Uczestniczenie w cyklu zagnieżdżonego przewijania

Udział w cyklu polega na przechwytywaniu, wykorzystywaniu i zgłaszaniu wykorzystania różnic wzdłuż hierarchii. Compose udostępnia zestaw narzędzi, które pozwalają wpływać na działanie zagnieżdżonego systemu przewijania i bezpośrednio z nim wchodzić w interakcje. Możesz na przykład wykonać pewne działania na różnicach przewijania, zanim komponent z możliwością przewijania zacznie się przewijać.

Jeśli zagnieżdżony cykl przewijania jest systemem działającym na łańcuchu węzłów, modyfikator nestedScroll umożliwia przechwytywanie i wstawianie zmian oraz wpływanie na dane (przyrosty przewijania) propagowane w łańcuchu. Ten modyfikator może być umieszczony w dowolnym miejscu w hierarchii i komunikuje się z zagnieżdżonymi instancjami modyfikatora przewijania w górę drzewa, dzięki czemu może udostępniać informacje za pomocą tego kanału. Elementami składowymi tego modyfikatora są NestedScrollConnectionNestedScrollDispatcher.

NestedScrollConnection umożliwia reagowanie na fazy zagnieżdżonego cyklu przewijania i wpływanie na zagnieżdżony system przewijania. Składa się z 4 metod wywołania zwrotnego, z których każda reprezentuje jedną z faz konsumpcji: przed/po przewinięciu i przed/po przesunięciu:

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 wartości delta:available wartość delta dla danego etapu i consumed wartość delta wykorzystana na poprzednich etapach. Jeśli w dowolnym momencie chcesz przestać propagować zmiany w górę hierarchii, możesz użyć do tego zagnieżdżonego połączenia 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 NestedScrollSource.

NestedScrollDispatcher inicjuje zagnieżdżony cykl przewijania. Użycie dyspozytora i wywołanie jego metod uruchamia cykl. Kontenery z możliwością przewijania mają wbudowany moduł wysyłający, który przekazuje do systemu różnice zarejestrowane podczas gestów. Dlatego w większości przypadków dostosowywania zagnieżdżonego przewijania używa się funkcji NestedScrollConnection zamiast dyspozytora, aby reagować na istniejące już delty, a nie wysyłać nowych. Więcej informacji znajdziesz w sekcji NestedScrollDispatcherSample.

Zmienianie rozmiaru obrazu podczas przewijania

Podczas przewijania przez użytkownika możesz utworzyć dynamiczny efekt wizualny, w którym rozmiar obrazu zmienia się 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ół, a powiększa, gdy przewija ją w górę, przy czym pozostaje 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 NestedScrollConnection do przechwytywania zdarzeń przewijania.
  • onPreScroll oblicza zmianę rozmiaru obrazu na podstawie wartości delta przewijania.
  • Zmienna stanu currentImageSize przechowuje bieżący rozmiar obrazu, który mieści się w zakresie od minImageSize do maxImageSize. imageScale i jest wyliczany na podstawie zmiennej currentImageSize.
  • Wartości przesunięcia LazyColumn są oparte na wartości currentImageSize.
  • W przypadku Image używany jest modyfikator graphicsLayer, aby zastosować obliczoną skalę.
  • Symbol translationYgraphicsLayer zapewnia, że obraz pozostanie wyśrodkowany w pionie podczas skalowania.

Wynik

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

Rysunek 8. Efekt skalowania obrazu podczas przewijania.

Interakcja z zagnieżdżonym przewijaniem

Jeśli spróbujesz zagnieździć elementy z możliwością przewijania View w komponentach z możliwością przewijania lub odwrotnie, możesz napotkać problemy. Najbardziej zauważalne przypadki wystąpią, gdy przewiniesz element podrzędny do początku lub końca i oczekujesz, że element nadrzędny przejmie przewijanie. Jednak to oczekiwane zachowanie może nie wystąpić lub może nie działać zgodnie z oczekiwaniami.

Ten problem wynika z oczekiwań wbudowanych w komponenty kompozycyjne z możliwością przewijania. Komponenty z możliwością przewijania mają regułę „nested-scroll-by-default”, co oznacza, że każdy kontener z możliwością przewijania musi uczestniczyć w łańcuchu przewijania zagnieżdżonego zarówno jako element nadrzędny za pomocą NestedScrollConnection, jak i jako element podrzędny za pomocą NestedScrollDispatcher. Gdy element podrzędny osiągnie granicę, będzie powodować zagnieżdżone przewijanie elementu nadrzędnego. Na przykład ta reguła umożliwia prawidłowe współdziałanie Compose Pager i Compose LazyRow. Jednak w przypadku przewijania w ramach interoperacyjności za pomocą ViewPager2 lub RecyclerView, które nie implementują NestedScrollingParent3, ciągłe przewijanie z elementu podrzędnego do nadrzędnego nie jest możliwe.

Aby włączyć interfejs API współdziałania z zagnieżdżonym przewijaniem między elementami View z możliwością przewijania a komponentami z możliwością przewijania zagnieżdżonymi w obu kierunkach, możesz użyć interfejsu API współdziałania z zagnieżdżonym przewijaniem, aby rozwiązać te problemy w tych scenariuszach.

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

Współpracujący element nadrzędny View to element, który implementuje już NestedScrollingParent3 i dlatego może otrzymywać delty przewijania ze współpracującego zagnieżdżonego elementu podrzędnego kompozycji. ComposeView zachowywałby się w tym przypadku jak dziecko i musiałby (pośrednio) zaimplementować NestedScrollingChild3. Przykładem współpracującego rodzica jest androidx.coordinatorlayout.widget.CoordinatorLayout.

Jeśli potrzebujesz interoperacyjności zagnieżdżonego przewijania między przewijanymi Viewkontenerami nadrzędnymi a zagnieżdżonymi przewijanymi komponentami podrzędnymi, możesz użyć rememberNestedScrollInteropConnection().

rememberNestedScrollInteropConnection() umożliwia i zapamiętuje NestedScrollConnection umożliwiające zagnieżdżone przewijanie 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. Przewijanie zagnieżdżone jest domyślnie włączone po stronie Compose, więc możesz użyć tego połączenia, aby włączyć przewijanie zagnieżdżone po stronie View i dodać niezbędną logikę łączącą Views z komponentami kompozycyjnymi.

Częstym przypadkiem użycia jest użycie funkcji CoordinatorLayout, CollapsingToolbarLayout i komponentu podrzędnego, co widać na 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 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())
                        }
                    }
                }
            }
        }
    }
}

Funkcja kompozycyjna nadrzędna zawierająca funkcję kompozycyjną podrzędną AndroidView

Ten scenariusz obejmuje implementację zagnieżdżonego interfejsu API do przewijania na stronie Compose – gdy masz element kompozycyjny nadrzędny zawierający element podrzędny AndroidView. Element AndroidView implementuje interfejs NestedScrollDispatcher, ponieważ jest elementem podrzędnym przewijanego elementu nadrzędnego Compose, a także interfejs NestedScrollingParent3, ponieważ jest elementem nadrzędnym przewijanego elementu podrzędnego View. Komponent nadrzędny będzie wtedy mógł otrzymywać zagnieżdżone delty przewijania z zagnieżdżonego komponentu podrzędnego z możliwością przewijaniaView.

Poniższy przykład pokazuje, jak w tym scenariuszu można osiągnąć współdziałanie zagnieżdżonego przewijania wraz z rozwijanym paskiem 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(
            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(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)
                    }
            }
        )
    }
}

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 funkcji przeciągania i zamykania:

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. Umożliwia to elementowi uczestniczenie w przewijaniu zagnieżdżonym, ale nie włącza automatycznego przewijania elementów. W przypadku komponentów kompozycyjnych, które nie są przewijane automatycznie, np. Box lub Column, różnice przewijania w takich komponentach nie będą propagowane w systemie przewijania zagnieżdżonego, a różnice nie dotrą do elementu NestedScrollConnection udostępnianego przez rememberNestedScrollInteropConnection(), dlatego nie dotrą do komponentu nadrzędnego View. Aby rozwiązać ten problem, ustaw też modyfikatory z możliwością przewijania dla tych typów zagnieżdżonych funkcji kompozycyjnych. Więcej informacji znajdziesz w poprzedniej sekcji dotyczącej zagnieżdżonego przewijania.

Rodzic, który nie współpracuje View, zawierający dziecko ComposeView

Widok nieobsługujący to taki, który nie implementuje niezbędnych interfejsów po stronie View.NestedScrolling Oznacza to, że zagnieżdżone przewijanie nie działa od razu z tymi Views. Nie współpracujące Views to RecyclerViewViewPager2.

Dodatkowe materiały