Si tienes una app con una IU basada en objetos View, es posible que no quieras volver a escribir toda su IU de una sola vez. Esta página te ayudará a agregar elementos de Compose nuevos a tu IU existente.
Cómo migrar la IU compartida
Si migras gradualmente a Compose, es posible que necesites usar elementos compartidos de la IU en el sistema de Compose y de View. Por ejemplo, si tu app tiene un componente CallToActionButton
personalizado, es posible que debas usarlo en pantallas basadas en Compose y en View.
En Compose, los elementos compartidos de la IU se convierten en elementos que admiten composición y que se pueden volver a usar en la app, independientemente de que el elemento al que se le aplica el estilo utilice XML o sea una vista personalizada. Por ejemplo, deberías crear un elemento CallToActionButton
que admita composición para tu componente Button
de llamada a la acción personalizada.
Para usar el elemento que admite composición en las pantallas basadas en View, debes crear un wrapper de vista personalizado que se extienda desde AbstractComposeView
. En su elemento Content
anulado que admite composición, coloca el elemento que creaste unido al tema de Compose como se muestra en el siguiente ejemplo:
@Composable fun CallToActionButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Button( colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondary ), onClick = onClick, modifier = modifier, ) { Text(text) } } class CallToActionViewButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var text by mutableStateOf("") var onClick by mutableStateOf({}) @Composable override fun Content() { YourAppTheme { CallToActionButton(text, onClick) } } }
Ten en cuenta que los parámetros que admiten composición se convierten en variables mutables dentro de la vista personalizada. Eso hace que la vista CallToActionViewButton
personalizada aumente y se pueda usar (por ejemplo, con la vinculación de vistas), como una vista tradicional. Consulta el siguiente ejemplo:
class ViewBindingActivity : ComponentActivity() { private lateinit var binding: ActivityExampleBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) binding.callToAction.apply { text = getString(R.string.greeting) onClick = { /* Do something */ } } } }
Si el componente personalizado contiene un estado mutable, consulta la fuente de información de estado.
Cómo migrar el tema de tu app
Material Design es el sistema de diseño recomendado para aplicar temas a las apps para Android.
Para las apps basadas en View, hay tres versiones de Material disponibles:
- Material Design 1 con la biblioteca AppCompat (es decir,
Theme.AppCompat.*
) - Material Design 2 con la biblioteca MDC-Android (es decir,
Theme.MaterialComponents.*
) - Material Design 3 con la biblioteca MDC-Android (es decir,
Theme.Material3.*
)
Para las apps de Compose, hay dos versiones de Material disponibles:
- Material Design 2 con la biblioteca de Compose Material (es decir,
androidx.compose.material.MaterialTheme
) - Material Design 3 con la biblioteca de Compose Material 3 (es decir,
androidx.compose.material3.MaterialTheme
)
Te recomendamos usar la versión más reciente, Material 3, si el sistema de diseño de tu app está en posición de hacerlo. Hay guías de migración disponibles para View y Compose:
- Material 1 a Material 2 en Views
- Material 2 a Material 3 en Views
- Material 2 a Material 3 en Compose
Cuando crees pantallas nuevas en Compose, independientemente de la versión de Material Design que uses, asegúrate de aplicar un MaterialTheme
antes de cualquier elemento componible que emita IU desde las bibliotecas de Material Compose. Los componentes de Material (Button
, Text
, etc.) dependen de que se implemente un MaterialTheme
, y su comportamiento no está definido sin él.
Todos los ejemplos de Jetpack Compose usan un tema de Compose personalizado creado sobre la base de MaterialTheme
.
Consulta Cómo diseñar sistemas en Compose y Cómo migrar temas de XML a Compose para obtener más información.
Animaciones de WindowInsets e IME
A partir de Compose 1.2.0, puedes controlar WindowInsets
con modificadores para hacerlo dentro de tus diseños. También se admiten las animaciones IME.
class ExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
MaterialTheme {
MyScreen()
}
}
}
}
@Composable
fun MyScreen() {
Box {
LazyColumn(
modifier = Modifier
.fillMaxSize() // fill the entire window
.imePadding() // padding for the bottom for the IME
.imeNestedScroll(), // scroll IME at the bottom
content = { }
)
FloatingActionButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp) // normal 16dp of padding for FABs
.navigationBarsPadding() // padding for navigation bar
.imePadding(), // padding for when IME appears
onClick = { }
) {
Icon( /* ... */)
}
}
}
Figura 2: Animaciones IME
Prioriza la división del estado sobre la presentación
Tradicionalmente, un View
es un elemento con estado. Un View
administra los campos que describen qué mostrar, además de cómo mostrarlo. Cuando conviertas un elemento View
en Compose, separa los datos que se procesarán para lograr un flujo de datos unidireccional, tal y como se explica con más detalle en el documento de elevación de estado.
Por ejemplo, un elemento View
tiene una propiedad visibility
que describe si es visible, invisible o no está presente. Esta es una propiedad inherente de View
. Si bien otros fragmentos de código pueden cambiar la visibilidad de un elemento View
, solo View
sabe su visibilidad actual. La lógica para garantizar que un View
sea visible puede ser propensa a errores y, a menudo, está vinculada al propio elemento View
.
Por el contrario, Compose facilita la visualización de elementos componibles completamente diferentes cuando se usa la lógica condicional en Kotlin que se muestra a continuación:
if (showCautionIcon) {
CautionIcon(/* ... */)
}
Por defecto, CautionIcon
no necesita ni le interesa saber por qué se muestra, y no hay un concepto de visibility
dado que este puede estar presente en la composición como no.
Si separas de forma clara la administración del estado y la lógica de presentación, puedes cambiar con mayor libertad la manera en la que muestras contenido como una conversión de estado en una IU. Poder elevar el estado cuando sea necesario también hace que los elementos componibles sean más reutilizables, ya que la propiedad del estado es más flexible.
Promueve componentes encapsulados y reutilizables
Los elementos View
a menudo saben dónde se encuentran: dentro de una Activity
, un Dialog
, un Fragment
o algún lugar dentro de otra jerarquía View
. Debido a que suelen aumentarse a partir de archivos de diseño estáticos, la estructura general de un elemento View
suele ser muy rígida. Como resultado, se produce un acoplamiento más alto y hace que sea más difícil cambiar o reutilizar un elemento View
.
Por ejemplo, un View
personalizado puede suponer que tiene un elemento View secundario de un tipo determinado, con un ID determinado, y cambiar sus propiedades directamente como respuesta a alguna acción. Esto acopla altamente los elementos View
, lo que aumenta las posibilidades de que el View
personalizado falle si no puede encontrar el elemento secundario, y es probable que el elemento secundario no se pueda volver a usar si el elemento View
superior no se personaliza.
Cuando se usan elementos componibles, las probabilidades de que esto ocurra son menores. Los elementos superiores pueden especificar con facilidad el estado y las devoluciones de llamada, de modo que se puedan escribir elementos componibles reutilizables sin tener que saber exactamente la ubicación en la se usarán.
var isEnabled by rememberSaveable { mutableStateOf(false) }
Column {
ImageWithEnabledOverlay(isEnabled)
ControlPanelWithToggle(
isEnabled = isEnabled,
onEnabledChanged = { isEnabled = it }
)
}
En el ejemplo anterior, todas las tres partes están más encapsuladas y menos acopladas:
ImageWithEnabledOverlay
solo necesita saber cuál es el estado actual deisEnabled
. No necesita saber siControlPanelWithToggle
existe o, incluso, cómo se puede controlar.ControlPanelWithToggle
no sabe queImageWithEnabledOverlay
existe. Puede haber cero, una o más formas en que se muestraisEnabled
yControlPanelWithToggle
no tendría que cambiar.Al elemento superior, no le importa la profundidad de las anidaciones de
ImageWithEnabledOverlay
oControlPanelWithToggle
. Esos elementos secundarios podrían fomentar cambios, intercambiar contenido o transmitirlo a otros elementos secundarios.
Este patrón se conoce como la inversión de control. Si lo deseas, puedes obtener más información sobre este tema en la documentación de CompositionLocal
.
Cómo controlar cambios de tamaños de pantalla
Una de las formas principales de crear diseños responsivos de View
es tener diferentes recursos para diferentes tamaños de ventanas. Si bien los recursos calificados continúan siendo una opción para las decisiones de diseño de la pantalla, Compose facilita el cambio completo de los diseños en el código con una lógica condicional normal. Si quieres obtener más información, consulta el documento para brindar compatibilidad con diferentes tamaños de pantalla.
Además, consulta el documento sobre creación de diseños adaptativos para obtener información sobre las técnicas que ofrece Compose para compilar IUs adaptables.
Desplazamiento anidado con View
A fin de obtener más información para habilitar la interoperabilidad de desplazamiento anidada entre elementos de View desplazables y elementos componibles desplazables, anidados en ambas direcciones, lee el artículo sobre Interoperabilidad de desplazamiento anidada.
Compose en RecyclerView
Los elementos ranging channel en RecyclerView tienen un buen rendimiento desde la versión 1.3.0-alpha02 de RecyclerView. Asegúrate de usar al menos la versión 1.3.0-alpha02 de RecyclerView para ver esos beneficios.