用Flutter建立一个直观的电子商务产品库

41 阅读6分钟

建立一个良好的用户体验对你的应用程序的成功至关重要。无论你销售的产品有多好,如果你的应用程序没有提供积极的用户体验,你将会陷入困境。在本教程中,您将学习如何用Flutter构建一个直观的产品图片库,并提供更好的购买体验。

我们将建立一个示例应用程序,以吸引人的方式列出产品--点击一个产品,我们将动画化一个产品的详细信息页面,您可以设置其数量并将其添加到购物车。在顶部,你还会看到添加到购物车中的商品数量,当你点击它时,你可以看到所有添加到其中的产品列表。

在本教程结束时,你将学会如何为更宽的屏幕构建,允许搜索产品,切换视图,管理购物车的状态,以及在屏幕间导航时添加材质动作,以获得流畅的用户体验。

下面是我们的Flutter电子商务图片库完成后的样子。

Final Example Ecommerce App

以下是我们在本教程中要介绍的内容。

创建项目

在你开始之前,你必须安装Flutter SDK并创建一个基本项目。让我们看看如何做到这一点。

首先,从这个链接获取Flutter SDK。一旦安装完毕,通过在终端点击以下命令来检查是否一切正常。

flutter doctor

(注意:如果有任何东西被标记为红色标记,你必须在继续前进之前解决它们)

现在,创建一个你想创建这个项目的目录,并输入以下命令。这将在指定的目录中创建一个新的项目。

flutter create ecomm_app

为了打开、编辑文件内容和运行/调试/测试该项目,你需要安装IDE。你可以从以下任何一个编辑器中选择。

打开projectfolder/lib/main.dart,点击播放按钮,从IDE中运行启动程序。你也可以通过输入以下命令从命令行运行它。

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

创建屏幕

该示例应用程序由三个屏幕组成:产品列表、产品详情和购物车页面。

让我们看看这些屏幕是如何融入流程的。

Ecommerce App Three Screens

首先,你会看到所有的产品。点击任何一个商品,就会打开详细信息页面。你可以将商品加入购物车,并导航到购物车页面。

产品列表

第一个屏幕显示所有产品的名称、图片和价格。在这里你可以搜索产品,并在网格视图和列表视图之间切换视图。在点击商品时,它会打开产品的详细信息页面。

这里有一些基本的小工具,你可以用来建立第一个屏幕。

Product List Screen

这里是创建这个容器的确切代码。

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,
                ),
              ),
            ],
          ),
        ),
      ],
    ),
  ),
);

产品详情

产品详情页显示产品信息。它允许你设置产品的数量并将其添加到购物车中。你也可以从这个页面打开购物车。

这里有一些基本的部件,你可以用来构建产品详情页面。

Product Details Screen

徽章小部件不是标准的小部件;相反,它是从一个叫做徽章的库中添加的。当值被更新时,这将自动使徽章成为动画。

下面是在购物车项目上显示徽章的代码。

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(),
        ),
      );
    },
  ),
)

购物车

这个页面显示了添加到购物车的所有项目的列表,并可以从购物车中删除任何项目。在这里你可以显示所有的价格摘要和结账选项。

这里有一些重要的小工具,你可以用来建立购物车屏幕。

Ecommerce Cart Screen

这里你可以显示购物车中所有物品的列表。

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
                  },
                ),
              ],
            ),
          ),
        ),
      );
    });

添加产品

一旦用户界面准备好了,你可以通过添加各种产品来填充产品列表。在现实世界中,你会用从后端服务器检索到的物品来填充这个列表,但为了简化,我们将在本地的一个变量中添加产品。

首先,创建一个产品类,持有诸如id,name,image,price, 和quantity 等字段。

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});
}

现在,通过使用上面的类,创建一个各种产品的列表。像这样。

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),

];

同样地,如果你想的话,你可以添加更多的产品。一旦这个列表准备好了,它的时间就到了,用它来充气的GridView部件,如下图所示。

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

建立购物车

任何电子商务应用程序的核心功能都是将产品添加到购物车中进行购买。为了构建这个功能,你可以在你的应用程序中加入状态管理。

对于这个应用程序,我们将使用称为Bloc的状态管理技术,因为你可以将业务逻辑与UI分开,而且更容易编写和重用测试。

在这里,我们已经在一篇博文中介绍了如何使用Bloc设计模式。

Bloc状态管理需要你添加三个基本类:Bloc、事件和状态。为了在购物车中添加或删除物品,我们将添加以下类。

CartBloc

这是业务逻辑(添加和删除物品)所在的地方。

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);
    }
  }
}

(注意:_cartItems 是管理购物车项目的单一真理来源。)

CartEvent

这是用来发送物品到购物车的单元。

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 }';
}

购物车状态

用于将项目发送到用户界面。

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 }';
}

从用户界面(一个带有 "添加到购物车 "文字的按钮),你可以插入以下代码,将项目添加到列表中。

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

要从购物车中删除产品,你可以简单地触发事件来删除该项目,如下所示。

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

要检索购物车中的商品,你可以把GridView或ListView包在Bloc builder里面,每当商品被添加或删除,列表就会被更新。

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

增加响应性

你可能也想适应你的应用程序的网络版本。这意味着用户不应该感觉到他们是在浏览器上使用一个移动应用程序;相反,它应该感觉到这是一个原生的网络应用。

对于这个应用程序,当应用程序在更宽的屏幕上使用时,我们可以显示更多项目。你可能会惊讶地发现,只需在代码中稍作改动就可以实现这一点。

下面是你如何做到这一点的。

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,
    ),
  );
});

你可以将GridView widget包裹在LayoutBuilder ,它提供了constraints ,用来决定宽度和高度。利用这些约束条件,我们可以建立各种用户界面。

对于我们的例子,在上面的代码中,只要屏幕分辨率变为700或更大的宽度,我们就会在横轴上显示四个项目。

下面是它的工作原理。

Ecommerce App Mobile Web Size

切换产品显示视图

有时你可能想让用户切换当前视图(即GridView)并在ListView中显示。

要做到这一点,你可以创建一个布尔变量(可能在块内),并切换其值。基于这个变量,你可以定义两个小部件--GridView和ListView--并改变图标。

下面是你如何改变图标的方法。

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));
})

这里是选择显示哪个列表的代码。

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],
          );
        });
  }
});

搜索产品

为了让用户从列表中搜索,你可以利用TextFormField的onChanged 事件。这给你提供了输入到TextFormField的最新字符。你可以用它来过滤主列表,然后在一个新的列表中提供搜索结果,如下所示。

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(() {});
    },
  ),
)

isSearchStarted 变量用来表示是否显示搜索结果。

ProductList(
  products: isSearchStarted ? searchedProducts : products,
)

添加动画(材料运动系统)

为了改善用户体验,可能需要添加动画。你可以添加动画来代替默认的导航过渡,当你点击产品详情页时,可以平滑地打开该页面。

你可以通过向你的应用程序添加动画库来使用Material motion系统的预建动画集。

要做到这一点,请将你的部件包裹在OpenContainer ,并在openBuilder 参数中提供你想要的动画的页面。

这里是代码。

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
    );
  },
)

这是它的外观。

Ecommerce App Transition

这个Flutter电子商务应用程序的完整源代码可以在GitHub找到。

总结

创建一个直观的用户体验对电子商务应用来说是至关重要的。本教程向你展示了如何开发漂亮的屏幕,并以吸引人的方式显示产品。

我们还学习了如何使用Bloc等状态管理技术来管理购物车中的物品,以及如何通过添加切换、搜索和动画等功能来增强应用。

The postBuild an intuitive ecommerce product gallery with Flutterappeared first onLogRocket Blog.