Diseño de API de tragamonedas en Jetpack Compose | de Anton Popov | diciembre 2022

Foto de Towfiqu barbhuiya Restricción del contenido de la lambda componible mediante el modificador layoutId. Jetpack Compose nos presentó un nuevo concepto: Slot API. Permite a los desarrolladores crear componentes de interfaz de usuario reutilizables flexibles pero fáciles de usar. Sin embargo, a veces hay demasiada flexibilidad: necesitamos una forma de colocar solo una cierta cantidad de componentes de la interfaz de usuario en una ranura. Pero ¿cómo se hace? Hoy lo descubriremos. ¡Abróchate el cinturón! Imagina que diseñamos nuestra propia TopAppBar:@Composablefun TopAppBar(title: String,icon: @Composable () -> Unit,)Y ya tenemos un Icon personalizado:@Composablefun Icon(painter: Painter , tint: Color = DefaultTintColor ) Pero queremos que los usuarios de TopAppBar puedan colocar uno y solo un icono componible en una ranura de icono. La forma más sencilla de hacerlo es: @Composablefun TopAppBar(título: Cadena, icono: pintor: Pintor, iconTint: Color = DefaultTintColor, ) { // …Icon(painter, iconTint)// …} Sin embargo, si un componente de icono tiene muchos parámetros (5-9 o incluso más) y/o TopAppBar tiene muchos iconos, esta solución no será práctica. Podemos crear una clase de datos TopAppBarIcon específicamente para TopAppBar: Clase de datos TopAppBarIcon(pintor de val: Painter,tint de val: Color = DefaultTintColor,)@Composablefun TopAppBar(título: String,icono: TopAppBarIcon,) {// … Icon(icon .pintor, icon.tint)// …}Sin embargo, esta solución tiene muchas desventajas:

  1. duplicación de código. Una lista de parámetros de iconos y sus valores predeterminados están duplicados en TopAppBarIcon, lo que será un dolor de cabeza.
  2. explosión combinatoria. Cuando se utiliza un icono en muchos otros componentes, hay muchas clases de contenedor para el mismo componente de icono.
  3. no idiomático. Jetpack Compose usa mucho las API de tragamonedas y los desarrolladores están acostumbrados. Este enfoque se desvía de la convención y confunde a los desarrolladores.
  4. área de recomposición. Cuando icon.tint cambia, desencadena una recomposición de toda la TopAppBar, lo que no es muy eficiente, especialmente cuando se usan animaciones (por ejemplo, animate Tint).

Compose Layout Subsystem tiene una cosa llamada diseñoId — un parámetro que puede tener cada LayoutNode (implementado con ParentDataModifier). Primero se configura con un Modifier.layoutId, luego se lee en una etapa de diseño (medición). Aplicando este conocimiento a nuestro problema, primero usamos Modifier.layoutId dentro de un icono como este: @Composablefun Icon(pintor: Painter, tint: Color = DefaultTintColor) {Box(Modifier.layoutId(IconLayoutId))) {Icon(pintor = pintor ,tint = tint,contentDescription = null)}}objeto privado IconLayoutIdEntonces crea una función componible RequireLayoutId:@Composablefun RequireLayoutId(layoutId: Any?,errorMessage: String = «Error de requisitos».,content: @Composable () -> Unit ,) = Layout(contenido) { mensurables, restricciones ->val child = mensurables .singleOrNull()?: error(«Solo se permite un solo hijo, era: ${mensurables.size}»)// Leer el LayoutIdrequire de un solo hijo (child .layoutId == layoutId) { errorMessage } // en realidad no mide ni diseña un diseño secundario (0, 0) { } } Esta función es un diseño personalizado que en realidad no mide ni diseña ningún elemento secundario, simplemente lo comprobará si un solo niño permitido tiene un LayoutId requerido. Finalmente usamos la función como esta: @Composable fun TopAppBar(title: String,icon: @Composable () -> Unit,) {RequireLayoutId (layoutId = IconLayoutId,errorMessage = «Solo se permite icono»,content = icon)// later in codeicon()}Aquí hay algunos casos de prueba: @Preview@Composablefun TestCases() = Column {// ✅TopAppBar(title = » Lorem») {Icon(painter = RememberVectorPainter(Icons.Default.Add))}// ❌ TopAppBar(título = «Lorem») {Button(onClick = {})}// ❌TopAppBar(título = «Lorem») { }// ❌TopAppBar(título = «Lorem») {Box {Icon(pintor = RememberVectorPainter (Icons.Default.Add))}}@Composablefun IconWrapper() { // Puede usar cualquier función componible que no emita un UIremember { «Algo» }LaunchedEffect(Unit) { delay(200) }Icon(painter = RememberVectorPainter(Icons .Default.Add))} // ✅TopAppBar(title=»Lorem») {IconWrapper()}} Si desea un control aún más granular sobre qué íconos se pueden pasar a TopAppBar, puede usar un Wrappe componible r que permite solo algunos específicos subconjunto de todas las configuraciones de iconos posibles: Interfaz TopAppBarScope {@Composablefun TopAppBarIcon(Painter: Painter) {Box(Modifier.layoutId(TopAppBarIconLayoutId)) {Icon(Painter = Painter, Tint = TopAppBarTint)}}Objeto complementario {instancia de valor privado = objeto: TopAppBarScope {}Operador interno Fun Invoke() = instancia}}Objeto privado TopAppBarIconLayoutId@Composablefun TopAppBar(título: String,icono: @Composable TopAppBarScope.() -> Unidad,) {// …RequireLayoutId(layoutId = TopAppBarIconLayoutId,errorMessage = «Solo se permite TopAppBarIcon») {TopAppBarScope().icon()}TopAppBarScope().icon()// …}Uso:@Preview@Composablefun TestCases() = Columna {// ✅TopAppBar(title = » Lorem «) {TopAppBarIcon(painter = RememberVectorPainter(Icons.Default.Add))}}A través de TopAppBarScope incluso obtenemos un buen autocompletado:Menú desplegable Autocompletar en TopAppBarScopePor supuesto, este enfoque se puede ampliar fácilmente para aceptar un Están definidos Número de diferentes componentes de la interfaz de usuario. Eso es todo por hoy, espero que te sirva! Siéntase libre de dejar un comentario si algo no está claro o si tiene alguna pregunta. ¡Gracias por leer!

Deja una respuesta

Tu dirección de correo electrónico no será publicada.