Modificatori di scorrimento
I modificatori verticalScroll
e horizontalScroll
offrono il modo più semplice per consentire all'utente di scorrere un elemento quando i limiti dei suoi contenuti sono superiori ai limiti di dimensione massima. Con i modificatori verticalScroll
e horizontalScroll
non è necessario tradurre o compensare i contenuti.
@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)) } } }
ScrollState
ti consente di modificare la posizione di scorrimento o di recuperare lo stato attuale. Per crearla con i parametri predefiniti, utilizza 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)) } } }
Modificatore scorrevole
Il modificatore di scrollable
è diverso da quelli di scorrimento perché scrollable
rileva i gesti di scorrimento, ma non ne sposta i contenuti. Affinché questo modificatore funzioni correttamente, è necessario un valore ScrollableState
.
Quando crei ScrollableState
devi fornire una funzione consumeScrollDelta
che verrà richiamata a ogni passaggio di scorrimento (tramite input gesto, scorrimento fluido o scorrimento continuo) con il delta in pixel.
Questa funzione deve restituire la quantità di distanza di scorrimento utilizzata, per garantire che l'evento venga propagato correttamente nei casi in cui sono presenti elementi nidificati che hanno il modificatore scrollable
.
Lo snippet riportato di seguito rileva i gesti e mostra un valore numerico per l'offset, ma non sfalsa alcun elemento:
@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()) } }
Scorrimento nidificato
Compose supporta lo scorrimento nidificato,in cui più elementi reagiscono a un singolo gesto di scorrimento. Un esempio tipico di scorrimento nidificato è un elenco all'interno di un altro elenco, mentre un caso più complesso è costituito da una barra degli strumenti compressa.
Scorrimento nidificato automatico
Lo scorrimento nidificato semplice non richiede alcuna azione da parte tua. I gesti che avviano un'azione di scorrimento vengono propagati automaticamente dai bambini ai genitori. In questo modo, quando il bambino non può scorrere oltre, il gesto viene gestito dall'elemento principale.
Lo scorrimento nidificato automatico è supportato e fornito pronto all'uso da alcuni
componenti e modificatori di Compose: verticalScroll
, horizontalScroll
,
scrollable
, le API Lazy
e TextField
. Ciò significa che quando l'utente scorre un elemento secondario interno di componenti nidificati, i modificatori precedenti propagano i delta di scorrimento agli elementi principali che hanno il supporto dello scorrimento nidificato.
L'esempio seguente mostra gli elementi a cui è applicato un modificatore verticalScroll
all'interno di un container a cui è applicato anche un modificatore 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) ) } } } } }
Utilizzo del modificatore nestedScroll
Se hai bisogno di creare uno scorrimento coordinato avanzato tra più elementi, il modificatore di nestedScroll
offre maggiore flessibilità definendo una gerarchia di scorrimento nidificato.
Come accennato nella sezione precedente, alcuni componenti dispongono di un supporto dello scorrimento nidificato integrato. Tuttavia, per gli elementi componibili che non possono scorrere automaticamente, come Box
o Column
, i delta di scorrimento su questi componenti non verranno propagati nel sistema di scorrimento nidificato e i delta non raggiungeranno NestedScrollConnection
né il componente principale. Per risolvere il problema, puoi utilizzare nestedScroll
per fornire questo supporto ad altri componenti, inclusi i componenti personalizzati.
Interoperabilità dello scorrimento nidificato (a partire da Compose 1.2.0)
Quando provi a nidificare gli elementi View
scorrevoli in
componibili a scorrimento o viceversa, potresti riscontrare problemi.
Quelli più evidenti si verificano quando scorri l'elemento secondario e raggiungi i limiti di inizio o fine e ti aspetti che lo scorrimento sia completato dal padre. Tuttavia, questo comportamento previsto potrebbe non verificarsi o non funzionare come previsto.
Questo problema è il risultato delle aspettative create negli elementi componibili scorrevoli.
I componibili scorrevoli hanno una regola "nested-scroll-by-default", il che significa che qualsiasi container scorrevole deve partecipare alla catena di scorrimento nidificata, sia come padre tramite NestedScrollConnection
sia come elemento secondario tramite NestedScrollDispatcher
.
L'asset secondario eseguirà quindi uno scorrimento nidificato per l'elemento padre quando quest'ultimo si trova al limite. Ad esempio, questa regola consente a Scrivi Pager
e Scrivi LazyRow
di funzionare bene insieme. Tuttavia, quando viene eseguito lo scorrimento dell'interoperabilità con ViewPager2
o RecyclerView
, dato che queste non implementano NestedScrollingParent3
, non è possibile lo scorrimento continuo da quello secondario all'elemento padre.
Per abilitare l'API di interoperabilità a scorrimento nidificata tra elementi View
scorrevoli e componibili a scorrimento, nidificati in entrambe le direzioni, puoi utilizzare l'API di interoperabilità di scorrimento nidificata per mitigare questi problemi negli scenari seguenti.
Un padre che collabora con View
e contiene un figlio ComposeView
Un elemento padre che collabora View
è un elemento che implementa già
NestedScrollingParent3
e pertanto può ricevere delta di scorrimento da un elemento componibile secondario nidificato
che cooperano. In questo caso ComposeView
agirebbe come figlio e dovrebbe
implementare (indirettamente)
NestedScrollingChild3
.
Un esempio di genitore che ha collaborato è
androidx.coordinatorlayout.widget.CoordinatorLayout
.
Se hai bisogno di un'interoperabilità dello scorrimento nidificata tra i container principali View
scorrevoli e gli elementi componibili secondari nidificati, puoi utilizzare rememberNestedScrollInteropConnection()
.
rememberNestedScrollInteropConnection()
consente e memorizza
NestedScrollConnection
che consente l'interoperabilità di scorrimento nidificata tra un elemento padre View
che
implementa
NestedScrollingParent3
e un elemento secondario di Compose. Dovrebbe essere usato in combinazione con un modificatore di nestedScroll
. Poiché lo scorrimento nidificato è abilitato per impostazione predefinita sul lato Scrivi, puoi utilizzare questa connessione per attivare sia lo scorrimento nidificato sul lato View
sia aggiungere la logica necessaria per il colla tra Views
e gli elementi componibili.
Un caso d'uso frequente utilizza CoordinatorLayout
, CollapsingToolbarLayout
e un
elemento componibile secondario, mostrato in questo esempio:
<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>
In Attività o Frammento, devi configurare l'elemento componibile di tuo figlio e
i requisiti
NestedScrollConnection
richiesti:
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()) } } } } } } }
Un componibile principale contenente un elemento secondario AndroidView
Questo scenario riguarda l'implementazione dell'API di interoperabilità a scorrimento nidificata sul lato Scrivi, quando hai un componibile padre contenente un elemento figlio AndroidView
. L'AndroidView
implementa
NestedScrollDispatcher
,
poiché agisce come elemento secondario per un elemento principale a scorrimento di Compose, nonché
NestedScrollingParent3
, poiché agisce come padre per un elemento figlio che scorre View
. L'elemento principale della scrittura potrà quindi ricevere i delta di scorrimento nidificati da un elemento figlio scorrevole nidificato View
.
L'esempio seguente mostra come ottenere l'interoperabilità dello scorrimento nidificato in questo scenario, insieme a una barra degli strumenti di compressione di 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) {
// ...
}
}
// ...
}
Questo esempio mostra come utilizzare l'API con un modificatore 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)
}
}
)
}
}
Infine, questo esempio mostra come l'API di interoperabilità di scorrimento nidificata viene utilizzata con BottomSheetDialogFragment
per ottenere un comportamento di trascinamento riuscito:
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
}
}
}
Tieni presente che rememberNestedScrollInteropConnection()
installerà un NestedScrollConnection
nell'elemento a cui lo colleghi. NestedScrollConnection
è responsabile della trasmissione dei delta dal livello di Compose al livello View
. In questo modo l'elemento può partecipare allo scorrimento nidificato, ma non viene attivato automaticamente lo scorrimento degli elementi. Per gli elementi componibili che non possono essere scorretti automaticamente, come Box
o Column
, i delta di scorrimento su questi componenti non verranno propagati nel sistema di scorrimento nidificato e i delta non raggiungeranno il valore NestedScrollConnection
fornito da rememberNestedScrollInteropConnection()
, pertanto non raggiungeranno il componente View
principale. Per risolvere il problema, assicurati di impostare anche modificatori scorrevoli su questi tipi di elementi componibili nidificati. Per informazioni più dettagliate, consulta la sezione precedente relativa allo scorrimento nidificato.
Un elemento principale non cooperante, View
, che contiene un account secondario ComposeView
Una vista che non collabora è un elemento che non implementa le interfacce NestedScrolling
necessarie sul lato View
. Tieni presente che ciò significa che
l'interoperabilità dello scorrimento nidificato con questi Views
non funziona
da subito. Views
che non collaborano sono RecyclerView
e ViewPager2
.
Consigliato per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Informazioni sui gesti
- Eseguire la migrazione di
CoordinatorLayout
a Compose - Utilizzare le visualizzazioni in Compose