Kotlin
A concise multiplatform language developed by JetBrains
Publication de Compose Multiplatform 1.5.0
Nous avons le plaisir d’annoncer la disponibilité de Compose Multiplatform 1.5.0. Cette mise à jour utilise le framework d’interface utilisateur déclaratif Jetpack Compose pour Kotlin et étend sa prise en charge à de nouvelles plateformes, au-delà d’Android : bureau, iOS et web. La version bureau est stable, la versions iOS est en phase Alpha et la prise en charge pour le web est encore expérimentale. Pour une présentation détaillée, veuillez consulter le site Compose Multiplatform.
Voici les principales améliorations qu’apporte cette mise à jour :
- Les API Dialog, Popup et WindowInsets sont maintenant en code commun.
- Nous avons amélioré le défilement, la gestion des ressources et les champs de texte sous iOS.
- Le framework de test de l’interface utilisateur a été stabilisé sur la version bureau.
Compose Multiplatform 1.5.0 est basé sur Jetpack Compose 1.5, qui est principalement dédié à l’amélioration des performances. Elle s’appuie également sur la version 1.1 de Material Design 3, qui fournit de nouveaux composants tels que les sélecteurs de date et d’heure.
Essayer Compose Multiplatform 1.5.0
Compose Multiplatform prend en charge les boîtes de dialogue, popups et WindowInsets
Les boîtes de dialogue et les popups sont maintenant disponibles dans Compose Multiplatform. Les boîtes de dialogue sont utilisées pour les événements modaux, qui requièrent une action de la part de l’utilisateur, par exemple faire un choix ou saisir des données. Les fenêtres contextuelles sont quant à elles utilisées pour les actions non modales, comme la fourniture de fonctionnalités optionnelles.
Dans cette version, les types de base Dialog et Popup, ainsi que DropdownMenu and AlertDialog, sont accessibles à partir du code commun, ce qui évite de devoir fournir des fonctionnalités spécifiques aux plateformes.
Par exemple, l’élément Composable ci-dessous est entièrement écrit en code commun :
@Composable fun CommonDialog() { var isDialogOpen by remember { mutableStateOf(false) } Button(onClick = { isDialogOpen = true }) { Text("Open") } if (isDialogOpen) { AlertDialog( onDismissRequest = { }, confirmButton = { Button(onClick = { isDialogOpen = false }) { Text("OK") } }, title = { Text("Alert Dialog") }, text = { Text("Lore ipsum") }, ) } }
Voici comment il apparaît sur bureau, Android et iOS :
Une troisième fonctionnalité disponible dans cette version est l’API WindowInsets API, qui décrit le niveau d’ajustement requis pour éviter que votre contenu chevauche l’interface utilisateur du système. Avec Compose Multiplatform 1.5.0, cette fonctionnalité peut être utilisée aussi bien sur Android que sur iOS.
Grâce à l’API WindowInsets, vous pouvez dessiner du contenu d’arrière-plan via Compose Multiplatform derrière l’encoche, sans ajout de ligne blanche en haut de l’application. Les captures d’écran ci-dessous montrent la différence que cela fait :
Améliorations pour iOS
Cette mise jour apporte de nombreuses améliorations pour la plateforme iOS. Le défilement reproduit le look and feel de la plateforme, la gestion des ressources a été simplifiée et la manipulation du texte améliorée.
Défilement naturel
Nous avons adapté le défilement sur iOS de façon à ce qu’il reproduise le défilement natif. Supposons que nous ayons du code dans lequel le nombre et/ou la taille des éléments à afficher excède l’espace disponible :
@Composable fun NaturalScrolling() { val items = (1..30).map { "Item $it" } LazyColumn { items(items) { Text( text = it, fontSize = 30.sp, modifier = Modifier.padding(start = 20.dp) ) } } }
Lors du défilement, les éléments apparaissent depuis les bords de l’écran, comme avec les applications iPhone natives :
Prise en charge de Dynamic Type
La fonctionnalité Dynamic Type sur iOS permet à l’utilisateur de définir sa taille de police préférée : plus grande pour une meilleure lisibilité ou plus petite pour afficher plus de contenu. La taille du texte utilisée dans une application doit être relative à ce paramètre système.
Cette fonctionnalité est maintenant prise en charge dans Compose Multiplatform. Les incréments utilisés pour le redimensionnement du texte sont les mêmes que ceux utilisés dans les applications natives, le comportement sera donc identique.
Prenons par exemple le Composable suivant :
@Composable fun DynamicType() { Text("This is some sample text", fontSize = 30.sp) }
Voici ce qui s’affiche lorsque la taille de lecture préférée est définie au minimum :
Et voici ce que cela donne lorsque la taille de lecture choisie est au maximum :
Prise en charge des écrans à taux de rafraîchissement élevé
Dans les versions précédentes, la fréquence d’images maximale était de 60 FPS, Cela pouvait entraîner des problèmes de lenteur et de latence de l’interface utilisateur sur les appareils dotés d’écrans 120 Hz. La nouvelle version prend en charge des fréquences d’images allant jusqu’à 120 FPS.
Simplification de la gestion des ressources
À partir de la version 1.5.0, touts les éléments du dossier « resources » d’un ensemble de sources iOS sont copiés dans le bundle de l’application par défaut. Si vous placez un fichier image dans src/commonMain/resources/
, il sera donc copié dans le paquet et utilisable dans votre code.
Si vous utilisez CocoaPods, désormais vous n’avez plus à configurer ce comportement dans le fichier de build Gradle. Il n’est pas nécessaire non plus d’appeler de nouveau podInstall pour que les ressources sont copiées après modification.
Si vous essayez de configurer le comportement explicitement dans les scripts de build (comme indiqué ci-dessous), une erreur se produira :
kotlin { cocoapods { extraSpecAttributes["resources"] = "..." } }
Pour plus de détails et un guide pour la migration du code existant, consultez ce document.
Amélioration de TextField
Dans les versions précédentes, deux cas de saisie de texte pouvaient entraîner un comportement indésirable. Pour la nouvelle version, nous avons travaillé à l’amélioration de TextField
afin d’éradiquer ces problèmes.
Problèmes de capitalisation
Tout d’abord, TextField
est maintenant capable de détecter si la mise en majuscule automatique de la première lettre a été désactivée. Cela peut notamment être utile lors de la saisie de mots de passe. Le contrôle de ce comportement se fait via l’argument KeyboardOptions.
Regardez le Composable ci-dessous :
fun TextFieldCapitalization() { var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, autoCorrect = false, keyboardType = KeyboardType.Ascii, ), ) }
L’image de gauche montre ce que l’on obtient lorsque la propriété de capitalisation est définie sur KeyboardCapitalization.None
. Sur l’image de droite, on voit le résultat lorsque la valeur est définie sur KeyboardCapitalization.Sentences
.
Claviers physiques
La deuxième situation concerne les claviers physiques. Dans les versions précédentes, lors de l’utilisation d’un clavier physique, appuyer sur Entrée entraînait l’ajout plusieurs nouvelles lignes, et appuyer sur Retour arrière déclenchait plusieurs suppressions. Avec la nouvelle version, ces événements sont désormais traités correctement.
Améliorations pour desktop
Stabilisation du framework de test
Cette version stabilise la prise en charge des tests sur Compose for Desktop. Jetpack Compose fournit un ensemble d’API de tests pour vérifier le comportement de votre code Compose. Ces API ont été portées sur desktop et la nouvelle version a supprimé toutes les limitations de disponibilité que l’on pouvait rencontrer auparavant, vous pouvez donc désormais écrire des tests d’interface utilisateur complets pour votre application.
Créons et testons une interface utilisateur simple pour explorer brièvement la fonctionnalité de test. Voici notre exemple de Composable :
@Composable fun App() { var searchText by remember { mutableStateOf("cats") } val searchHistory = remember { mutableStateListOf() } Column(modifier = Modifier.padding(30.dp)) { TextField( modifier = Modifier.testTag("searchText"), value = searchText, onValueChange = { searchText = it } ) Button( modifier = Modifier.testTag("search"), onClick = { searchHistory.add("You searched for: $searchText") } ) { Text("Search") } LazyColumn { items(searchHistory) { Text( text = it, fontSize = 20.sp, modifier = Modifier.padding(start = 10.dp).testTag("attempt") ) } } } }
Cela crée une interface utilisateur simple qui enregistre les tentatives de recherche :
Vous remarquerez que Modifier.testTag
est utilisé pour attribuer des noms à TextField
, Button
et aux éléments dans LazyColumn.
Nous pouvons alors manipuler l’interface utilisateur dans un test JUnit :
class SearchAppTest { @get:Rule val compose = createComposeRule() @Test fun `Should display search attempts`() { compose.setContent { App() } val testSearches = listOf("cats", "dogs", "fish", "birds") for (text in testSearches) { compose.onNodeWithTag("searchText").performTextReplacement(text) compose.onNodeWithTag("search").performClick() } val lastAttempt = compose .onAllNodesWithTag("attempt") .assertCountEquals(testSearches.size) .onLast() val expectedText = "You searched for: ${testSearches.last()}" lastAttempt.assert(hasText(expectedText)) } }
En utilisant la règle JUnit spécifique à Compose, nous :
- Définissons le contenu de l’interface utilisateur en tant qu’App Composable.
- Repérons le champ de texte et le bouton à l’aide de
onNodeWithTag
. - Entrons à plusieurs reprises des exemples de valeurs dans le champ de texte et cliquons sur le bouton.
- Trouvons tous les nœuds de texte qui ont été générés via
onAllNodesWithTag
. - Vérifions que le nombre actuel de nœuds de texte ont été créés et récupérons le dernier.
- Vérifions que cette dernière tentative contient le message attendu.
Amélioration de l’interopérabilité avec Swing
Cette version introduit une prise en charge expérimentale visant à améliorer le rendu des panneaux de composition dans les composants Swing. Cela évite les problèmes de rendu transitoires lorsque les panneaux sont affichés, masqués ou redimensionnés. Cela permet également d’avoir une superposition correcte lors de la combinaison de composants Swing et de panneaux compose. Un composant Swing peut désormais être affiché au-dessus ou en dessous d’un ComposePanel
.
Regardez cet exemple :
fun main() { System.setProperty("compose.swing.render.on.graphics", "true") SwingUtilities.invokeLater { val composePanel = ComposePanel().apply { setContent { Box(modifier = Modifier.background(Color.Black).fillMaxSize()) } } val popup = object : JComponent() { ... } val rightPanel = JLayeredPane().apply { add(composePanel) add(popup) ... } val leftPanel = JPanel().apply { background = CYAN } val splitter = JSplitPane(..., leftPanel,rightPanel) JFrame().apply { add(splitter) setSize(600, 600) isVisible = true } } }
Ce code crée et affiche une JFrame
Swing avec le contenu suivant :
- La JFrame contient un
JSplitPane
avec un séparateur vertical. - Sur la gauche du volet divisé se trouve un
JPanel
standard de couleur cyan. - À droite se trouve un
JLayeredPane
composé de deux couches :- Un
ComposePanel
contenant un composableBox
de couleur noire - Un composant Swing personnalisé, dans lequel le texte « Popup » apparaît dans un rectangle blanc. Nous avons réalisé cela en remplaçant la méthode
paintComponent
.
- Un
Lorsque la propriété compose.swing.render.on.graphics
est définie sur true, alors :
- Le composant Swing personnalisé s’affiche au-dessus du Composable
Box
. - Il n’y a pas d’artefacts graphiques de transition lorsque le curseur est déplacé.
Si ce flag n’avait pas été défini, le composant personnalisé ne serait pas visible et il pourrait y avoir des artefacts de transition lors du déplacement du curseur :
Faites-nous part de vos commentaires sur Compose Multiplatform et participez aux discussions sur Compose Multiplatform et Jetpack Compose en rejoignant le canal Slack #compose de Kotlin. Vous trouverez les discussions sur Compose Multiplatform for iOS dans le canal dédié #compose-ios.
Essayer Compose Multiplatform 1.5.0
Autres lectures et vidéos
Auteur de l’article original en anglais :