17 widgets de Flutter que son útiles

- Andrés Cruz

17 widgets de Flutter que son útiles

Tratar de aprender un nuevo idioma puede ser aterrador y tedioso. Muchas veces, desearíamos saber ciertas características que existían antes. En el artículo de hoy, les hablaré sobre los widgets de Flutter más prácticos que desearía haber conocido antes.

Spacer

Spacer crea un espacio vacío ajustable que ocupa cualquier espacio restante entre los widgets en un contenedor Flex, como una fila o una columna.

Row(
        children: const <Widget>[
          Text('Begin'),
          Spacer(), // Defaults to a flex of one.
          Text('Middle'),
          // Gives twice the space between Middle and End than Begin and Middle.
          Spacer(flex: 2),
          Text('End'),
        ],
      ),

TextButton.icon

Este widget reemplaza la necesidad de usar una fila al crear un botón con un icono. Debe proporcionar un icono y una etiqueta.

TextButton.icon(
              onPressed: () {}, 
              icon: Icon(Icons.home), 
              label: Text('Home')
              ),

Wrap

Muestra sus elementos secundarios horizontal o verticalmente según el valor de dirección proporcionado. Se puede usar en lugar de Gridview. Este widget responde y se adapta a diferentes tamaños de pantalla sin hacer mucho.

Wrap(
                direction: Axis.horizontal,
                alignment: WrapAlignment.start,
                spacing: 2.0,
                runSpacing: 3.0,
                children: [],
              )

AnimatedSwitcher

Este widget anima un nuevo widget en lugar de otro. Proporciona una buena transición que hace que la aplicación sea realmente fluida. Siempre agregue una clave a su widget secundario para asegurarse de que funcione correctamente.

AnimatedSwitcher(
            child: Text(
              '$_count',
              // This key causes the AnimatedSwitcher to interpret this as a "new"
              // child each time the count changes, so that it will begin its animation
              // when the count changes.
              key: ValueKey<int>(_count),
            ),
            duration: Duration(microseconds: 200),
            transitionBuilder: (Widget child, Animation<double> animation) {
              return ScaleTransition(scale: animation, child: child);
            },
          )

SafeArea

Este widget agrega relleno a sus widgets, lo que garantiza que su aplicación no entre en conflicto con el sistema operativo y las funciones de visualización del dispositivo, como la barra de estado.

SafeArea(child: Container())

RefreshIndicator

Toma widgets desplazables como un niño. Cuando el child se desplaza demasiado, un indicador de progreso circular animado se desvanece a la vista y llama a un Future para actualizar el contenido del scrollable.

RefreshIndicator(
child: ListView(), 
onRefresh: () async {}),

RichText

Esto nos permite mostrar texto con diferentes estilos en la misma oración o párrafo. Puede incluir enlaces en línea, texto subrayado, texto en color y mucho más.

RichText(
  text: TextSpan(
    text: 'Hello ',
    style: DefaultTextStyle.of(context).style,
    children: const <TextSpan>[
      TextSpan(text: 'bold', style: TextStyle(fontWeight: FontWeight.bold)),
      TextSpan(text: ' world!'),
    ],
  ),
)

Transform

Este widget lleva tu juego de animación a un nivel completamente nuevo. Puede implementar animaciones simples como rotar y escalar a otras más complejas como animaciones 3D y sesgadas. Proporciona constructores con nombres útiles como rotación, escala y traducción para una implementación rápida.

Container(
  color: Colors.black,
  child: Transform(
    alignment: Alignment.topRight,
    transform: Matrix4.skewY(0.3)..rotateZ(-math.pi / 12.0),
    child: Container(
      padding: const EdgeInsets.all(8.0),
      color: const Color(0xFFE8581C),
      child: const Text('Hello there!'),
    ),
  ),
)

InteractiveViewer

La forma más fácil de introducir funciones de zoom, panorámica, arrastre y pellizcos en un widget. Es altamente personalizable según sus necesidades.

InteractiveViewer(
        boundaryMargin: const EdgeInsets.all(20.0),
        minScale: 0.1,
        maxScale: 1.6,
        child: Container(
          decoration: const BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: <Color>[Colors.orange, Colors.red],
              stops: <double>[0.0, 1.0],
            ),
          ),
        ),
      ),

Flow

Este widget aprovecha el poder de las transformaciones para ofrecer animaciones geniales. Es uno de esos widgets que debes ver en acción para comprender su funcionamiento. Consulte la documentación oficial para obtener más información.

https://api.flutter.dev/flutter/widgets/Flow-class.html

Chip

Es un widget simple que muestra datos simples de manera organizada y hermosa. Tiene varias variantes, como InputChip, ChoiceChip, FilterChip y ActionChip.

Chip(   
avatar: CircleAvatar(     
backgroundColor: Colors.grey.shade800,     
child: const Text('AB'),   ),   
label: const Text('Aaron Burr'), 
)

End Drawer

EndDrawer se usa normalmente para proporcionar navegación a otras páginas, pero también podemos usarlo como pantalla de filtro.

class EndDrawerScreen extends StatefulWidget {
  const EndDrawerScreen({Key? key}) : super(key: key);

  @override
  _EndDrawerScreenState createState() => _EndDrawerScreenState();
}

class _EndDrawerScreenState extends State<EndDrawerScreen> {
  late ThemeData theme;

  GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: scaffoldKey,
      appBar: AppBar(
        title: const Text("End Drawer"),
      ),
      endDrawer: endDrawer(),
      resizeToAvoidBottomInset: false,
      body: const Center(child: Text("End Drawer Screen")),
    );
  }

  Widget endDrawer() {
    return SafeArea(
      child: Container(
        margin: const EdgeInsets.all(16),
        width: 300,
        decoration: const BoxDecoration(
          borderRadius: BorderRadius.all(Radius.circular(8)),
          color: Colors.green,
        ),
        child: Drawer(
          child:
              Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
            Container(
              padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
              color: Colors.lightBlue,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Center(
                    child: Text(
                      "Filter",
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 20,
                      ),
                    ),
                  ),
                  IconButton(
                      onPressed: () {
                        Navigator.pop(context);
                      },
                      icon: const Icon(
                        Icons.cancel,
                        color: Colors.white,
                      ))
                ],
              ),
            ),
            Container(
              padding: EdgeInsets.all(8),
              child: const Text(
                "Select your category",
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
              ),
            ),
            Container(
              padding: EdgeInsets.all(8),
              child: Wrap(
                spacing: 4,
                runSpacing: 4,
                children: categories(),
              ),
            ),
          ]),
        ),
      ),
    );
  }

  List<Widget> categories() {
    List<String> typesList =
        List<String>.generate(10, (index) => "Category $index");

    List<Widget> categories = [];
    for (var item in typesList) {
      categories.add(Container(
        decoration: BoxDecoration(
          borderRadius: const BorderRadius.all(Radius.circular(12)),
          color: Colors.grey.shade300,
        ),
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
        child: Text(
          item,
        ),
      ));
    }
    return categories;
  }
}

Loading Button

Prefiero usar un botón de carga animado como este en lugar de CircularProgressIndicator porque muestro un estado de error o éxito y hago que el usuario no pueda presionar el botón nuevamente hasta que solucione el error.

class CustomLoadingButton extends StatelessWidget {
 final RoundedLoadingButtonController controller;
 final VoidCallback? onPressed;
 final EdgeInsetsGeometry? padding;
 final double? elevation;
 final double borderRadiusAll;
 final Color? backgroundColor;
 final Color? successColor;
 final Color? errorColor;
 final Color? splashColor;
 final Widget child;
 final double width;
 final double height;
 const CustomLoadingButton(
     {Key? key,
     required this.controller,
     this.onPressed,
     required this.child,
     this.padding,
     this.borderRadiusAll = 0,
     this.backgroundColor = Colors.green,
     this.successColor = Colors.green,
     this.errorColor = Colors.red,
     this.elevation = 4,
     this.splashColor,
     this.height = 35,
     this.width = 300})
     : super(key: key);
 @override
 Widget build(BuildContext context) {
   return RoundedLoadingButton(
     color: backgroundColor,
     successColor: successColor,
     errorColor: errorColor,
     controller: controller,
     onPressed: onPressed,
     valueColor: Colors.white,
     borderRadius: borderRadiusAll,
     child: child,
     height: height,
     width: width,
   );
 }
}
RoundedLoadingButtonController btnController = RoundedLoadingButtonController();
TextEditingController textEditingController = TextEditingController();
final formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
 return Container(
   padding: EdgeInsets.all(16),
   child: Form(
     key: formKey,
     child: Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
         TextFormField(
           controller: textEditingController,
           decoration: InputDecoration(
             labelText: "Add your number",
           ),
           validator: (value) {
             if (value != null && value.isNotEmpty) {
               if (RegExp(r'^[a-z]+$').hasMatch(value)) {
                 return "Just numbers allowed";
               }
               return null;
             } else {
               return "Please enter your number";
             }
           },
         ),
         SizedBox(
           height: 16,
         ),
         Center(
           child: RLoadingButton(
             controller: btnController,
             onPressed: () {
               Future.delayed(Duration(seconds: 1)).then((value) {
                 if (formKey.currentState!.validate()) {
                   btnController.success();
                 } else {
                   btnController.error();
                 }
               });
             },
             backgroundColor: Colors.blue,
             child: Text("Add"),
           ),
         )
       ],
     ),
     onChanged: () {
       if (btnController.currentState == ButtonState.success || btnController.currentState == ButtonState.error) {
         btnController.reset();
       }
       formKey.currentState!.validate();
     },
   ),
 );
}

Stepper

Stepper es un widget de material que muestra el progreso a través de una secuencia de pasos. Los pasos son particularmente útiles en el caso de formularios en los que un paso requiere completar otro, o donde es necesario completar varios pasos para enviar el formulario completo.

Puedes usarlo en dirección vertical y horizontal.

Modal Bottom Sheet

Un Modal Bottom Sheet es una alternativa a un menú o cuadro de diálogo y evita que el usuario interactúe con el resto de la aplicación.

Es como un cuadro de diálogo que se abre desde abajo y podemos usarlo para crear espacio para más contenido en nuestras aplicaciones.

class BottomSheetScreen extends StatefulWidget {
  const BottomSheetScreen({Key? key}) : super(key: key);

  @override
  State<BottomSheetScreen> createState() => _BottomSheetScreenState();
}

class _BottomSheetScreenState extends State<BottomSheetScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'Modal Bottom Sheet',
        ),
      ),
      body: Container(
        alignment: Alignment.center,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              "MODAL BOTTOM SHEET",
            ),
            const SizedBox(
              height: 20,
            ),
            ElevatedButton(
              onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: (context) {
                      return Column(
                        mainAxisSize: MainAxisSize.min,
                        children: <Widget>[
                          ListTile(
                            leading: const Icon(Icons.copy),
                            title: const Text('Copy'),
                            onTap: () {
                              Navigator.pop(context);
                            },
                          ),
                          ListTile(
                            leading: const Icon(Icons.save),
                            title: const Text('Save'),
                            onTap: () {
                              Navigator.pop(context);
                            },
                          ),
                          ListTile(
                            leading: const Icon(Icons.share),
                            title: const Text('Share'),
                            onTap: () {
                              Navigator.pop(context);
                            },
                          ),
                        ],
                      );
                    });
              },
              child: const Text(
                'Click Me',
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Tooltip

Los Tooltips proporcionan una etiqueta de texto que identifica el elemento, como una descripción de su función.

Los Tooltips sobre herramientas mejoran la accesibilidad de los widgets visuales al proporcionar una representación textual del widget, que, por ejemplo, puede ser vocalizada por un lector de pantalla.

Los Tooltips sobre herramientas se utilizan para mostrar una etiqueta de texto simple y no se recomienda mostrar información e imágenes ricas en ella.

Tooltip(
 message: "Add new item",
 child: Icon(Icons.add_circle_rounded, size: 32, color: Colors.blue,),
),

Slide Action

Puede utilizar Slide Action para hacer que el usuario confirme o realice una acción.

Rich Text
Este widget muestra texto que utiliza varios estilos diferentes.
RichText(
 text: const TextSpan(
   text: 'Don\'t have an account yet? ',
   style: TextStyle(
     color: Colors.black,
     fontSize: 18,
   ),
   children: [
     TextSpan(
       text: 'Sign',
       style: TextStyle(
         color: Colors.blue,
         fontSize: 18,
         decoration: TextDecoration.underline,
         fontWeight: FontWeight.w800,
       ),
     ),
     TextSpan(
       text: ' ',
     ),
     TextSpan(
       text: 'Up',
       style: TextStyle(
         color: Colors.red,
         fontSize: 18,
         decoration: TextDecoration.underline,
         fontWeight: FontWeight.w800,
       ),
     ),
   ],
 ),
)

Artículos:

https://betterprogramming.pub/10-flutter-widgets-that-come-in-handy-decae69a0f26

https://medium.com/@ahmad.hamoush.785/8-flutter-widgets-every-app-should-contain-5c6129a6928b

Conoceremos unos muy utiles widgets en Flutter que debes de conocer si o si para crear tus aplicaciones.

Acepto recibir anuncios de interes sobre este Blog.

Andrés Cruz