Modyfikatory przewijania
Modyfikatory verticalScroll
i horizontalScroll
zapewniają najprostszy sposób na przewijanie elementu przez użytkownika, gdy granice jego zawartości przekraczają granicę maksymalnego rozmiaru. Korzystając z modyfikatorów verticalScroll
i horizontalScroll
, 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)) } } }
Element ScrollState
umożliwia zmianę pozycji przewijania lub pobranie jej bieżącego stanu. Aby utworzyć go z parametrami domyślnymi, użyj parametru 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 przewijany
Modyfikator
scrollable
różni się od modyfikatorów przewijania tym, że scrollable
wykrywa gesty przewijania, ale nie przesuwa ich zawartości. Aby ten modyfikator działał prawidłowo, wymagany jest element ScrollableState
.
Tworząc ScrollableState
, musisz podać funkcję consumeScrollDelta
, która będzie wywoływana po każdym kroku przewijania (poprzez wprowadzanie gestami, płynne przewijanie lub przesuwanie palcem) z delta w pikselach.
Aby zapewnić prawidłowe rozpowszechnianie zdarzenia w przypadku elementów zagnieżdżonych z modyfikatorem scrollable
, funkcja musi zwracać pokonywaną odległość przewijania.
Ten fragment kodu wykrywa gesty i wyświetla wartość liczbową przesunięcia, ale nie zwraca ż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()) } }
Zagnieżdżone przewijanie
Tworzenie obsługuje przewijanie zagnieżdżone,w którym wiele elementów reaguje na pojedynczy gest przewijania. Typowym przykładem przewijania zagnieżdżonego jest lista wewnątrz innej listy, a w bardziej skomplikowanym przypadku – zwijający się pasek narzędzi.
Automatyczne przewijanie zagnieżdżone
Proste przewijanie zagnieżdżone nie wymaga ze strony użytkownika żadnego działania. Gesty inicjujące przewijanie są automatycznie przekazywane z dziedziny dziecka do rodziców. Gdy dziecko nie może już przewinąć strony, gest jest obsługiwany przez element nadrzędny.
Automatyczne zagnieżdżone przewijanie jest obsługiwane i dostępne od razu przez niektóre komponenty i modyfikatory komponentu Compose: verticalScroll
, horizontalScroll
, scrollable
, Lazy
API i TextField
. Oznacza to, że gdy użytkownik przewinie wewnętrzne elementy podrzędne zagnieżdżonych komponentów, poprzednie modyfikatory przekazują delta przewijania do elementów nadrzędnych, które obsługują przewijanie zagnieżdżone.
Poniższy przykład pokazuje elementy z zastosowanym modyfikatorem verticalScroll
wewnątrz kontenera, do którego zastosowano też 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) ) } } } } }
Korzystanie z modyfikatora nestedScroll
Jeśli chcesz utworzyć zaawansowane skoordynowane przewijanie między wieloma elementami, modyfikator nestedScroll
zapewnia większą elastyczność, ponieważ definiuje zagnieżdżoną hierarchię przewijania.
Jak wspomnieliśmy w poprzedniej sekcji, niektóre komponenty mają wbudowaną obsługę przewijania zagnieżdżonego. Jednak w przypadku elementów kompozycyjnych, których nie da się przewijać automatycznie, np. Box
czy Column
, delta takich komponentów nie będą rozpowszechniane w zagnieżdżonym systemie przewijania, a delta nie będą docierać do komponentu NestedScrollConnection
ani elementu nadrzędnego. Aby rozwiązać ten problem, możesz użyć nestedScroll
, aby udzielić takiej pomocy innym komponentom, w tym komponentom niestandardowym.
Zagnieżdżona interoperacyjność z przewijaniem (od opcji Tworzenie 1.2.0)
Gdy próbujesz zagnieździć przewijane elementy View
w komponentach z możliwością przewijania lub na odwrót, możesz napotkać problemy.
Najczęściej dzieje się tak, gdy przewiniesz element podrzędny do osiągnięcia jego początku lub końca i oczekujesz, że rodzic przejmie przewijanie. Może ono jednak nie działać lub nie działać zgodnie z oczekiwaniami.
Ten problem jest wynikiem oczekiwań związanych z przewijanymi elementami kompozycyjnymi.
Elementy kompozycyjne z możliwością przewijania mają regułę „zagnieżdżone przewijanie domyślne”, co oznacza, że każdy kontener, który można przewijać, musi należeć do zagnieżdżonego łańcucha przewijania – zarówno jako element nadrzędny za pomocą NestedScrollConnection
, jak i jako element podrzędny w ramach NestedScrollDispatcher
.
Gdy element podrzędny jest na granicy, element podrzędny generuje zagnieżdżony przewijany element nadrzędny. Na przykład ta reguła umożliwia współdziałanie funkcji Utwórz Pager
i Utwórz LazyRow
. Jeśli jednak przewijanie współdziała z elementami ViewPager2
lub RecyclerView
, które nie implementują
NestedScrollingParent3
,
ciągłe przewijanie z elementu podrzędnego do elementu nadrzędnego nie jest możliwe.
Aby włączyć zagnieżdżony interfejs API interoperacyjności przewijania między elementami View
, które można przewijać oraz przewijanymi elementami kompozycyjnymi, zagnieżdżonymi w obu kierunkach, możesz zapobiec tym problemom w poniższych scenariuszach, używając zagnieżdżonego interfejsu API interoperacyjności przewijania.
Współpracujący element nadrzędny View
zawierający element podrzędny ComposeView
Współpracujący element nadrzędny View
to taki, który implementuje już NestedScrollingParent3
, więc może otrzymywać wartości delta przewijania z współpracującego zagnieżdżonego elementu kompozycyjnego. ComposeView
będzie działać jako element podrzędny i (pośrednio) wdrożyć NestedScrollingChild3
.
Przykładem współpracującego elementu nadrzędnego jest androidx.coordinatorlayout.widget.CoordinatorLayout
.
Jeśli potrzebujesz zagnieżdżonego przewijania między możliwymi do przewijania kontenerami nadrzędnymi View
a zagnieżdżonymi podrzędnymi elementami kompozycyjnymi z możliwością przewijania, możesz użyć funkcji rememberNestedScrollInteropConnection()
.
rememberNestedScrollInteropConnection()
dopuszcza i zapamiętuje element NestedScrollConnection
, który umożliwia współdziałanie z zagnieżdżonym przewijaniem między elementem nadrzędnym View
, który implementuje element NestedScrollingParent3
oraz elementem podrzędnym tworzenia. Tej opcji należy używać w połączeniu z modyfikatorem nestedScroll
. Ponieważ przewijanie zagnieżdżone jest domyślnie włączone po stronie tworzenia wiadomości, możesz użyć tego połączenia, aby włączyć zarówno zagnieżdżone przewijanie po stronie elementu View
, jak i odpowiednią logikę klejową między elementami Views
i kompozycyjnymi.
Częstym przypadkiem użycia jest użycie właściwości CoordinatorLayout
, CollapsingToolbarLayout
i podrzędnego elementu kompozycyjnego, co widać 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 sekcji Aktywność lub Fragment musisz skonfigurować podrzędną funkcję kompozycyjną i wymagany NestedScrollConnection
:
open class MainActivity : ComponentActivity() { @OptIn(ExperimentalComposeUiApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<ComposeView>(R.id.compose_view).apply { setContent { val nestedScrollInterop = rememberNestedScrollInteropConnection() // Add the nested scroll connection to your top level @Composable element // using the nestedScroll modifier. LazyColumn(modifier = Modifier.nestedScroll(nestedScrollInterop)) { items(20) { item -> Box( modifier = Modifier .padding(16.dp) .height(56.dp) .fillMaxWidth() .background(Color.Gray), contentAlignment = Alignment.Center ) { Text(item.toString()) } } } } } } }
Nadrzędny element kompozycyjny zawierający element podrzędny AndroidView
Ten scenariusz obejmuje implementację zagnieżdżonego interfejsu API interoperacyjności przewijania po stronie tworzenia – gdy istnieje nadrzędny obiekt kompozycyjny zawierający element podrzędny AndroidView
. Element AndroidView
implementuje właściwość NestedScrollDispatcher
, ponieważ działa jako element podrzędny wobec przewijającego elementu nadrzędnego w komponencie Compose, a także NestedScrollingParent3
, ponieważ pełni rolę elementu nadrzędnego względem przewijanego elementu podrzędnego View
. Element nadrzędny tworzenia będzie wówczas mógł otrzymywać zagnieżdżone delta przewijania z zagnieżdżonego elementu podrzędnego View
, który można przewijać.
Poniższy przykład pokazuje, jak w tym scenariuszu uzyskać zagnieżdżone przewijanie wraz z paskiem narzędzi zwijania widoku:
@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 korzystać z 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 ten przykład pokazuje, jak interfejs API zagnieżdżonego przewijania Interop API jest używany w BottomSheetDialogFragment
do zapewnienia skutecznego 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 przenoszenie delt z poziomu tworzenia na poziom View
. Dzięki temu element może uczestniczyć w zagnieżdżonym przewijaniu, ale nie umożliwia automatycznego przewijania elementów. W przypadku elementów kompozycyjnych, które nie są przewijane automatycznie, np. Box
lub Column
, delta takich komponentów nie będą rozpowszechniane w zagnieżdżonym systemie przewijania, a delta nie będą docierać do elementów NestedScrollConnection
w funkcji rememberNestedScrollInteropConnection()
, dlatego te delta nie dotrą do nadrzędnego komponentu View
. Aby rozwiązać ten problem, pamiętaj, aby ustawić modyfikatory przewijane na te typy zagnieżdżonych funkcji kompozycyjnych. Więcej informacji znajdziesz w poprzedniej sekcji poświęconej przewijaniu zagnieżdżonym.
Niedziałający element nadrzędny View
zawierający element podrzędny ComposeView
Widok nieobsługiwany to widok, w którym po stronie View
nie są zaimplementowane niezbędne interfejsy NestedScrolling
. Pamiętaj, że oznacza to, że zagnieżdżone przewijanie z tymi elementami Views
nie działa od razu. Views
, które nie obsługują współpracy, to RecyclerView
i ViewPager2
.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Gesty
- Przenieś
CoordinatorLayout
do tworzenia - Korzystanie z widoków w sekcji Utwórz