Migrar a Material 3
Aprende cómo migrar la UI de tu aplicación de Flutter de Material 2 a Material 3.
Resumen
#La biblioteca Material se ha actualizado para coincidir con la especificación de diseño de Material 3. Los cambios incluyen nuevos componentes y temas de componentes, visuales de componentes actualizados y mucho más. Muchas de estas actualizaciones son transparentes. Verás la nueva versión de un widget afectado al volver a compilar tu aplicación con la versión 3.16 (o posterior). Pero también se requiere algo de trabajo manual para completar la migración.
Guía de migración
#Antes del lanzamiento de la versión 3.16, podías optar por los cambios de Material 3
estableciendo la bandera useMaterial3 como true. A partir del lanzamiento de Flutter 3.16
(noviembre de 2023), useMaterial3 es true por defecto.
Por cierto, puedes revertir al comportamiento de Material 2 en tu aplicación estableciendo
useMaterial3 en false. Sin embargo, esto es solo una solución temporal. La
bandera useMaterial3 y la implementación de Material 2 eventualmente se
eliminarán como parte de la política de obsolescencia de Flutter.
Colores
#Los valores predeterminados para ThemeData.colorScheme están actualizados para coincidir
con la especificación de diseño de Material 3.
El constructor ColorScheme.fromSeed genera un ColorScheme
derivado del seedColor proporcionado. Los colores generados por este
constructor están diseñados para funcionar bien juntos y cumplir con los requisitos de contraste
para la accesibilidad en el sistema de diseño de Material 3.
Al actualizar a la versión 3.16, tu interfaz de usuario podría verse un poco extraña
sin el ColorScheme correcto. Para solucionar esto, migra al
ColorScheme generado a partir del constructor ColorScheme.fromSeed.
Código antes de la migración:
theme: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.blue),
),
Código después de la migración:
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
Para generar un esquema de colores dinámico basado en contenido, utiliza el
método estático ColorScheme.fromImageProvider. Para ver un ejemplo de generación de un
esquema de colores, consulta la muestra de ColorScheme a partir de una imagen de red.
Los cambios en Flutter Material 3 incluyen un nuevo color de fondo.
ColorScheme.surfaceTint indica un widget elevado.
Algunos widgets utilizan colores diferentes.
Para devolver la UI de tu aplicación a su comportamiento anterior (lo cual no recomendamos):
- Establece
Colors.grey[50]!enColorScheme.background(cuando el tema esBrightness.light). - Establece
Colors.grey[850]!enColorScheme.background(cuando el tema esBrightness.dark).
Código antes de la migración:
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
Código después de la migración:
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple).copyWith(
background: Colors.grey[50]!,
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
).copyWith(background: Colors.grey[850]!),
),
El valor de ColorScheme.surfaceTint indica la elevación de un componente en
Material 3. Algunos widgets pueden usar tanto surfaceTint como shadowColor
para
indicar elevación (por ejemplo, Card y ElevatedButton) y otros pueden
usar solo surfaceTint para indicar la elevación (como AppBar).
Para volver al comportamiento anterior del widget, establece Colors.transparent
en ColorScheme.surfaceTint en el tema. Para diferenciar la sombra de un widget
del contenido (cuando no tiene sombra), establece el color ColorScheme.shadow en la
propiedad shadowColor en el tema del widget sin un color de sombra predeterminado.
Código antes de la migración:
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
Código después de la migración:
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple).copyWith(
surfaceTint: Colors.transparent,
),
appBarTheme: AppBarTheme(
elevation: 4.0,
shadowColor: Theme.of(context).colorScheme.shadow,
),
),
El ElevatedButton ahora se estiliza con una nueva combinación de colores.
Anteriormente, cuando el flag useMaterial3 estaba configurado en false, ElevatedButton
se estilizaba con ColorScheme.primary para el fondo y
ColorScheme.onPrimary para el primer plano. Para lograr los mismos visuales, cambia
al nuevo Widget FilledButton sin los cambios de elevación o sombra paralela.
Código antes de la migración:
ElevatedButton(
onPressed: () {},
child: const Text('Button'),
),
Código después de la migración:
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () {},
child: const Text('Button'),
),
Tipografía
#Los valores predeterminados para ThemeData.textTheme se han actualizado para coincidir con los
valores predeterminados de Material 3. Los cambios incluyen el tamaño de fuente, grosor de fuente,
espaciado entre letras y altura de línea actualizados. Para más detalles, consulta la
documentación de TextTheme.
Como se muestra en el siguiente ejemplo, antes de la versión 3.16, un widget Text
con una cadena larga utilizando TextTheme.bodyLarge en un diseño restringido
ajustaba el texto en dos líneas. Sin embargo, la versión 3.16 ajusta el texto en
tres líneas. Si necesitas obtener el comportamiento anterior, ajusta el estilo de texto
y, si es necesario, el espaciado de las letras.
Código antes de la migración:
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
'This is a very long text that should wrap to multiple lines.',
style: Theme.of(context).textTheme.bodyLarge,
),
),
Código después de la migración:
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
'This is a very long text that should wrap to multiple lines.',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
letterSpacing: 0.0,
),
),
),
Componentes
#Algunos componentes no pudieron simplemente actualizarse para coincidir con la especificación de diseño de Material 3, sino que necesitaron una implementación completamente nueva. Estos componentes requieren una migración manual, ya que el SDK de Flutter no sabe qué quieres exactamente.
Reemplaza el widget BottomNavigationBar del estilo de Material 2
con el nuevo widget
NavigationBar. Es ligeramente más alto, contiene indicadores de navegación
en forma de píldora y utiliza nuevos mapeos de colores.
Código antes de la migración:
BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
),
Código después de la migración:
NavigationBar(
destinations: const <Widget>[
NavigationDestination(
icon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.business),
label: 'Business',
),
NavigationDestination(
icon: Icon(Icons.school),
label: 'School',
),
],
),
Consulta la muestra completa sobre la
migración de BottomNavigationBar a NavigationBar.
Reemplaza el widget Drawer
con NavigationDrawer, el cual proporciona
indicadores de navegación en forma de píldora, esquinas redondeadas y nuevos mapeos de colores.
Código antes de la migración:
Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Text(
'Drawer Header',
style: Theme.of(context).textTheme.titleLarge,
),
),
ListTile(
leading: const Icon(Icons.message),
title: const Text('Messages'),
onTap: () { },
),
ListTile(
leading: const Icon(Icons.account_circle),
title: const Text('Profile'),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () { },
),
],
),
),
Código después de la migración:
NavigationDrawer(
children: <Widget>[
DrawerHeader(
child: Text(
'Drawer Header',
style: Theme.of(context).textTheme.titleLarge,
),
),
const NavigationDrawerDestination(
icon: Icon(Icons.message),
label: Text('Messages'),
),
const NavigationDrawerDestination(
icon: Icon(Icons.account_circle),
label: Text('Profile'),
),
const NavigationDrawerDestination(
icon: Icon(Icons.settings),
label: Text('Settings'),
),
],
),
Consulta la muestra completa sobre la
migración de Drawer a NavigationDrawer.
Material 3 introduce barras de aplicaciones medianas y grandes que muestran un título más grande
antes de hacer scroll. En lugar de una sombra paralela, se utiliza el color ColorScheme.surfaceTint
para crear una separación del contenido al hacer scroll.
El siguiente código demuestra cómo implementar la barra de aplicaciones mediana:
CustomScrollView(
slivers: <Widget>[
const SliverAppBar.medium(
title: Text('Title'),
),
SliverToBoxAdapter(
child: Card(
child: SizedBox(
height: 1200,
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 100, 8, 100),
child: Text(
'Here be scrolling content...',
style: Theme.of(context).textTheme.headlineSmall,
),
),
),
),
),
],
),
Ahora hay dos tipos de widgets TabBar: primarios y secundarios.
Las pestañas secundarias se utilizan dentro de un área de contenido para separar aún más el contenido
relacionado y establecer una jerarquía. Consulta el ejemplo de TabBar.secondary.
La nueva propiedad TabBar.tabAlignment
especifica la alineación horizontal
de las pestañas.
La siguiente muestra enseña cómo modificar la alineación de pestañas en una TabBar con scroll:
AppBar(
title: const Text('Title'),
bottom: const TabBar(
tabAlignment: TabAlignment.start,
isScrollable: true,
tabs: <Widget>[
Tab(
icon: Icon(Icons.cloud_outlined),
),
Tab(
icon: Icon(Icons.beach_access_sharp),
),
Tab(
icon: Icon(Icons.brightness_5_sharp),
),
],
),
),
SegmentedButton, una versión actualizada de
ToggleButtons,
utiliza esquinas completamente redondeadas, difiere en altura y
tamaño de diseño, y utiliza un Set de Dart para determinar los elementos seleccionados.
Código antes de la migración:
enum Weather { cloudy, rainy, sunny }
ToggleButtons(
isSelected: const [false, true, false],
onPressed: (int newSelection) { },
children: const <Widget>[
Icon(Icons.cloud_outlined),
Icon(Icons.beach_access_sharp),
Icon(Icons.brightness_5_sharp),
],
),
Código después de la migración:
enum Weather { cloudy, rainy, sunny }
SegmentedButton<Weather>(
selected: const <Weather>{Weather.rainy},
onSelectionChanged: (Set<Weather> newSelection) { },
segments: const <ButtonSegment<Weather>>[
ButtonSegment(
icon: Icon(Icons.cloud_outlined),
value: Weather.cloudy,
),
ButtonSegment(
icon: Icon(Icons.beach_access_sharp),
value: Weather.rainy,
),
ButtonSegment(
icon: Icon(Icons.brightness_5_sharp),
value: Weather.sunny,
),
],
),
Consulta la muestra completa sobre la
migración de ToggleButtons a SegmentedButton.
Nuevos componentes
#- "Las barras de menú y menús en cascada" proporcionan un sistema de menú estilo escritorio que es
totalmente navegable con el mouse o teclado. Los menús están anclados por
un
MenuBaro unMenuAnchor. El nuevo sistema de menú no es algo a lo que las aplicaciones existentes deban migrar obligatoriamente, sin embargo las aplicaciones que están desplegadas en la web o en plataformas de escritorio deberían considerar usarlo en lugar de las clasesPopupMenuButton(y relacionadas). DropdownMenucombina un campo de texto y un menú para producir lo que a veces se llama un combo box. Los usuarios pueden seleccionar un elemento de menú de una lista potencialmente grande ingresando una cadena coincidente o interactuando con el menú mediante pantalla táctil, ratón o teclado. Esto puede ser un buen reemplazo para el widgetDropdownButton, aunque no es obligatorio.SearchBarySearchAnchorson para interacciones en las que el usuario introduce una consulta de búsqueda, la aplicación calcula una lista de respuestas coincidentes y luego el usuario selecciona una o ajusta la consulta.Badgedecora su hijo con una etiqueta pequeña de solo unos pocos caracteres, como '+1'. Las insignias se utilizan normalmente para decorar el icono dentro de unaNavigationDestination, unaNavigationRailDestination, unaNavigationDrawerDestinationo el icono de un botón, como enTextButton.icon.FilledButtonyFilledButton.tonalson muy similares a unElevatedButtonsin los cambios de elevación y sombra paralela.FilterChip.elevated,ChoiceChip.elevatedyActionChip.elevatedson variantes elevadas de los mismos chips con una sombra paralela y un color de relleno.Dialog.fullscreenllena toda la pantalla y típicamente contiene un título, un botón de acción y un botón de cierre en la parte superior.
Timeline
#En versión estable: 3.16
Referencias
#Documentación:
Documentación de la API:
Issues relevantes:
PRs relevantes:
A menos que se indique lo contrario, la documentación de este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-05. Ver código fuente oreportar un problema.