Przewiń

Modyfikatory przewijania

Modyfikatory verticalScroll i horizontalScroll to najprostszy sposób, aby umożliwić użytkownikowi przewijanie elementu, gdy granice jego zawartości są większe niż maksymalne ograniczenia rozmiaru. W przypadku modyfikatorów verticalScrollhorizontalScroll nie musisz tłumaczyć ani przesuwać treś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.

Element ScrollState umożliwia zmianę pozycji przewijania lub uzyskanie jej bieżącego stanu. Aby utworzyć go z parametrami domyślnymi, użyj polecenia 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 rejestruje zmiany, ale nie przesuwa automatycznie zawartości. Zamiast tego uprawnienia są delegowane na 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 przy każdym kroku przewijania (za pomocą gestu, płynnego przewijania lub szybkiego przewijania) z wartością delta w pikselach. Ta funkcja musi zwracać ilość przewiniętej odległości, aby zdarzenie było prawidłowo propagowane w przypadku elementów zagnieżdżonych, które mają modyfikator 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 wykrywający naciśnięcie palcem i wyświetlający wartość liczbową w miejscu, w którym znajduje się palec.

Przewijanie zagnieżdżone

Zagnieżdżone przewijanie to system, w którym wiele komponentów przewijania zawartych w sobie nawzajem współpracuje ze sobą, reagując na jeden 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 przewijane i umożliwia interakcję z różnicami 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 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 żadnych działań z Twojej strony. 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 przewijane w pionie, reagujące 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. 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 NestedScrollConnection ani do komponentu nadrzędnego. Aby rozwiązać ten problem, możesz użyć nestedScroll, aby zapewnić 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 cyklu zagnieżdżonego 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

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ę

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

Faza przed przewijaniem – propagacja w dół

W fazie zużycia węzła sam węzeł wykorzysta wszystkie różnice, które nie zostały wykorzystane przez jego węzły nadrzędne. Wtedy następuje faktyczne przewijanie i jest ono widoczne.

Faza wykorzystania węzła

W tym czasie dziecko może przewinąć pozostałą część strony lub tylko jej fragment. Pozostałe elementy zostaną przesłane z powrotem do góry, aby przejść fazę po przewinięciu.

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

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, czy nie.

Faza po przewinięciu – przenoszenie w dół

Podobnie jak w przypadku przewijania, po zakończeniu gestu przeciągnięcia intencje użytkownika mogą zostać przełożone na prędkość, która jest używana do szybkiego przewijania (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, zużycie 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

Uczestnictwo w cyklu oznacza przechwytywanie, wykorzystywanie i raportowanie 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 interakcję, np. gdy musisz coś zrobić z różnicami 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. Podstawowymi elementami 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 i po przewinięciu oraz przed i 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 danej fazy i consumed wartość delta wykorzystana w poprzednich fazach. 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 przesyła do systemu różnice zarejestrowane podczas gestów. Z tego powodu większość przypadków użycia dostosowywania zagnieżdżonego przewijania polega na używaniu NestedScrollConnection zamiast dyspozytora, aby reagować na już istniejące 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 w dół, i powiększa, gdy przewija 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 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 wyznaczany 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 translationY w ramach graphicsLayer zapewnia, że obraz pozostanie wyśrodkowany w pionie podczas skalowania.

Wynik

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

Rysunek 1. Efekt skalowania obrazu podczas przewijania.

Współdziałanie zagnieżdżonego przewijania

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 będą sytuacje, w których przewijasz element podrzędny i docierasz do jego początku lub końca, a oczekujesz, że przewijanie przejmie element nadrzędny. 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. W takim przypadku element podrzędny będzie powodować zagnieżdżone przewijanie elementu nadrzędnego, gdy osiągnie granicę. Na przykład ta reguła umożliwia prawidłowe współdziałanie funkcji Compose Pager i Compose LazyRow. Jednak w przypadku przewijania w ramach interoperacyjności za pomocą ViewPager2 lub RecyclerView, ponieważ nie implementują one 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 z dzieckiem ComposeView

Współpracujący element nadrzędny View to element, który implementuje NestedScrollingParent3, dzięki czemu może otrzymywać delty przewijania ze współpracującego zagnieżdżonego elementu podrzędnego. 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 kompozycyjnymi, 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 CoordinatorLayout, CollapsingToolbarLayout i komponentu podrzędnego, 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ć komponent 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 element podrzędny AndroidView

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

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 automatycznie przewijania elementów. W przypadku komponentów kompozycyjnych, których nie można przewijać automatycznie, takich jak 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 dostarczonego przez rememberNestedScrollInteropConnection(), dlatego nie dotrą do komponentu nadrzędnego View. Aby rozwiązać ten problem, upewnij się, że do tych typów zagnieżdżonych funkcji kompozycyjnych ustawiasz też modyfikatory z możliwością przewijania. Więcej informacji znajdziesz w poprzedniej sekcji dotyczącej zagnieżdżonego przewijania.

Rodzic niechętny do współpracy View z dzieckiem ComposeView

Widok nie współpracują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 w przypadku tych Views. Nie współpracujące Views to RecyclerViewViewPager2.

Dodatkowe materiały