7 widgets de Flutter que toda aplicación debería contener
- Andrés Cruz
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ículo original:
https://medium.com/@ahmad.hamoush.785/8-flutter-widgets-every-app-should-contain-5c6129a6928b
Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter