Dans Jetpack Compose, scrollable2D et draggable2D sont des modificateurs de bas niveau conçus pour gérer les entrées de pointeur en deux dimensions. Alors que les modificateurs 1D standards scrollable et draggable sont limités à une seule orientation, les variantes 2D suivent simultanément les mouvements sur les axes X et Y.
Par exemple, le modificateur scrollable existant est utilisé pour le défilement et le balayage dans une seule orientation, tandis que scrollable2d est utilisé pour le défilement et le balayage en 2D. Cela vous permet de créer des mises en page plus complexes qui se déplacent dans toutes les directions, comme des feuilles de calcul ou des visionneuses d'images. Le modificateur scrollable2d est également compatible avec le défilement imbriqué dans les scénarios 2D.
Sélectionnez scrollable2D ou draggable2D.
Le choix de l'API dépend des éléments d'interface utilisateur que vous souhaitez déplacer et du comportement physique souhaité pour ces éléments.
Modifier.scrollable2D : utilisez ce modificateur sur un conteneur pour déplacer le contenu qu'il contient. Par exemple, utilisez-le avec des cartes, des feuilles de calcul ou des visionneuses de photos, où le contenu du conteneur doit défiler horizontalement et verticalement. Il inclut une prise en charge intégrée du balayage, de sorte que le contenu continue de défiler après un balayage, et il se coordonne avec les autres composants de défilement de la page.
Modifier.draggable2D : utilisez ce modificateur pour déplacer un composant lui-même. Il s'agit d'un modificateur léger, de sorte que le mouvement s'arrête exactement lorsque le doigt de l'utilisateur s'arrête. Il n'inclut pas la prise en charge du lancer.
Si vous souhaitez rendre un composant déplaçable, mais que vous n'avez pas besoin de la prise en charge du défilement rapide ou du défilement imbriqué, utilisez draggable2D.
Implémenter des modificateurs 2D
Les sections suivantes fournissent des exemples d'utilisation des modificateurs 2D.
Implémenter Modifier.scrollable2D
Utilisez ce modificateur pour les conteneurs dans lesquels l'utilisateur doit déplacer du contenu dans toutes les directions.
Capturer des données de mouvement en 2D
Cet exemple montre comment capturer des données de mouvement 2D brutes et afficher le décalage X,Y :
@Composable private fun Scrollable2DSample() { // 1. Manually track the total distance the user has moved in both X and Y directions var offset by remember { mutableStateOf(Offset.Zero) } Box( modifier = Modifier .fillMaxSize() // ... contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(200.dp) // 2. Attach the 2D scroll logic to capture XY movement deltas .scrollable2D( state = rememberScrollable2DState { delta -> // 3. Update the cumulative offset state with the new movement delta offset += delta // Return the delta to indicate the entire movement was handled by this box delta } ) // ... contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { // 4. Display the current X and Y values from the offset state in real-time Text( text = "X: ${offset.x.roundToInt()}", // ... ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "Y: ${offset.y.roundToInt()}", // ... ) } } } }
L'extrait de code précédent effectue les opérations suivantes :
- Utilise
offsetcomme état contenant la distance totale parcourue par l'utilisateur. - Dans
rememberScrollable2DState, une fonction lambda est définie pour gérer chaque delta généré par le doigt de l'utilisateur. Le codeoffset.value += deltamet à jour l'état manuel avec la nouvelle position. - Les composants
Textaffichent les valeurs X et Y actuelles de cet étatoffset, qui sont mises à jour en temps réel lorsque l'utilisateur fait glisser l'élément.
Faire un panoramique sur une grande fenêtre d'affichage
Cet exemple montre comment utiliser des données capturées à défilement 2D et appliquer un translationX et un translationY à un contenu plus grand que son conteneur parent :
@Composable private fun Panning2DImage() { // Manually track the total distance the user has moved in both X and Y directions val offset = remember { mutableStateOf(Offset.Zero) } // Define how gestures are captured. The lambda is called for every finger movement val scrollState = rememberScrollable2DState { delta -> offset.value += delta delta } // The Viewport (Container): A fixed-size box that acts as a window into the larger content Box( modifier = Modifier .size(600.dp, 400.dp) // The visible area dimensions // ... // Hide any parts of the large content that sit outside this container's boundaries .clipToBounds() // Apply the 2D scroll modifier to intercept touch and fling gestures in all directions .scrollable2D(state = scrollState), contentAlignment = Alignment.Center, ) { // The Content: An image given a much larger size than the container viewport Image( painter = painterResource(R.drawable.cheese_5), contentDescription = null, modifier = Modifier .requiredSize(1200.dp, 800.dp) // Manual Scroll Effect: Since scrollable2D doesn't move content automatically, // we use graphicsLayer to shift the drawing position based on the tracked offset. .graphicsLayer { translationX = offset.value.x translationY = offset.value.y }, contentScale = ContentScale.FillBounds ) } }
Modifier.scrollable2D.Modifier.scrollable2D.L'extrait de code précédent inclut les éléments suivants :
- Le conteneur est défini sur une taille fixe (
600x400dp), tandis que le contenu reçoit une taille beaucoup plus grande (1200x800dp) pour éviter qu'il ne soit redimensionné à la taille de son parent. - Le modificateur
clipToBounds()sur le conteneur garantit que toute partie du contenu volumineux qui se trouve en dehors de la zone600x400est masquée. - Contrairement aux composants de haut niveau tels que
LazyColumn,scrollable2Dne déplace pas automatiquement le contenu pour vous. Vous devez plutôt appliquer leoffsetsuivi à votre contenu, soit à l'aide de transformationsgraphicsLayer, soit à l'aide de décalages de mise en page. - Dans le bloc
graphicsLayer,translationX = offset.value.xettranslationY = offset.value.ydécalent la position de dessin de l'image ou du texte en fonction du mouvement de votre doigt, ce qui crée l'effet visuel de défilement.
Implémenter le défilement imbriqué avec scrollable2D
Cet exemple montre comment un composant bidirectionnel peut être intégré à un parent unidimensionnel standard, comme un flux d'actualités vertical.
Tenez compte des points suivants lorsque vous implémentez le défilement imbriqué :
- Le lambda pour
rememberScrollable2DStatene doit renvoyer que le delta consommé, pour permettre à la liste parente de prendre le relais naturellement lorsque l'enfant atteint sa limite. - Lorsqu'un utilisateur effectue un balayage diagonal, la vitesse 2D est partagée. Si l'enfant atteint une limite pendant l'animation, l'élan restant est propagé au parent pour poursuivre le défilement naturellement.
@Composable private fun NestedScrollable2DSample() { var offset by remember { mutableStateOf(Offset.Zero) } val maxScrollDp = 250.dp val maxScrollPx = with(LocalDensity.current) { maxScrollDp.toPx() } Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) .background(Color(0xFFF5F5F5)), horizontalAlignment = Alignment.CenterHorizontally ) { Text( "Scroll down to find the 2D Box", modifier = Modifier.padding(top = 100.dp, bottom = 500.dp), style = TextStyle(fontSize = 18.sp, color = Color.Gray) ) // The Child: A 2D scrollable box with nested scroll coordination Box( modifier = Modifier .size(250.dp) .scrollable2D( state = rememberScrollable2DState { delta -> val oldOffset = offset // Calculate new potential offset and clamp it to our boundaries val newX = (oldOffset.x + delta.x).coerceIn(-maxScrollPx, maxScrollPx) val newY = (oldOffset.y + delta.y).coerceIn(-maxScrollPx, maxScrollPx) val newOffset = Offset(newX, newY) // Calculate exactly how much was consumed by the child val consumed = newOffset - oldOffset offset = newOffset // IMPORTANT: Return ONLY the consumed delta. // The remaining (unconsumed) delta propagates to the parent Column. consumed } ) // ... contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { val density = LocalDensity.current Text("2D Panning Zone", color = Color.White.copy(alpha = 0.7f), fontSize = 12.sp) Spacer(Modifier.height(8.dp)) Text("X: ${with(density) { offset.x.toDp().value.roundToInt() }}dp", color = Color.White, fontWeight = FontWeight.Bold) Text("Y: ${with(density) { offset.y.toDp().value.roundToInt() }}dp", color = Color.White, fontWeight = FontWeight.Bold) } } Text( "Once the Purple Box hits Y: 250 or -250,\nthis parent list will take over the vertical scroll.", textAlign = TextAlign.Center, modifier = Modifier.padding(top = 40.dp, bottom = 800.dp), style = TextStyle(fontSize = 14.sp, color = Color.Gray) ) } }
Dans l'extrait précédent :
- Le composant 2D peut utiliser le mouvement de l'axe X pour effectuer un panoramique en interne, tout en envoyant simultanément le mouvement de l'axe Y à la liste parente une fois que les limites verticales de l'enfant sont atteintes.
- Au lieu de piéger l'utilisateur dans la surface 2D, le système calcule le delta consommé et transmet le reste à la hiérarchie. L'utilisateur peut ainsi continuer à faire défiler le reste de la page sans lever le doigt.
Implémenter Modifier.draggable2D
Pour en savoir plus sur l'utilisation de Modifier.draggable2D, consultez Faire glisser en deux dimensions avec Modifier.draggable2D.