Przewiń

Modyfikatory przewijania

Modyfikatory verticalScroll i horizontalScroll stanowią najprostszy sposób na umożliwienie użytkownikowi przewijania elementu, gdy granice jego zawartości są większe niż maksymalne ograniczenia rozmiaru. Dzięki modyfikatorom verticalScroll i horizontalScroll nie musisz tłumaczyć ani przesuwać zawartości.

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

Prosta lista pionowa reagująca na gesty przewijania

ScrollState umożliwia zmianę pozycji przewijania lub uzyskanie jej bieżącego stanu. Aby utworzyć go z parametrami domyślnymi, użyj funkcji 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))
        }
    }
}

Modyfikator przewijania

Modyfikator scrollable różni się od modyfikatorów przewijania tym, że scrollable wykrywa gesty przewijania i przechwytuje wartości delta, ale nie przesuwa zawartości automatycznie. Zamiast tego jest ona delegowana do użytkownika za pomocą parametru ScrollableState, który jest wymagany do prawidłowego działania tego modyfikatora.

Podczas tworzenia funkcji ScrollableState musisz podać funkcję consumeScrollDelta, która będzie wywoływana w przypadku każdego kroku przewijania (za pomocą gestów, płynnego przewijania lub przewijania szybkiego) z delta w pikselach. Ta funkcja musi zwracać odległość przewinięcia, aby zapewnić prawidłowe propagowanie zdarzenia w przypadku elementów zagnieżdżonych z modyfikatorem scrollable.

Poniższy fragment kodu wykrywa gesty i wyświetla wartość liczbową przesunięcia, ale nie przesuwa żadnych elementów:

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

Element interfejsu, który wykrywa dotknięcie palcem i wyświetla wartość liczbową położenia palca

Zagnieżdżone przewijanie

Przewijanie zagnieżdżone to system, w którym wiele elementów przewijania zawartych w sobie współpracuje ze sobą, reagując na pojedynczy gest przewijania i przekazując swoje wartości delta (zmiany).

System przewijania zagnieżdżonego umożliwia koordynację komponentów, które są przewijalne i połączone hierarchicznie (najczęściej przez udostępnianie tego samego elementu nadrzędnego). Ten system łączy kontenery przewijania i umożliwia interakcję z deltami przewijania, które są propagowane i udostępniane.

Compose oferuje wiele sposobów obsługi nawiasów zagnieżdżonych między składanymi elementami. Typowym przykładem przewijania zagnieżdżonego jest lista wewnątrz innej listy, a bardziej złożonym przypadkiem jest zwijana pasek narzędzi.

Automatyczne przewijanie zagnieżdżone

Prosty nawias imbrikowany nie wymaga od Ciebie żadnego działania. Gesty, które inicjują działanie przewijania, są automatycznie przekazywane z elementów podrzędnych do nadrzędnych. Oznacza to, że gdy dziecko nie może już dalej przewijać, gest jest obsługiwany przez element nadrzędny.

Automatyczne przewijanie zagnieżdżonych elementów jest obsługiwane i dostępne w ramach niektórych komponentów i modyfikatorów w Compose: verticalScroll, horizontalScroll, scrollable, interfejsów API LazyTextField. Oznacza to, że gdy użytkownik przewija wewnętrzne elementy potomne zagnieżdżonych komponentów, poprzednie modyfikatory przekazują zmiany przewijania do elementów nadrzędnych, które obsługują zagnieżdżone przewijanie.

Ten przykład przedstawia elementy z modyfikatorem verticalScroll 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

Korzystanie z modyfikatora nestedScroll

Jeśli chcesz utworzyć zaawansowane przewijanie skoordynowane 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ą wbudowane wsparcie dla przewijania zagnieżdżonego. Jednak w przypadku komponentów, które nie są przewijane automatycznie, takich jak Box czy Column, różnice przewijania w takich komponentach nie będą się rozprzestrzeniać w ramach wbudowanego systemu przewijania i nie dotrą do komponentu nadrzędnego NestedScrollConnection. Aby rozwiązać ten problem, możesz użyć nestedScroll, aby zapewnić obsługę innym komponentom, w tym komponentom niestandardowym.

Zagnieżdżony cykl 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 hierarchicznego przez wszystkie komponenty (lub węzły) będące częścią zagnieżdżonego systemu przewijania, np. za pomocą przewijanych komponentów i modyfikatorów lub za pomocą elementu nestedScroll.

Fazy cyklu przewijania zagnieżdżonego

Gdy element z możliwością przewijania wykryje zdarzenie wyzwalające (np. gest), jeszcze przed wywołaniem faktycznego działania przewijania, wygenerowane różnice są wysyłane do wbudowanego systemu przewijania i przechodzą 3 fazy: przed przewijaniem, wykorzystanie węzła i po przewijaniu.

Fazy zagnieżdżonego cyklu przewijania

W pierwszej fazie, czyli przed przewijaniem, komponent, który otrzymał różnice zdarzeń inicjujących, prześle te zdarzenia w górę po drzewie hierarchicznym do najwyższego elementu nadrzędnego. Zdarzenia delta będą się następnie przemieszczać w dół, co oznacza, że będą propagowane od elementu nadrzędnego na poziomie wyższym do elementu podrzędnego, który rozpoczął cykl zwinięcia wgłąb.

Faza przed przewijaniem – wysyłanie danych

Dzięki temu zagnieżdżone elementy rodzicielskie przewijania (komponenty korzystające z elementów nestedScroll lub modyfikatorów przewijania) mogą wykonać jakąś operację na wartości delta, zanim sam węzeł będzie mógł ją wykorzystać.

Faza przed przewijaniem – dyfuzja

W fazie konsumpcji węzła sam węzeł będzie używać różnicy, której nie wykorzystały jego elementy nadrzędne. Wtedy ruch przewijania jest widoczny.

Faza wykorzystania węzła

W tej fazie dziecko może wykorzystać całą lub część pozostałego czasu. Pozostałe elementy zostaną przesłane z powrotem, aby przejść przez fazę po przewinięciu.

Wreszcie, w fazie po przewinięciu wszystko, czego nie zużył sam węzeł, zostanie ponownie wysłane do swoich przodków do wykorzystania.

Faza po przewinięciu – wysyłanie

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 go użyć.

Faza po przewinięciu – przenoszenie w dół

Podobnie jak w przypadku przewijania, gdy gest przeciągania zostanie zakończony, zamiar użytkownika może zostać przekształcony w szybkość, która służy do przewijania (animacji) przewijalnego kontenera. Rzut jest też częścią ścieżki przewijania zagnieżdżonego, a prędkości generowane przez zdarzenie przeciągania przechodzą przez podobne fazy: przed rzutem, zużycie węzła i po rzucie. Pamiętaj, że animacja gestu przesunięcia jest powiązana tylko z gesturem dotykowym i nie zostanie wywołana przez inne zdarzenia, takie jak a11y czy przewijanie za pomocą sprzętu.

Uczestniczyć w zagnieżdżonym cyklu przewijania

Udział w cyklu oznacza przechwytywanie, wykorzystywanie i raportowanie zużycia delt w hierarchii. Compose udostępnia zestaw narzędzi, które umożliwiają wpływanie na działanie systemu przewijania zagnieżdżonego i bezpośrednie z nim współpracowanie, np. gdy trzeba coś zrobić z wartościami delta 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 (różnice w przewijaniu), które są 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 drzewie, aby udostępniać informacje przez ten kanał. Elementy składowe tego modyfikatora to NestedScrollConnection i NestedScrollDispatcher.

NestedScrollConnection pozwala reagować na fazy cyklu przewijania zagnieżdżonego i wpływać na system przewijania zagnieżdżonego. Składa się z 4 metod wywołania, z których każda reprezentuje jedną z faz konsumpcji: przed przewijaniem i po nim oraz przed i po przenoszeniu:

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 przekazywanym delta: available delta dla danego etapu i consumed delta zużyte w poprzednich fazach. Jeśli w dowolnym momencie chcesz zatrzymać propagowanie różnic w hierarchii, możesz użyć 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 typie NestedScrollSource.

NestedScrollDispatcherinicjuje zagnieżdżony cykl przewijania. Użycie rozsyłacza i wywołanie jego metod powoduje uruchomienie cyklu. Kontenery, które można przewijać, mają wbudowany moduł rozsyłający, który wysyła do systemu różnice uchwycone podczas wykonywania gestów. Z tego powodu większość przypadków użycia dostosowywania przewijania zagnieżdżonego wymaga użycia NestedScrollConnection zamiast rozsyłarki, aby reagować na istniejące różnice, a nie wysyłać nowych. Więcej informacji znajdziesz w sekcji NestedScrollDispatcherSample.

Zmienianie rozmiaru obrazu podczas przewijania

Podczas przewijania przez użytkownika możesz tworzyć dynamiczne efekty wizualne, w których obraz zmienia rozmiar w zależności od pozycji przewijania.

Zmienianie rozmiaru obrazu na podstawie pozycji przewijania

Ten fragment kodu demonstruje zmianę rozmiaru obrazu w elementach LazyColumn na podstawie pozycji przewijania pionowego. Obraz zmniejsza się, gdy użytkownik przewija w dół, a powiększa się, gdy przewija w górę, pozostając w określonym zakresie 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 funkcji NestedScrollConnection do przechwytywania zdarzeń przewijania.
  • onPreScroll oblicza zmianę rozmiaru obrazu na podstawie przesunięcia osi Y.
  • Zmienna stanu currentImageSize przechowuje bieżący rozmiar obrazu, który jest ograniczony do wartości od minImageSize do maxImageSize. imageScale i wynika z wartości currentImageSize.
  • Odsunięcia LazyColumn na podstawie currentImageSize.
  • Funkcja Image używa modyfikatora graphicsLayer, aby zastosować obliczony współczynnik skali.
  • Element translationY w elementzie graphicsLayer zapewnia, że obraz pozostaje wyśrodkowany w pionowej podczas skalowania.

Wynik

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

Rysunek 1. Efekt skalowania obrazu podczas przewijania.

Interoperacyjność zagnieżdżonego przewijania

Jeśli spróbujesz zagnieżdżać elementy przewijalne View w komponentach przewijalnych lub odwrotnie, możesz napotkać problemy. Najbardziej zauważalne są te, które występują, gdy przewijasz element potomny i docierasz do jego początku lub końca, a oczekujesz, że element nadrzędny przejmie przewijanie. Jednak może się okazać, że nie będzie ono możliwe lub nie będzie działać zgodnie z oczekiwaniami.

Ten problem jest spowodowany oczekiwaniami związanymi z komponowalnymi komponentami z możliwością przewijania. Składowe przewijalne podlegają regułom „domyślnego przewijania w ramach zagnieżdżonego elementu”. Oznacza to, że każdy przewijalny kontener musi uczestniczyć w łańcuchu zagnieżdżonego przewijania, zarówno jako element nadrzędny za pomocą NestedScrollConnection, jak i jako element podrzędny za pomocą NestedScrollDispatcher. Gdy element podrzędny znajduje się na granicy, powoduje przewijanie elementu nadrzędnego. Na przykład ta reguła umożliwia prawidłowe działanie akcji Compose Pager i Compose LazyRow. Jednak podczas przewijania w celu zapewnienia interoperacyjności za pomocą ViewPager2 lub RecyclerView, ponieważ te nie implementują NestedScrollingParent3, ciągłe przewijanie z elementu podrzędnego do nadrzędnego nie jest możliwe.

Aby włączyć interfejs API do obsługi sterowania przewijaniem w elementach View i komponentach przewijanych, które są w obu kierunkach zagnieżdżone, możesz użyć interfejsu API do obsługi sterowania przewijaniem w elementach zagnieżdżonych, aby rozwiązać te problemy w tych scenariuszach:

Współpracujący rodzic View zawiera element podrzędny dziecko ComposeView

Współdziałający komponent nadrzędny View to taki, który już implementuje NestedScrollingParent3, a zatem może odbierać różnice przewijania z współdziałającego zagnieżdżonego komponentu podrzędnego. ComposeView działa w tym przypadku jako usługa podrzędna i musi (pośrednio) zaimplementować NestedScrollingChild3. Przykładem współpracującego rodzica jest androidx.coordinatorlayout.widget.CoordinatorLayout.

Jeśli potrzebujesz możliwości korzystania z przewijania zagnieżdżonego między przewijalnymi kontenerami nadrzędnymi View a zagnieżdżonymi przewijalnymi komponentami podrzędnymi, możesz użyć rememberNestedScrollInteropConnection().

rememberNestedScrollInteropConnection() pozwala i pamięta NestedScrollConnection, która 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żchłe jest domyślnie włączone po stronie Compose, możesz użyć tego połączenia, aby włączyć przewijanie zagnieżchłe po stronie View i dodać niezbędną logikę łączącą Views i komponenty.

Często używane są komponenty CoordinatorLayout, CollapsingToolbarLayout i komponent potomny, 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ć składankę podrzędną i wymagane komponenty: 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())
                        }
                    }
                }
            }
        }
    }
}

Element nadrzędny zawierający element podrzędny AndroidView

Ten scenariusz obejmuje implementację interfejsu API do interoperacyjności w ramach sterowania złożonym w komponencie, gdy masz komponent nadrzędny zawierający komponent podrzędny AndroidView. AndroidView implementuje NestedScrollDispatcher, ponieważ działa jako element podrzędny względem elementu nadrzędnego Compose scrolling, a także NestedScrollingParent3, ponieważ działa jako element nadrzędny względem elementu podrzędnego View. Element nadrzędny kompozytora będzie mógł otrzymywać zagnieżdżone wartości delta przewijania z zagnieżdżonego elementu podrzędnego, który można przewijać.View

Ten przykład pokazuje, jak w tym scenariuszu uzyskać interoperacyjność przewijania zagnieżdżonego wraz z zwijanym paskiem narzędzi w edytorze:

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

Na koniec przykład pokazujący, jak za pomocą interfejsu API do interoperacyjności przewijania zagnieżdżonego BottomSheetDialogFragment można uzyskać pożądane zachowanie 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 funkcja rememberNestedScrollInteropConnection() zainstaluje element NestedScrollConnection w elemencie, do którego go dodasz. NestedScrollConnection odpowiada za przesyłanie różnic z poziomu Compose na poziom View. Umożliwia to elementowi udział w przewijaniu zagnieżdżonym, ale nie powoduje automatycznego przewijania elementów. W przypadku komponentów, które nie są przewijane automatycznie, takich jak Box czy Column, różnice w przewijaniu takich komponentów nie będą się rozprzestrzeniać w ramach zaimplementowanego systemu przewijania i nie dotrą do komponentu nadrzędnego View.NestedScrollConnectionrememberNestedScrollInteropConnection() Aby rozwiązać ten problem, upewnij się, że dla tych typów zagnieżdżonych komponentów są też ustawione modyfikatory przewijania. Więcej informacji znajdziesz w poprzedniej sekcji dotyczącej zagnieżdżonego przewijania.

rodzic, który nie współpracuje View, z dzieckiem ComposeView

Widok niewspółpracujący to taki, który nie implementuje niezbędnych interfejsów NestedScrolling po stronie View. Oznacza to, że wbudowana obsługa przewijania zagnieżdżonego w tych elementach Views nie działa od razu po zainstalowaniu. Niewspółpracujące Views to RecyclerView i ViewPager2.