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ż ograniczenia maksymalnego rozmiaru. Modyfikatory verticalScrollhorizontalScroll nie wymagają tłumaczenia ani przesuwania 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

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ą ScrollableState , co jest wymagane 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 { mutableFloatStateOf(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

Przewijanie zagnieżdżone to system, w którym wiele komponentów przewijania znajdujących się w sobie nawzajem współpracuje ze sobą, reagując na jeden gest przewijania i przekazując sobie zmiany 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 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 przewijanie zagnieżdżone

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, takich jak Box lub Column, delty przewijania w takich komponentach nie będą propagowane w zagnieżdżonym systemie przewijania i nie dotrą do 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 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 wyzwalające, wyśle 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 przewinięciem – wysyłanie
w górę

Dzięki temu zagnieżdżone elementy przewijane (kompozycje 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 wartości delta, które nie zostały użyte przez jego elementy nadrzędne. Wtedy następuje faktyczne przewijanie i jest ono widoczne.

Faza zużycia 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, 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 do fazy 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, gdy gest przeciągnięcia się zakończy, 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.

uczestniczyć 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 interakcję. 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ż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 za pomocą tego kanału. Podstawowe elementy tego modyfikatora to 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 różnicy:available różnica w przypadku danej fazy i consumed różnica 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 powoduje rozpoczęcie cyklu. 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 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ę, ale zawsze mieści się 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 obliczany 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 przypadki wystąpią, gdy przewiniesz element podrzędny do początku lub końca i 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 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, 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 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 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. 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 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 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 osiągnąć współdziałanie zagnieżdżonego przewijania wraz z zwijającym się 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óre nie są przewijane 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 nadrzędnego komponentu View. Aby rozwiązać ten problem, upewnij się, że w przypadku 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 nie współpracuje View z dzieckiem ComposeView

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

Dodatkowe materiały