Categorías: Programación

Cree una galería de productos de comercio electrónico intuitiva con Flutter

Crear una experiencia de usuario excepcional es esencial para el éxito de su aplicación. No importa qué tan bueno sea el producto que venda, si su aplicación no ofrece una UX positiva, tendrá dificultades. En este tutorial, aprenderá cómo crear una galería de productos intuitiva con Flutter y brindar una mejor experiencia de compra.

Crearemos una aplicación de muestra que enumera los productos de una manera atractiva: al tocar un producto, animaremos una página de detalles del producto donde puede establecer su cantidad y agregarlo al carrito. En la parte superior también verá la cantidad de artículos agregados al carrito, y cuando haga clic en él, podrá ver una lista de todos los productos que se agregaron al mismo.

Al final de este tutorial, aprenderá cómo crear pantallas más grandes, permitir la búsqueda de productos, alternar la vista, administrar el estado del carrito y agregar movimiento de material mientras navega entre pantallas para una experiencia de usuario fluida.

Así es como se verá nuestra galería de productos de comercio electrónico de Flutter cuando esté completa:

Esto es lo que cubriremos en este tutorial:

Creación del proyecto

Antes de comenzar, debe instalar Flutter SDK y crear un proyecto básico. Veamos cómo hacer esto.

Primero, obtén el SDK de Flutter desde este enlace. Una vez instalado, comprueba si todo está bien pulsando el siguiente comando en la terminal:

flutter doctor

(Nota: si algo está marcado con una marca roja, debe cuidarlo antes de continuar)

Ahora cree un directorio donde desee crear este proyecto e ingrese el siguiente comando. Esto creará un nuevo proyecto en el directorio especificado.

flutter create ecomm_app

Para abrir, modificar el contenido del archivo y ejecutar/depurar/probar el proyecto, necesita instalar el IDE. Puede elegir uno de los siguientes editores:

Abre el carpetadelproyecto/lib/main.dart y ejecute la aplicación de arranque desde el IDE presionando el botón de reproducción. También puede ejecutarlo desde la línea de comando ingresando el siguiente comando:

flutter run
// Tip: If you are running multiple devices, you can run the 
// following:
flutter run -d "<device_name>"

Creación de pantallas

La aplicación de muestra consta de tres pantallas: la lista de productos, los detalles del producto y la página del carrito de compras.

Veamos cómo encajan estas pantallas en el flujo:

Primero, verás todos los productos. Toque cualquier elemento para abrir la página de detalles. Puede agregar artículos al carrito de compras e ir a la página del carrito de compras.

Listas de productos

La primera pantalla muestra todos los productos con su nombre, imagen y precio. Aquí puede buscar productos y cambiar la vista entre vista de cuadrícula y vista de lista. Al tocar el artículo, se abre la página de detalles del producto.

Más artículos interesantes de LogRocket:

Estos son algunos de los widgets esenciales que puede usar para crear la primera pantalla:

Aquí está el código exacto para crear este contenedor:

Container(
  //width: MediaQuery.of(context).size.width * 0.45,
  decoration: BoxDecoration(
    color: AppTheme.of(context).secondaryBackground,
    boxShadow: [
      BoxShadow(
        blurRadius: 4,
        color: Color(0x3600000F),
        offset: Offset(0, 2),
      )
    ],
    borderRadius: BorderRadius.circular(8),
  ),
  child: Padding(
    padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 12),
    child: Column(
      mainAxisSize: MainAxisSize.max,
      children: [
        Row(
          mainAxisSize: MainAxisSize.max,
          children: [
            Expanded(
              child: ClipRRect(
                borderRadius: BorderRadius.only(
                  bottomLeft: Radius.circular(0),
                  bottomRight: Radius.circular(0),
                  topLeft: Radius.circular(8),
                  topRight: Radius.circular(8),
                ),
                child: Image.network(
                  product.image,
                  width: 100,
                  height: 100,
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ],
        ),
        Padding(
          padding: EdgeInsetsDirectional.fromSTEB(0, 4, 0, 0),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            children: [
              Padding(
                padding: EdgeInsetsDirectional.fromSTEB(8, 4, 0, 0),
                child: Text(
                  product.name,
                  style: AppTheme.of(context).bodyText1,
                ),
              ),
            ],
          ),
        ),
        Padding(
          padding: EdgeInsetsDirectional.fromSTEB(0, 2, 0, 0),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            children: [
              Padding(
                padding: EdgeInsetsDirectional.fromSTEB(8, 4, 0, 0),
                child: Text(
                  '\$${product.price}',
                  style: AppTheme.of(context).bodyText2,
                ),
              ),
            ],
          ),
        ),
      ],
    ),
  ),
);

Detalles de producto

La página de detalles del producto muestra información del producto. Le permite establecer la cantidad del producto y agregarlo al carrito. También puede abrir el carrito de compras desde esta página.

Estos son algunos de los widgets esenciales que puede usar para crear la pantalla de detalles del producto:

El widget de insignia no es el widget estándar; en cambio, se agrega desde una biblioteca llamada Insignias. Esto anima automáticamente la insignia cuando se actualiza el valor.

Aquí está el código que muestra la insignia sobre el artículo del carrito:

Badge(
  badgeContent: Text(
    '${cartItem.length}',
    style: AppTheme.of(context).bodyText1.override(
          fontFamily: 'Poppins',
          color: Colors.white,
        ),
  ),
  showBadge: true,
  shape: BadgeShape.circle,
  badgeColor: AppTheme.of(context).primaryColor,
  elevation: 4,
  padding: EdgeInsetsDirectional.fromSTEB(8, 8, 8, 8),
  position: BadgePosition.topEnd(),
  animationType: BadgeAnimationType.scale,
  toAnimate: true,
  child: IconButton(
    icon: Icon(
      Icons.shopping_cart_outlined,
      color: AppTheme.of(context).secondaryText,
      size: 30,
    ),
    onPressed: () {
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => CheckoutWidget(),
        ),
      );
    },
  ),
)

Carro

Esta página muestra la lista de todos los artículos agregados al carrito con la opción de eliminar cualquier artículo del carrito. Aquí puede ver todos los resúmenes de precios y una opción de pago.

Estos son algunos de los widgets importantes que puede usar para crear la pantalla del carrito de compras:

Aquí se explica cómo mostrar una lista de todos los artículos en un carrito:

ListView.builder(
    padding: EdgeInsets.zero,
    primary: false,
    shrinkWrap: true,
    scrollDirection: Axis.vertical,
    itemCount: cartItems.length,
    itemBuilder: (BuildContext context, int index) {
      return Padding(
        padding: EdgeInsetsDirectional.fromSTEB(16, 8, 16, 0),
        child: Container(
          width: double.infinity,
          height: 100,
          decoration: BoxDecoration(
            color: AppTheme.of(context).secondaryBackground,
            boxShadow: [
              BoxShadow(
                blurRadius: 4,
                color: Color(0x320E151B),
                offset: Offset(0, 1),
              )
            ],
            borderRadius: BorderRadius.circular(12),
          ),
          child: Padding(
            padding: EdgeInsetsDirectional.fromSTEB(16, 8, 8, 8),
            child: Row(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Hero(
                  tag: 'ControllerImage',
                  transitionOnUserGestures: true,
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(12),
                    child: Image.network(
                      cartItems[index].image,
                      width: 80,
                      height: 80,
                      fit: BoxFit.fitWidth,
                    ),
                  ),
                ),
                Padding(
                  padding: EdgeInsetsDirectional.fromSTEB(12, 0, 0, 0),
                  child: Column(
                    mainAxisSize: MainAxisSize.max,
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Padding(
                        padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 8),
                        child: Text(
                          cartItems[index].name,
                          style: AppTheme.of(context).subtitle2.override(
                                fontFamily: 'Poppins',
                                color: AppTheme.of(context).primaryText,
                              ),
                        ),
                      ),
                      Text(
                        '\$${cartItems[index].price}',
                        style: AppTheme.of(context).bodyText2,
                      ),
                      Padding(
                        padding: EdgeInsetsDirectional.fromSTEB(0, 8, 0, 0),
                        child: Text(
                          'Quanity: ${cartItems[index].quantity}',
                          style: AppTheme.of(context).bodyText2,
                        ),
                      ),
                    ],
                  ),
                ),
                IconButton(
                  icon: Icon(
                    Icons.delete_outline_rounded,
                    color: Color(0xFFE86969),
                    size: 20,
                  ),
                  onPressed: () {
                  // Remove item
                  },
                ),
              ],
            ),
          ),
        ),
      );
    });

Agregar productos

Una vez que la interfaz de usuario esté lista, puede completar la lista de productos agregando varios productos. En un escenario real, completaría esta lista con elementos obtenidos de su backend, pero agregaremos productos localmente en una variable para simplificar.

Primero, cree una clase de producto que contenga campos como id, nombre, imagen, precio y cantidad.

class Product {
  final int id;
  final String name;
  final String image;
  final double price;
  int quantity;

  Product({required this.id, required this.name, required this.image, required this.price, this.quantity = 0});
}

Ahora cree una lista de varios productos usando la clase anterior. Así:

final List<Product> products = [
  Product(
      id: 1,
      name: 'Champion',
      image:
          'https://images.unsplash.com/photo-1606107557195-0e29a4b5b4aa?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=764&q=80',
      price: 55.5),
  Product(
      id: 2,
      name: 'Stark',
      image:
          'https://images.unsplash.com/photo-1549298916-b41d501d3772?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1624&q=80',
      price: 65.5),

];

Así mismo, puedes añadir más productos si lo deseas. Una vez que esta lista esté lista, es hora de continuar y usarla para inflar el widget GridView, como se muestra a continuación:

GridView.builder(
  itemCount: products.length,
  itemBuilder: (context, index) => ProductTile(
    itemNo: index,
    product: products[index],
  ),
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    childAspectRatio: 1,
  ),
)

construye el carro

La funcionalidad central de cualquier aplicación de comercio electrónico es la capacidad de agregar productos al carrito para comprarlos. Para crear esta funcionalidad, puede integrar la gestión de estado en su aplicación.

Para esta aplicación, usaremos la técnica de administración de estado llamada Bloque porque puede separar la lógica comercial de la interfaz de usuario y es más fácil escribir y reutilizar pruebas.

Aquí hemos explicado cómo usar el patrón de diseño de bloque en una publicación de blog.

La gestión del estado del bloque requiere la adición de tres clases esenciales: bloque, evento y estado. Para agregar o eliminar artículos del carrito, agregaremos las siguientes clases:

CarritoBloque

Aquí es donde reside la lógica empresarial (agregar y eliminar elementos).

class CartBloc extends Bloc<CartEvent, CartState> {
  CartBloc() : super(ProductAdded(cartItem: []));

  final List<Product> _cartItems = [];
  List<Product> get items => _cartItems;
  bool isGridView = true;

  @override
  Stream<CartState> mapEventToState(CartEvent event) async* {
    if (event is AddProduct) {
      _cartItems.add(event.productIndex);
      yield ProductAdded(cartItem: _cartItems);
    } else if (event is RemoveProduct) {
      _cartItems.remove(event.productIndex);
      yield ProductRemoved(cartItem: _cartItems);
    } else if (event is ChangeGallaryView) {
      isGridView = event.isGridView;
      yield ChangeGallaryViewState(isGridView: isGridView);
    }
  }
}

(Nota: _cartItems es una fuente única de información para administrar artículos del carrito)

CarritoEvento

Esto se utiliza para enviar artículos al bloque del carrito.

abstract class CartEvent extends Equatable {
  const CartEvent();

  @override
  List<Object> get props => [];
}

class AddProduct extends CartEvent {
  final Product productIndex;

  const AddProduct(this.productIndex);

  @override
  List<Object> get props => [productIndex];

  @override
  String toString() => 'AddProduct { index: $productIndex }';
}

class RemoveProduct extends CartEvent {
  final Product productIndex;

  const RemoveProduct(this.productIndex);

  @override
  List<Object> get props => [productIndex];

  @override
  String toString() => 'RemoveProduct { index: $productIndex }';
}

Estado del carrito

Esto se utiliza para enviar elementos a la interfaz de usuario.

abstract class CartState {
  final List<Product> cartItem;
  final bool isGridView;
  const CartState({this.cartItem = const [], this.isGridView = true});

  @override
  List<Object> get props => [];
}

class CartLoadInProgress extends CartState {
  CartLoadInProgress({required super.cartItem});
}

class ProductAdded extends CartState {
  final List<Product> cartItem;

  const ProductAdded({required this.cartItem}) : super(cartItem: cartItem);

  @override
  List<Object> get props => [cartItem];

  @override
  String toString() => 'ProductAdded { todos: $cartItem }';
}

Desde la interfaz de usuario (un botón con el texto «Agregar al carrito»), puede insertar el siguiente código para agregar el artículo a la lista:

onPressed: () {
  Product p = widget.product;
  p.quantity = countControllerValue!.toInt();
  BlocProvider.of<CartBloc>(context).add(AddProduct(p));
}

Para eliminar el producto del carrito, simplemente puede activar el evento para eliminar el artículo, así:

onPressed: () {
  BlocProvider.of<CartBloc>(context).add(RemoveProduct(cartItems[index]));
}

Para recuperar artículos del carrito, puede envolver GridView o ListView en el generador de bloques, y la lista se actualiza cada vez que se agrega o elimina el artículo.

BlocBuilder<CartBloc, CartState>(builder: (_, cartState) {
  return ListView.builder();
}),

Agregar capacidad de respuesta

También puedes adaptar la versión web de tu aplicación. Esto significa que los usuarios no deberían sentir que están usando una aplicación móvil en un navegador; más bien debería dar la impresión de que es una aplicación web nativa.

Para esta aplicación, podemos mostrar más elementos cuando la aplicación se usa en una pantalla más grande. Es posible que se sorprenda al saber que esto se puede lograr con solo un pequeño cambio en el código.

Así es cómo:

return LayoutBuilder(builder: (context, constraints) {
  return GridView.builder(
    itemCount: products.length,
    itemBuilder: (context, index) => ProductTileAnimation(
      itemNo: index,
      product: products[index],
    ),
    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: constraints.maxWidth > 700 ? 4 : 2, //<-SEE HERE
      childAspectRatio: 1,
    ),
  );
});

Puede envolver el widget GridView en LayoutBuilder, que proporciona las restricciones utilizadas para determinar el ancho y el alto. Usando restricciones, podemos construir varias interfaces de usuario.

Para nuestro ejemplo, en el código anterior, cada vez que la resolución de la pantalla cambia a 700 o más de ancho, mostramos cuatro elementos en el eje transversal.

Así es como funciona:

Alternar vista de visualización de productos

A veces, es posible que desee permitir que los usuarios cambien la vista actual (es decir, GridView) y la muestren en ListView.

Para hacer esto, puede crear una variable booleana (probablemente dentro del bloque) y alternar su valor. Según esta variable, puede configurar dos widgets, GridView y ListView, y cambiar el icono.

Aquí se explica cómo cambiar el icono:

BlocBuilder<CartBloc, CartState>(builder: (_, cartState) {
  bool isGridView = cartState.isGridView;
  return IconButton(
      onPressed: () {
        BlocProvider.of<CartBloc>(context).add(ChangeGallaryView(!isGridView));
      },
      icon: !isGridView ? Icon(Icons.grid_on) : Icon(Icons.list));
})

Aquí está el código para elegir la lista a mostrar:

BlocBuilder<CartBloc, CartState>(builder: (_, cartState) {
  bool isGridView = cartState.isGridView;
  if (isGridView) {
    return LayoutBuilder(builder: (context, constraints) {
      return GridView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) => ProductTile(
          itemNo: index,
          product: products[index],
        ),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 1,
        ),
      );
    });
  } else {
    return ListView.builder(
        itemCount: products.length,
        itemBuilder: (BuildContext context, int index) {
          return ProductTile(
            itemNo: index,
            product: products[index],
          );
        });
  }
});

Búsqueda de Producto

Para permitir que los usuarios busquen en una lista, puede aprovechar el evento onChanged de TextFormField. Esto le da los últimos caracteres ingresados ​​en TextFormField. Puede usarlo para filtrar la lista principal y luego proporcionar el resultado de la búsqueda en una nueva lista, como se muestra a continuación:

TextFormField(
  controller: textController,
  obscureText: false,
  onChanged: (_) => EasyDebounce.debounce(
    'tFMemberController',
    Duration(milliseconds: 0),
    () {
      isSearchStarted =
          textController!.text.isNotEmpty && textController!.text.trim().length > 0;
      if (isSearchStarted) {
        print('${textController!.text.trim()}');
        searchedProducts = products
            .where((item) =>
                item.name.toLowerCase().contains(textController!.text.trim().toLowerCase()))
            .toList();
      }
      setState(() {});
    },
  ),
)

La variable isSearchStarted se utiliza para indicar si mostrar o no el resultado de la búsqueda.

ProductList(
  products: isSearchStarted ? searchedProducts : products,
)

Adición de animaciones (sistema de movimiento de materiales)

Es posible que desee agregar animaciones para mejorar la experiencia del usuario. En lugar de tener una transición de navegación predeterminada, puede agregar una animación que abre suavemente la página de detalles del producto cuando se toca.

Puede usar el conjunto de ajustes preestablecidos de animación del sistema de movimiento Material agregando la Biblioteca de animación a su aplicación.

Para hacer esto, envuelva su widget en OpenContainer y especifique la página que desea animar en el parámetro openBuilder.

Aquí está el código:

ContainerTransitionType _transitionType = ContainerTransitionType.fade;
OpenContainer<bool>(
  transitionType: _transitionType,
  openBuilder: (BuildContext _, VoidCallback openContainer) {
    return ProductDetailWidget(
      product: product,
    );
  },
  closedShape: const RoundedRectangleBorder(),
  closedElevation: 0.0,
  closedBuilder: (BuildContext _, VoidCallback openContainer) {
    return Container(
      // Product tile
    );
  },
)

Esto es lo que parece:

El código fuente completo de esta aplicación de comercio electrónico Flutter se puede encontrar en GitHub aquí.

Conclusión

Crear una experiencia de usuario intuitiva es esencial para una aplicación de comercio electrónico. Este tutorial le mostró cómo desarrollar grandes pantallas y exhibir productos de una manera atractiva.

También aprendimos a usar técnicas de administración de estado como Block para administrar elementos del carrito de compras y mejorar la aplicación agregando funciones como alternar, buscar y animaciones.

WP Dev JaGonzalez

Hijo, esposo y padre de un hermoso niño. Amante de los animales, la tecnología, informática y programación. Si tienes alguna duda, inquietud, comentario o deseas comunicarte directamente conmigo, puedes enviarme un correo electrónico a admin@jagonzalez.org

Compartir
Publicado por
WP Dev JaGonzalez

Entradas recientes

iPhone Hackeado: Qué Hacer para Proteger tu Dispositivo y Asegurar tu Seguridad

¿Has notado aplicaciones desconocidas o un drenaje inesperado de la batería? Estos podrían ser indicios…

3 meses hace

Cómo Restablecer un iPhone a su Estado de Fábrica

Saber cómo Restablecer un iPhone a su Estado de Fábrica es clave para solucionar problemas…

3 meses hace

Motorola planea lanzar al menos dos nuevos teléfonos Moto G en septiembre

Motorola ha confirmado el lanzamiento de Moto G84 5G y Moto G54 5G en India,…

1 año hace

El equipo de WizardLM afirma que un modelo de IA de terceros les robó el trabajo

Recuerde WizardCoder, ¿el codificador de IA que cubrimos recientemente aquí en Windows Report? Nos jactamos…

1 año hace

Las fallas del complemento Jupiter X Core amenazaron a 172.000 sitios web con apropiaciones de cuentas

Los investigadores han descubierto numerosos fallos de seguridad en el complemento WordPress Jupiter X Core…

1 año hace

Consola portátil Xbox: aquí tienes todo lo que necesitas saber al respecto

Para solucionar problemas del sistema de PC con Windows, necesitará una herramienta dedicada Fortect es…

1 año hace