Flutter自定义组件外观

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);
        }
      },
    );
  }
}
相关推荐
比格丽巴格丽抱11 小时前
flutter项目苹果编译运行打包上线
flutter·ios
SoaringHeart11 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
AiFlutter16 小时前
Flutter通过 Coap发送组播
flutter
嘟嘟叽2 天前
初学 flutter 环境变量配置
flutter
iFlyCai2 天前
深入理解Flutter生命周期函数之StatefulWidget(一)
flutter·生命周期·dart·statefulwidget
sunly_2 天前
Flutter:photo_view图片预览功能
android·javascript·flutter
Summer不秃2 天前
Flutter中sqflite的使用案例
flutter
sunly_2 天前
Flutter:TweenAnimationBuilder自定义隐式动画
flutter
AiFlutter2 天前
Flutter-Web首次加载时添加动画
前端·flutter
Allen Su3 天前
【Flutter 问题系列第 84 篇】如何清除指定网络图片的缓存
flutter·缓存·如何清除指定网络图片的缓存·网络图片缓存