In this section, we will embark on a practical journey to create an e-commerce catalog list page. My goal is to guide you through a step-by-step process, allowing you to grasp the concepts of customizing widget appearance using styles and themes. By the end of this challenge, you will have built a modern and simple product catalog screen that's both visually appealing and user-friendly.
在本节中,我们将开始创建电子商务目录列表页面的实践之旅。我的目标是一步一步地引导你,让你掌握使用样式和主题自定义 widget 外观的概念。在本挑战结束时,您将创建一个现代而简单的产品目录页面,它既具有视觉吸引力,又对用户友好。
Project requirements
项目要求
Our e-commerce catalog list page will consist of the following components:
我们的电子商务目录列表页面将由以下部分组成:
App Bar: A top navigation bar displaying the app's logoand search icon.
应用程序栏: 顶部导航栏:显示应用程序徽标和搜索图标。
Product List: A grid layout showcasing various products,each with an image, title, and price.
产品列表: 展示各种产品的网格布局,每种产品都有图片、标题和价格。
Product Card: A styled card container for each product,with a shadow effect and proper padding.
产品卡: 每个产品都有一个风格化的卡片容器,具有阴影效果和适当的衬垫。
Color Scheme: The app should support both light anddark modes, allowing users to switch between themeffortlessly.
色彩方案: 应用程序应支持浅色和深色模式,让用户可以轻松切换主题。
Now let's Dive In!
现在,让我们 "潜入 "其中!
Step 1: Set Up the Project
步骤 1:设置项目
Before we begin, create a new Flutter project and structure your folders appropriately.
在开始之前,请创建一个新的 Flutter 项目,并合理安排文件夹结构。
Step 2: Define the Color Palette
步骤 2:确定调色板
Define the primary and accent colors for your app's theme. For instance, you might choose a shade of blue as your primary color and a contrasting color as your accent.
确定应用程序主题的主色调和重点色调。例如,您可以选择蓝色作为主色调,对比色作为点缀色。
Step 3: Create a Theme
步骤 3:创建主题
Inside the main.dart
file, wrap your MaterialApp
with a Theme
widget. Configure the theme with the colors and other properties you've defined.
在 main.dart 文件中,用主题部件包装 MaterialApp。使用您定义的颜色和其他属性配置主题。
Step 4: Build the App Bar
步骤 4:创建应用程序栏
Create the app bar with your app's logo and search icon. Use the AppBar
widget and customize it using your defined theme.
创建带有应用徽标和搜索图标的应用栏。使用 AppBar 小工具,并使用您定义的主题对其进行自定义。
Step 5: Design the Product List
步骤 5:设计产品清单
Use the GridView.builder
widget to create the product list. Each product will be represented by a ProductCard
widget.
使用 GridView.builder widget 创建产品列表。每个产品都将由 ProductCard widget 表示。
Step 6: Implement the Product Card
步骤 6:实施产品卡
Define a custom ProductCard
widget that represents an individual product. Style it with shadows, borders, and appropriate padding.
定义代表单个产品的自定义 ProductCard widget。使用阴影、边框和适当的填充为其设计样式。
Step 7: Implement Light and Dark Modes
步骤 7:实施明暗模式
Now comes the exciting part. Allow your users to switch between light and dark modes. In your Theme
widget, set up a brightness
property that toggles between Brightness.light
and Brightness.dark
based on user preference.
现在到了激动人心的部分。允许用户在明暗模式之间切换。在主题 widget 中,设置一个亮度属性,根据用户的偏好在 Brightness.light 和 Brightness.dark 之间切换。
As you attempt to create the screen, don't hesitate to experiment with different color palettes, font styles, and element layouts. Change the primary and accent colors to observe how they affect the overall look. Adjust the shadow properties on the product cards to fine-tune their appearance.
在尝试创建屏幕时,不要犹豫,尝试不同的调色板、字体样式和元素布局。改变主色和重点色,观察它们对整体外观的影响。调整产品卡片上的阴影属性,对其外观进行微调。
Solution
解决方案
Let's walk through the step-by-step solution for creating the ecommerce catalog list page with custom styles and themes.
让我们一步步了解如何使用自定义样式和主题创建电子商务目录列表页面。
Step 1: Define the theme.dart
步骤 1:定义 theme.dart
Create a new file theme.dart
in your lib folder and begin by defining the AppThemes
and AppColors
classes to handle theme changes.
在 lib 文件夹中创建一个新文件 theme.dart,首先定义 AppThemes 和 AppColors 类来处理主题更改。
dart
// theme.dart
import "package:flutter/material.dart";
class AppColors {
static const Color primaryColor = Colors.blue;
}
class AppThemes {
static final lightTheme = ThemeData.light().copyWith(
primaryColor: AppColors.primaryColor,
);
static final darkTheme = ThemeData.dark().copyWith(
primaryColor: AppColors.primaryColor,
);
}
Step 2: Import Required Packages and Create Theme Manager
第 2 步:导入所需软件包并创建主题管理器
In the main.dart
file, begin by importing the necessary packages and defining the ThemeManager
class to handle theme changes.
在 main.dart
文件中,首先导入必要的软件包并定义 ThemeManager
类以处理主题更改。
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'theme.dart';
void main() {
runApp(const MyApp());
}
class ThemeManager {
static void setThemeMode(ThemeMode mode) {
Get.changeThemeMode(mode);
}
}
Step 3: Define the Main App Widget
步骤 3:定义主应用程序小工具
Create the MyApp
widget which sets up the MaterialApp and initializes the app with a light theme.
创建 MyApp widget,设置 MaterialApp 并使用浅色主题初始化应用程序。
dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Lagos Store',
theme: AppThemes.lightTheme,
darkTheme: AppThemes.darkTheme,
home: const CatalogPage(),
);
}
}
Step 4: Create the Catalog Page
步骤 4:创建目录页面
In your lib folder, define the CatalogPage
widget that displays the app bar and the grid of products.
在 lib 文件夹中,定义用于显示应用栏和产品网格的 CatalogPage widget。
dart
class CatalogPage extends StatelessWidget {
const CatalogPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lagos Store'),
actions: const [
ThemeManagerWidget(),
],
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.7,
),
itemCount: products.length,
itemBuilder: (context, index) {
return ProductCard(product: products[index]);
},
),
);
}
}
Step 5: Define the Product Class
步骤 5:定义产品类
Modify the Product
class to include a price field along with the existing name and imageUrl fields.
修改 Product 类,使其包含一个价格字段以及现有的 name 和 imageUrl 字段。
dart
class Product {
final String name;
final String imageUrl;
final double price; // Add the price field
Product({required this.name, required this.imageUrl, required this.price});
}
final List<Product> products = [
Product(name: 'White Garri', imageUrl: 'assets/1.jpg', price: 99.99),
Product(name: 'Smoked Fish', imageUrl: 'assets/2.jpg', price: 149.99),
Product(name: 'Bean Flour', imageUrl: 'assets/3.jpg', price: 199.99),
// Add more products
];
Step 6: Create Product Cards
步骤 6:创建产品卡
Adjust the ProductCard
widget to display the product's name and price.
调整 ProductCard 部件,以显示产品名称和价格。
dart
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({super.key, required this.product});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
product.imageUrl,
height: 100,
),
const SizedBox(height: 8),
Text(product.name),
Text('\$${product.price.toStringAsFixed(2)}',
style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
);
}
}
Step 7: Add Theme Toggle Button
第 7 步:添加主题切换按钮
Modify the ThemeManagerWidget
to include a toggle button for switching between light and dark themes.
修改 ThemeManagerWidget,使其包含一个用于在浅色和深色主题之间切换的切换按钮。
dart
class ThemeManagerWidget extends StatelessWidget {
const ThemeManagerWidget({super.key});
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.brightness_4),
onPressed: () {
if (Get.isDarkMode) {
ThemeManager.setThemeMode(ThemeMode.light);
} else {
ThemeManager.setThemeMode(ThemeMode.dark);
}
},
);
}
}
And that's it! You've successfully built an e-commerce catalog list page with custom styles and themes using Flutter.
就是这样!您已经使用 Flutter 成功创建了一个具有自定义样式和主题的电子商务目录列表页面。
Full source code
完整源代码
pubspec.yaml
yaml
name: c01_hello
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.5.3
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
get:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
flutter:
uses-material-design: true
assets:
- assets/1.jpg
- assets/2.jpg
- assets/3.jpg
theme.dart
dart
// theme.dart
import "package:flutter/material.dart";
class AppColors {
static const Color primaryColor = Colors.blue;
}
class AppThemes {
static final lightTheme = ThemeData.light().copyWith(
primaryColor: AppColors.primaryColor,
);
static final darkTheme = ThemeData.dark().copyWith(
primaryColor: AppColors.primaryColor,
);
}
main.dart
dart
//main.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'theme.dart';
void main() {
runApp(const MyApp());
}
class ThemeManager {
static void setThemeMode(ThemeMode mode) {
Get.changeThemeMode(mode);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Lagos Store',
theme: AppThemes.lightTheme,
darkTheme: AppThemes.darkTheme,
home: const CatalogPage(),
);
}
}
class CatalogPage extends StatelessWidget {
const CatalogPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lagos Store'),
actions: const [
ThemeManagerWidget(),
],
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.7,
),
itemCount: products.length,
itemBuilder: (context, index) {
return ProductCard(product: products[index]);
},
),
);
}
}
class Product {
final String name;
final String imageUrl;
final double price; // Add the price field
Product({required this.name, required this.imageUrl, required this.price});
}
final List<Product> products = [
Product(name: 'White Garri', imageUrl: 'assets/1.jpg', price: 99.99),
Product(name: 'Smoked Fish', imageUrl: 'assets/2.jpg', price: 149.99),
Product(name: 'Bean Flour', imageUrl: 'assets/3.jpg', price: 199.99),
// Add more products
];
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({super.key, required this.product});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
product.imageUrl,
height: 100,
),
const SizedBox(height: 8),
Text(product.name),
Text('\$${product.price.toStringAsFixed(2)}',
style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
);
}
}
class ThemeManagerWidget extends StatelessWidget {
const ThemeManagerWidget({super.key});
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.brightness_4),
onPressed: () {
if (Get.isDarkMode) {
ThemeManager.setThemeMode(ThemeMode.light);
} else {
ThemeManager.setThemeMode(ThemeMode.dark);
}
},
);
}
}
效果预览:
R emember, practice makes perfect, so don't hesitate to explore more and apply what you've learned to your own projects. Happy coding!
请记住,实践出真知,所以不要犹豫,继续探索,并将所学应用到自己的项目中。祝你编码愉快!
代码改造和封装
初始代码
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(const MyApp());
}
class AppColors {
static const Color primaryColor = Colors.blue;
}
class AppThemes {
static final lightTheme = ThemeData.light().copyWith(
primaryColor: AppColors.primaryColor,
);
static final darkTheme = ThemeData.dark().copyWith(
primaryColor: AppColors.primaryColor,
);
}
class ThemeManager {
static void setThemeMode(ThemeMode mode) {
Get.changeThemeMode(mode);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Lagos Store',
theme: AppThemes.lightTheme,
darkTheme: AppThemes.darkTheme,
home: const CatalogPage(),
);
}
}
class CatalogPage extends StatelessWidget {
const CatalogPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lagos Store'),
actions: const [
ThemeManagerWidget(),
],
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.7,
),
itemCount: products.length,
itemBuilder: (context, index) {
return ProductCard(product: products[index]);
},
),
);
}
}
class Product {
final String name;
final String imageUrl;
final double price; // Add the price field
Product({required this.name, required this.imageUrl, required this.price});
}
final List<Product> products = [
Product(name: 'White Garri', imageUrl: 'assets/1.jpg', price: 99.99),
Product(name: 'Smoked Fish', imageUrl: 'assets/2.jpg', price: 149.99),
Product(name: 'Bean Flour', imageUrl: 'assets/3.jpg', price: 199.99),
// Add more products
];
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({super.key, required this.product});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
product.imageUrl,
height: 100,
),
const SizedBox(height: 8),
Text(product.name),
Text('\$${product.price.toStringAsFixed(2)}',
style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
);
}
}
class ThemeManagerWidget extends StatelessWidget {
const ThemeManagerWidget({super.key});
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.brightness_4),
onPressed: () {
if (Get.isDarkMode) {
ThemeManager.setThemeMode(ThemeMode.light);
} else {
ThemeManager.setThemeMode(ThemeMode.dark);
}
},
);
}
}
初步抽离
theme.dart
dart
import 'package:flutter/material.dart';
class AppColors {
static const Color primaryColor = Colors.blue;
}
class AppThemes {
static final lightTheme = ThemeData.light().copyWith(
primaryColor: AppColors.primaryColor,
);
static final darkTheme = ThemeData.dark().copyWith(
primaryColor: AppColors.primaryColor,
);
}
main.dart
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'theme.dart';
void main() {
runApp(const MyApp());
}
class ThemeManager {
static void setThemeMode(ThemeMode mode) {
Get.changeThemeMode(mode);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Lagos Store',
theme: AppThemes.lightTheme,
darkTheme: AppThemes.darkTheme,
home: const CatalogPage(),
);
}
}
class CatalogPage extends StatelessWidget {
const CatalogPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Lagos Store'),
actions: const [
ThemeManagerWidget(),
],
),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.7,
),
itemCount: products.length,
itemBuilder: (context, index) {
return ProductCard(product: products[index]);
},
),
);
}
}
class Product {
final String name;
final String imageUrl;
final double price; // Add the price field
Product({required this.name, required this.imageUrl, required this.price});
}
final List<Product> products = [
Product(name: 'White Garri', imageUrl: 'assets/1.jpg', price: 99.99),
Product(name: 'Smoked Fish', imageUrl: 'assets/2.jpg', price: 149.99),
Product(name: 'Bean Flour', imageUrl: 'assets/3.jpg', price: 199.99),
// Add more products
];
class ProductCard extends StatelessWidget {
final Product product;
const ProductCard({super.key, required this.product});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
product.imageUrl,
height: 100,
),
const SizedBox(height: 8),
Text(product.name),
Text('\$${product.price.toStringAsFixed(2)}',
style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
);
}
}
class ThemeManagerWidget extends StatelessWidget {
const ThemeManagerWidget({super.key});
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.brightness_4),
onPressed: () {
if (Get.isDarkMode) {
ThemeManager.setThemeMode(ThemeMode.light);
} else {
ThemeManager.setThemeMode(ThemeMode.dark);
}
},
);
}
}