La grande forme avec Jetpack Compose | de Heba Mekawi | février 2022
Comme toujours, toute application aura toujours besoin d’un écran de formulaire comme la connexion, l’inscription, la modification du profil, le formulaire de demande et bien d’autres…
Cet article vise à être une référence simple pour les composants de champs de formulaire communs
Voyons comment créer un formulaire avec Jetpack Compose 🚀
Nous couvrirons tous les points suivants :
- Action IME
- Mot de passe avec bascule de visibilité
- Numéro de mobile avec sélecteur de code de pays
- Sélecteur de date
- Sélecteur d’images de la galerie ou de l’appareil photo
Champ de texte de style
Avant de commencer, vous devez avoir une vue composable du composant de champ de texte en fonction de la conception de votre application, cela peut ressembler à ceci :
fun AppTextField(
modifier: Modifier = Modifier,
text: String,
placeholder: String,
leadingIcon: @Composable (() -> Unit)? = null,
onChange: (String) -> Unit = {},
imeAction: ImeAction = ImeAction.Next,
keyboardType: KeyboardType = KeyboardType.Text,
keyBoardActions: KeyboardActions = KeyboardActions(),
isEnabled: Boolean = true
) {
OutlinedTextField(
modifier = modifier.fillMaxWidth(),
value = text,
onValueChange = onChange,
leadingIcon = leadingIcon,
textStyle = TextStyle(fontSize = 18.sp),
keyboardOptions = KeyboardOptions(imeAction = imeAction, keyboardType = keyboardType),
keyboardActions = keyBoardActions,
enabled = isEnabled,
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = Color.Black,
unfocusedBorderColor = Color.Gray,
disabledBorderColor = Color.Gray,
disabledTextColor = Color.Black
),
placeholder = {
Text(text = placeholder, style = TextStyle(fontSize = 18.sp, color = Color.LightGray))
}
)
}
Cet exemple suit également MVVM afin que le modèle de vue contienne tous les états, tous peuvent être vides ou pré-remplis avec la valeur précédente (comme modifier le cas de l’écran)
class FormViewModel : ViewModel() {var firstName by mutableStateOf("")
var lastName by mutableStateOf("")
var password by mutableStateOf("")
var mobileNumber by mutableStateOf("")
var mobileCountryCode by mutableStateOf("")
var dateOfBirth by mutableStateOf("")
...
}
Ajouter des actions IME
Dans la vue de formulaire composable parent, nous pouvons ajouter plusieurs composants de champ de texte, tous attachés avec des actions IME du clavier
//User name text fieldColumn{ val focusManager = LocalFocusManager.current
AppTextField(
text = viewModel.firstName,
placeholder = "First Name",
onChange = {
viewModel.firstName = it
},
imeAction = ImeAction.Next,//Show next as IME button
keyboardType = KeyboardType.Text, //Plain text keyboard
keyBoardActions = KeyboardActions(
onNext = {
focusManager.moveFocus(FocusDirection.Down)
}
)
)
...
}
De plus, si la conception a deux vues de champ de texte dans une rangée horizontale, nous pouvons utiliser FocusDirection.Left ou FocusDirection.Right
Champ Mot de passe
Simple comme un champ de texte brut mais avec PasswordVisualTransformation
AppTextField(
text = viewModel.password,
placeholder = "Password",
onChange = {
viewModel.password = it
},
imeAction = ...
visualTransformation = PasswordVisualTransformation(),
keyboardType = KeyboardType.Password,
keyBoardActions = ...
)
Ajoutons aussi le Bascule de visibilité bouton icône pour afficher / masquer le mot de passe comme celui-ci
var isPasswordVisible by remember { mutableStateOf(false) }AppTextField(
....,
leadingIcon = {
IconButton(onClick = {
isPasswordVisible = !isPasswordVisible
}) {
Icon(
imageVector = if (isPasswordVisible)
Icons.Filled.Visibility
else
Icons.Filled.VisibilityOff,
contentDescription = "Password Visibility"
)
}
}, visualTransformation = if (isPasswordVisible)
VisualTransformation.None
else
PasswordVisualTransformation(),
...
)
Veuillez noter que pour utiliser ces icônes, vous aurez besoin de la dépendance suivante
implementation "androidx.compose.material:material-icons-extended:$compose_version"
Champ Numéro de portable
Identique au champ de texte brut mais avec le type de clavier du téléphone
keyboardType = KeyboardType.Phone,
Ajoutons le Sélecteur de code de pays dans le champ de texte comme suit
Et ajoutons l’étiquette de code de pays sélectionnée au champ de texte en tant qu’étiquette principale
leadingIcon = {
viewModel.mobileCountry?.let {
CountryPickerView(
countries = viewModel.countriesList
selectedCountry = it,
onSelection = { selectedCountry ->
viewModel.mobileCountry = selectedCountry
},
)
}
},
En utilisant CountryPickerUtils.kt, le modèle de vue contiendra les états nécessaires comme d’habitude
val countriesList = getCountriesList()
var mobileCountry by mutableStateOf<Country?>(null)
Et le composable CountryPickerView peut être comme ça
@Composable
fun CountryPickerView(
selectedCountry: Country,
onSelection: (Country) -> Unit,
countries: List<Country>
) {
var showDialog by remember { mutableStateOf(false) }
Text(
modifier = Modifier
.clickable {
showDialog = true
}
.padding(start = 20.dp, end = 5.dp),
text = "${getFlagEmojiFor(selectedCountry.nameCode)} +${selectedCountry.code}"
)if (showDialog)
CountryCodePickerDialog(countries, onSelection) {
showDialog = false
}
}
Et ceci est une implémentation très simple de CountryCodePickerDialog, améliorez absolument la conception selon vos besoins en fonction de la conception de votre application
@Composable
fun CountryCodePickerDialog(
countries: List<Country>,
onSelection: (Country) -> Unit,
dismiss: () -> Unit,
) {
Dialog(onDismissRequest = dismiss) {
Box {
LazyColumn(
Modifier
.fillMaxWidth()
.padding(horizontal = 30.dp, vertical = 40.dp)
.background(shape = RoundedCornerShape(20.dp), color = Color.White)
) {
for (country in countries) {
item {
Text(
modifier = Modifier
.clickable {
onSelection(country)
dismiss()
}
.fillMaxWidth()
.padding(10.dp),
text = "${getFlagEmojiFor(country.nameCode)} ${country.fullName}"
)
}
}
}
}
}
}
Champ de sélection de date
Il affichera une boîte de dialogue de sélection de date comme celle-ci
val context = LocalContext.current
AppTextField(
modifier = Modifier.clickable {
viewModel.showDatePickerDialog(context)
},
text = viewModel.dateOfBirth,
placeholder = "Birthdate",
onChange = {
viewModel.dateOfBirth = it
},
isEnabled = false
)
La boîte de dialogue de date affichera soit la date du jour, soit la dernière date sélectionnée dans la variable d’état dateOfBirth
private var dateFormat = "yyyy-MM-dd"fun showDatePickerDialog(context: Context) {
val calendar = getCalendar()
DatePickerDialog(
context, { _, year, month, day ->
dateOfBirth = getPickedDateAsString(year, month, day)
},
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
.show()
}private fun getCalendar(): Calendar {
return if (dateOfBirth.isEmpty())
Calendar.getInstance()
else
getLastPickedDateCalendar()
}private fun getLastPickedDateCalendar(): Calendar {
val dateFormat = SimpleDateFormat(dateFormat)
val calendar = Calendar.getInstance()
calendar.time = dateFormat.parse(dateOfBirth)
return calendar
}private fun getPickedDateAsString(year: Int, month: Int, day: Int): String {
val calendar = Calendar.getInstance()
calendar.set(year, month, day)
val dateFormat = SimpleDateFormat(dateFormat)
return dateFormat.format(calendar.time)
}
Sélecteur d’images
- Choisissez une image dans la galerie
- Capturer l’image de l’appareil photo
Nous allons d’abord permettre à l’utilisateur de choisir l’image de la galerie comme suit
Première importation de bibliothèque de composition de bobines
implementation "io.coil-kt:coil-compose:1.4.0"
et conserver l’uri de l’image sélectionnée dans le modèle de vue
val pickedImage = mutableStateOf<Uri?>(null)
Ajouter la vue du sélecteur d’images à la vue du formulaire
ImagePickerView(
modifier = Modifier.align(Alignment.CenterHorizontally),
lastSelectedImage = viewModel.pickedImage.value,
onSelection = {
viewModel.pickedImage.value = it
}
)
Pour choisir une image dans la galerie, le composable ImagePickerView utilisera le lanceur de galerie comme celui-ci
@Composable
fun ImagePickerView(
modifier: Modifier = Modifier,
lastSelectedImage: Uri?,
onSelection: (Uri?) -> Unit
) {
val galleryLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.GetContent()) {
onSelection(it)
}
Image(
modifier = modifier
.size(100.dp)
.clip(CircleShape)
.background(Color.LightGray)
.clickable {
galleryLauncher.launch("image/*")
},
painter = rememberImagePainter(lastSelectedImage),
contentDescription = "Profile Picture",
contentScale = ContentScale.Crop
)
}
Maintenant pour capturer l’image de la caméra comme suit
Commençons par gérer l’autorisation de la caméra à l’aide de la bibliothèque Accompanist et importons-la
implementation "com.google.accompanist:accompanist-permissions:0.24.1-alpha"
Et ajoutez l’autorisation de la caméra au fichier manifeste de l’application
<uses-permission android:name="android.permission.CAMERA" />
Maintenant, mettons à jour le composable ImagePickerView en
- Demander la permission
- Ouvrir le lanceur de caméra (si l’autorisation est juste accordée ou sur clic)
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun ImagePickerView(
modifier: Modifier = Modifier,
lastSelectedImage: Uri?,
onSelection: (Uri?) -> Unit
) {
val context = LocalContext.current
val cameraPermission = rememberPermissionState(Manifest.permission.CAMERA)
var isPermissionRequested by rememberSaveable { mutableStateOf(false) } val cameraLauncher: ManagedActivityResultLauncher<Void?, Bitmap?> =
rememberLauncherForActivityResult(ActivityResultContracts.TakePicturePreview()) {
onSelection(it?.toUri(context))
}
if (isPermissionRequested && cameraPermission.hasPermission) {
cameraLauncher.launch()
isPermissionRequested = false
}
Image(
modifier = modifier
.size(100.dp)
.clip(CircleShape)
.background(Color.LightGray)
.clickable {
if (!cameraPermission.hasPermission) {
cameraPermission.launchPermissionRequest()
isPermissionRequested = true
} else
cameraLauncher.launch()
},
painter = rememberImagePainter(lastSelectedImage),
contentDescription = "Profile Picture",
contentScale = ContentScale.Crop
)
}
Veuillez également noter que l’état d’autorisation de l’accompagnateur a devrait afficher la justification que vous pouvez utiliser pour informer l’utilisateur pourquoi vous avez besoin de cette autorisation avant de la demander, en savoir plus à ce sujet ici
C’est ça! Tu l’as fait! 💪
Voyez-vous quelque chose qui manque? commentez s’il vous plaît!
Merci d’avoir lu, rendez-vous dans le prochain article 😊
Commentaires
Laisser un commentaire