Scroll-Modifikatoren
Die Modifikatoren verticalScroll
und horizontalScroll
sind die einfachste Möglichkeit, dem Nutzer das Scrollen durch ein Element zu ermöglichen, wenn die Grenzen seines Inhalts die maximale Größe überschreiten. Mit den Modifikatoren verticalScroll
und horizontalScroll
müssen Sie den Inhalt nicht übersetzen oder verschieben.
@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)) } } }
Mit ScrollState
kannst du die Scrollposition ändern oder den aktuellen Status abrufen. Wenn Sie sie mit Standardparametern erstellen möchten, verwenden Sie 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)) } } }
Scrollbarer Modifikator
Der scrollable
-Modifikator unterscheidet sich von den Scroll-Modifikatoren dadurch, dass scrollable
die Scroll-Gesten erkennt, aber ihren Inhalt nicht versetzt. Damit dieser Modifikator korrekt funktioniert, ist ein ScrollableState
erforderlich.
Beim Erstellen von ScrollableState
musst du eine consumeScrollDelta
-Funktion bereitstellen, die bei jedem Scrollschritt (durch Gesteneingabe, optimiertes Scrollen oder Ziehen) mit dem Delta in Pixeln aufgerufen wird.
Diese Funktion muss die verbrauchte Scrollstrecke zurückgeben, damit das Ereignis in Fällen korrekt weitergegeben wird, in denen verschachtelte Elemente mit dem Modifikator scrollable
vorhanden sind.
Das folgende Snippet erkennt die Gesten und zeigt einen numerischen Wert für einen Offset an, verschiebt aber keine Elemente:
@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()) } }
Verschachteltes Scrollen
Die Funktion „Compose“ unterstützt das verschachtelte Scrollen,bei dem mehrere Elemente auf eine einzelne Scroll-Geste reagieren. Ein typisches Beispiel für verschachteltes Scrollen ist eine Liste innerhalb einer anderen Liste. Ein komplexerer Fall ist eine minimierbare Symbolleiste.
Automatisches verschachteltes Scrollen
Einfaches verschachteltes Scrollen erfordert von Ihrer Seite keine Maßnahmen. Gesten, die einen Scrollvorgang starten, werden automatisch von den untergeordneten Elementen an die übergeordneten Elemente weitergegeben. Wenn das Kind also nicht weiter scrollen kann, wird die Geste vom übergeordneten Element ausgeführt.
Automatisches verschachteltes Scrollen wird von einigen Komponenten und Modifikatoren von Composer unterstützt und standardmäßig bereitgestellt: verticalScroll
, horizontalScroll
, scrollable
, Lazy
APIs und TextField
. Wenn der Nutzer also in einem inneren untergeordneten Element verschachtelter Komponenten scrollt, geben die vorherigen Modifikatoren die Scroll-Deltas an die übergeordneten Elemente weiter, die verschachteltes Scrollen unterstützen.
Im folgenden Beispiel sehen Sie Elemente mit dem Modifizierer verticalScroll
in einem Container, auf den auch der Modifizierer verticalScroll
angewendet wird.
@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) ) } } } } }
Den nestedScroll
-Modifikator verwenden
Wenn Sie ein erweitertes koordiniertes Scrollen zwischen mehreren Elementen erstellen müssen, bietet der Modifizierer nestedScroll
mehr Flexibilität, da Sie eine verschachtelte Scroll-Hierarchie definieren.
Wie im vorherigen Abschnitt erwähnt, haben einige Komponenten eine integrierte Scrollunterstützung. Bei zusammensetzbaren Funktionen, die nicht automatisch gescrollt werden können, z. B. Box
oder Column
, werden Scroll-Deltas für diese Komponenten jedoch im verschachtelten Scrollsystem nicht weitergegeben und die Deltas erreichen weder NestedScrollConnection
noch die übergeordnete Komponente. Sie können dieses Problem beheben, indem Sie nestedScroll
verwenden, um diese Unterstützung auf andere Komponenten, einschließlich benutzerdefinierter Komponenten, zu übertragen.
Verschachtelte Scroll-Interoperabilität (ab Compose 1.2.0)
Wenn Sie versuchen, scrollbare View
-Elemente in scrollbaren zusammensetzbaren Funktionen zu verschachteln oder umgekehrt, können Probleme auftreten.
Die spärlichsten treten auf, wenn Sie das untergeordnete Element scrollen, seine Start- oder Endgrenzen erreichen und erwarten, dass das übergeordnete Element den Scrollvorgang übernimmt. Dieses erwartete Verhalten tritt jedoch möglicherweise nicht ein oder funktioniert nicht wie erwartet.
Dieses Problem ist auf die Erwartungen an scrollbare zusammensetzbare Funktionen zurückzuführen.
Für scrollbare zusammensetzbare Funktionen gilt eine „standardmäßige verschachtelte Scrolling“-Regel. Das bedeutet, dass jeder scrollbare Container Teil der verschachtelten Scrollkette sein muss, sowohl als übergeordneter Container über NestedScrollConnection
als auch als untergeordneter Container über NestedScrollDispatcher
.
Das untergeordnete Element würde dann einen verschachtelten Scrollvorgang für das übergeordnete Element ausführen, wenn sich das untergeordnete Element am Grenzwert befindet. Diese Regel sorgt beispielsweise dafür, dass die Funktionen „Schreiben“ Pager
und „Schreiben“ LazyRow
gut zusammen funktionieren. Wenn jedoch mit ViewPager2
oder RecyclerView
Interoperabilität gescrollt wird, da NestedScrollingParent3
dort nicht implementiert ist, ist das kontinuierliche Scrollen von den untergeordneten zu übergeordneten Elementen nicht möglich.
Wenn Sie die verschachtelte Scrolling Interop API zwischen scrollbaren View
-Elementen und scrollbaren zusammensetzbaren Funktionen aktivieren möchten, die in beide Richtungen verschachtelt sind, können Sie die verschachtelte Scrolling Interop API verwenden, um diese Probleme in den folgenden Szenarien zu minimieren.
Ein kooperierendes übergeordnetes Element (View
) mit einem untergeordneten ComposeView
Eine kooperierende übergeordnete View
ist eine, die NestedScrollingParent3
bereits implementiert und daher Scroll-Deltas von einer kooperierenden verschachtelten untergeordneten zusammensetzbaren Funktion empfangen kann. ComposeView
würde in diesem Fall als untergeordnetes Netzwerk fungieren und NestedScrollingChild3
(indirekt) implementieren.
Ein Beispiel für ein kooperierendes übergeordnetes Element ist androidx.coordinatorlayout.widget.CoordinatorLayout
.
Wenn Sie verschachtelte Scroll-Interoperabilität zwischen scrollbaren übergeordneten View
-Containern und verschachtelten scrollbaren untergeordneten zusammensetzbaren Funktionen benötigen, können Sie rememberNestedScrollInteropConnection()
verwenden.
rememberNestedScrollInteropConnection()
lässt und speichert den NestedScrollConnection
, der verschachtelte Scroll-Interoperabilität zwischen einem übergeordneten View
-Element, das NestedScrollingParent3
implementiert, und einer untergeordneten „Compose“-Datei ermöglicht. Dieser sollte in Verbindung mit einem nestedScroll
-Modifikator verwendet werden. Da das verschachtelte Scrollen auf der Seite „Compose“ standardmäßig aktiviert ist, können Sie über diese Verbindung sowohl verschachteltes Scrollen auf der View
-Seite aktivieren und die erforderliche Klebelogik zwischen Views
und zusammensetzbaren Funktionen einfügen.
Ein häufiger Anwendungsfall ist die Verwendung von CoordinatorLayout
, CollapsingToolbarLayout
und einer untergeordneten zusammensetzbaren Funktion, wie in diesem Beispiel:
<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>
Du musst in deiner Aktivität oder deinem Fragment die untergeordnete zusammensetzbare Funktion und das erforderliche NestedScrollConnection
einrichten:
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()) } } } } } } }
Eine übergeordnete zusammensetzbare Funktion, die das untergeordnete AndroidView
enthält
In diesem Szenario wird die Implementierung der verschachtelten Scroll-Interop API auf der Erstellungsseite beschrieben, wenn Sie eine übergeordnete zusammensetzbare Funktion haben, die ein untergeordnetes AndroidView
-Element enthält. Mit AndroidView
wird NestedScrollDispatcher
implementiert, da es dem übergeordneten Element „Compose“ als untergeordnetes Element fungiert. Außerdem wird NestedScrollingParent3
als übergeordnetes Element für ein untergeordnetes Element des Typs View
verwendet, das dem Bildlauf untergeordnet ist. Das übergeordnete Element „Compose“ kann dann verschachtelte Scrolldeltas von einem verschachtelten scrollbaren untergeordneten View
empfangen.
Das folgende Beispiel zeigt, wie Sie in diesem Szenario verschachtelte Scroll-Interop zusammen mit einer minimierbaren Symbolleiste "Compose" erstellen können:
@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) {
// ...
}
}
// ...
}
Dieses Beispiel zeigt, wie Sie die API mit einem scrollable
-Modifikator verwenden können:
@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)
}
}
)
}
}
Abschließend wird in diesem Beispiel gezeigt, wie die verschachtelte Scroll-Interop API mit BottomSheetDialogFragment
verwendet wird, um ein erfolgreiches Ziehen und Schließen zu erreichen:
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
}
}
}
Mit rememberNestedScrollInteropConnection()
wird ein NestedScrollConnection
-Element in dem Element installiert, an das Sie es anhängen. NestedScrollConnection
ist für die Übertragung der Deltas von der Erstellungsebene in die Ebene View
verantwortlich. Dadurch kann das Element am verschachtelten Scrollen teilnehmen, aber nicht automatisch das Scrollen von Elementen. Bei zusammensetzbaren Funktionen, die nicht automatisch gescrollt werden können, z. B. Box
oder Column
, werden Scroll-Deltas dieser Komponenten im verschachtelten Scrollsystem nicht weitergegeben. Sie erreichen außerdem nicht den von rememberNestedScrollInteropConnection()
bereitgestellten NestedScrollConnection
. Daher erreichen diese Deltas nicht die übergeordnete View
-Komponente. Um dieses Problem zu beheben, müssen Sie auch scrollbare Modifikatoren auf diese Arten verschachtelter zusammensetzbarer Funktionen festlegen. Weitere Informationen finden Sie im vorherigen Abschnitt zum verschachtelten Scrollen.
Ein nicht kooperierendes übergeordnetes Element (View
) mit einem untergeordneten ComposeView
In einer nicht kooperativen Ansicht werden die erforderlichen NestedScrolling
-Schnittstellen auf der View
-Seite nicht implementiert. Das bedeutet, dass die Interoperabilität mit verschachteltem Scrollen mit diesen Views
nicht standardmäßig funktioniert. Die nicht kooperierenden Views
sind RecyclerView
und ViewPager2
.
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Gesten und Bewegungen
CoordinatorLayout
zum Schreiben migrieren- Ansichten in Compose verwenden