Cómo integrar Compose con tu IU existente

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

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:

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( /* ... */)
        }
    }
}

Animación que muestra un elemento de IU que se desplaza desde arriba hacia abajo a fin de dejar lugar para un teclado.

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 de isEnabled. No necesita saber si ControlPanelWithToggle existe o, incluso, cómo se puede controlar.

  • ControlPanelWithToggle no sabe que ImageWithEnabledOverlay existe. Puede haber cero, una o más formas en que se muestra isEnabled y ControlPanelWithToggle no tendría que cambiar.

  • Al elemento superior, no le importa la profundidad de las anidaciones de ImageWithEnabledOverlay o ControlPanelWithToggle. 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.