【Flutter Todo 实战】第八章:关键技术深入

· 5 min read

本章将深入讲解待办事项应用中使用的三个关键技术:Material Design 3、SharedPreferences 和 Provider。

8.1 Material Design 3

什么是 Material Design 3?

Material Design 3(简称 M3)是 Google 最新的设计语言,相比 M2 有以下改进:

  • 更现代的视觉风格 - 圆角更大、颜色更柔和
  • 动态颜色 - 可以从壁纸提取主题色
  • 更好的可访问性 - 对比度更高
  • 更灵活的组件 - 更多自定义选项

启用 Material Design 3

MaterialApp(
  theme: ThemeData(
    useMaterial3: true,  // 启用 M3
    colorScheme: ColorScheme.fromSeed(
      seedColor: Color(0xFF6750A4),  // 主题种子色
    ),
  ),
)

ColorScheme(颜色方案)

ColorScheme 定义了应用的颜色系统:

ColorScheme(
  primary: Color(0xFF6750A4),      // 主色
  onPrimary: Colors.white,          // 主色上的文字
  primaryContainer: Color(0xFFEADDFF),  // 主色容器
  onPrimaryContainer: Color(0xFF21005D), // 主色容器上的文字
  
  secondary: Color(0xFF625B71),     // 次色
  onSecondary: Colors.white,
  
  surface: Colors.white,            // 表面色(卡片背景)
  onSurface: Color(0xFF1C1B1F),     // 表面上的文字
  
  error: Color(0xFFB3261E),         // 错误色
  onError: Colors.white,
  
  outline: Color(0xFF79747E),       // 边框色
)

使用 ColorScheme

final colorScheme = Theme.of(context).colorScheme;

Container(
  color: colorScheme.primaryContainer,
  child: Text(
    'Hello',
    style: TextStyle(color: colorScheme.onPrimaryContainer),
  ),
)

主题配置完整示例

ThemeData _buildLightTheme() {
  return ThemeData(
    useMaterial3: true,
    brightness: Brightness.light,
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(0xFF6750A4),
      brightness: Brightness.light,
    ),
    
    // AppBar 主题
    appBarTheme: const AppBarTheme(
      centerTitle: true,
      elevation: 0,
    ),
    
    // 卡片主题
    cardTheme: CardThemeData(
      elevation: 0,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
      ),
    ),
    
    // 输入框主题
    inputDecorationTheme: InputDecorationTheme(
      filled: true,
      border: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
        borderSide: BorderSide.none,
      ),
    ),
    
    // 浮动按钮主题
    floatingActionButtonTheme: FloatingActionButtonThemeData(
      elevation: 4,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
    ),
  );
}

8.2 SharedPreferences

什么是 SharedPreferences?

SharedPreferences 是一个轻量级的本地存储方案,适合存储简单的键值对数据:

  • ✅ 适合存储:用户设置、小量数据、缓存
  • ❌ 不适合存储:大量数据、复杂查询、敏感信息

添加依赖

1dependencies:
2  shared_preferences: ^2.2.2

基本使用

import 'package:shared_preferences/shared_preferences.dart';

// 获取实例
final prefs = await SharedPreferences.getInstance();

// 存储数据
await prefs.setString('username', '张三');
await prefs.setInt('age', 25);
await prefs.setBool('isLoggedIn', true);
await prefs.setStringList('tags', ['flutter', 'dart']);

// 读取数据
final username = prefs.getString('username');
final age = prefs.getInt('age');

// 删除数据
await prefs.remove('username');

// 清空所有数据
await prefs.clear();

存储任务列表

import 'dart:convert';

class StorageService {
  static const String _tasksKey = 'tasks';
  
  /// 保存任务列表
  static Future saveTasks(List tasks) async {
    final prefs = await SharedPreferences.getInstance();
    
    // 1. 将 Task 列表转换为 JSON 列表
    final tasksJson = tasks.map((task) => task.toJson()).toList();
    
    // 2. 将 JSON 列表编码为字符串
    final tasksString = jsonEncode(tasksJson);
    
    // 3. 存储字符串
    await prefs.setString(_tasksKey, tasksString);
  }
  
  /// 加载任务列表
  static Future> loadTasks() async {
    final prefs = await SharedPreferences.getInstance();
    
    // 1. 读取字符串
    final tasksString = prefs.getString(_tasksKey);
    
    if (tasksString == null || tasksString.isEmpty) {
      return [];
    }
    
    // 2. 解码字符串为 JSON 列表
    final tasksJson = jsonDecode(tasksString) as List;
    
    // 3. 将 JSON 列表转换为 Task 列表
    return tasksJson
        .map((json) => Task.fromJson(json as Map))
        .toList();
  }
}

数据流向

Task 对象列表
     │
     ▼
 toJson() 转换
     │
     ▼
JSON 列表 (List)
     │
     ▼
jsonEncode() 编码
     │
     ▼
字符串 (String)
     │
     ▼
SharedPreferences 存储

8.3 Provider 深入

Provider 的几种类型

Provider 类型 用途
Provider 提供不可变对象
ChangeNotifierProvider 提供可通知变化的对象
FutureProvider 提供异步数据
StreamProvider 提供流数据
MultiProvider 组合多个 Provider

MultiProvider(多个 Provider)

当应用需要多个 Provider 时:

MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => TaskProvider()),
    ChangeNotifierProvider(create: (_) => UserProvider()),
    ChangeNotifierProvider(create: (_) => SettingsProvider()),
  ],
  child: MyApp(),
)

ProxyProvider(依赖其他 Provider)

当一个 Provider 依赖另一个 Provider 时:

ChangeNotifierProxyProvider(
  create: (_) => StatsProvider(),
  update: (_, taskProvider, statsProvider) {
    return statsProvider!..updateTasks(taskProvider.tasks);
  },
)

性能优化

1. 使用 const 构造函数

// ❌ 每次重建都创建新对象
return Container(
  child: Text('Hello'),
);

// ✅ 使用 const,复用对象
return const Container(
  child: Text('Hello'),
);

2. 使用 Selector 精确监听

// ❌ 任务数量变化时整个组件重建
final provider = context.watch();
return Text('${provider.totalTaskCount}');

// ✅ 只监听 totalTaskCount 的变化
Selector(
  selector: (_, provider) => provider.totalTaskCount,
  builder: (_, count, __) => Text('$count'),
)

3. 合理使用 Consumer 的 child 参数

Consumer(
  builder: (context, provider, child) {
    return Stack(
      children: [
        // 这个组件不会重建
        child!,
        // 这个组件会重建
        Text('${provider.totalTaskCount}'),
      ],
    );
  },
  // 传入不需要重建的组件
  child: ExpensiveWidget(),
)

8.4 异步编程

Future 和 async/await

// 定义异步方法
Future loadData() async {
  // 等待异步操作完成
  final data = await fetchData();
  
  // 继续执行
  processData(data);
}

// 调用异步方法
void initState() {
  super.initState();
  loadData();  // 不需要 await
}

错误处理

Future loadData() async {
  try {
    final data = await fetchData();
    processData(data);
  } catch (e) {
    // 处理错误
    print('加载失败: $e');
  } finally {
    // 无论成功失败都会执行
    setLoading(false);
  }
}

多个异步操作

// 串行执行
Future loadAll() async {
  await loadTasks();
  await loadSettings();
  await loadUser();
}

// 并行执行
Future loadAll() async {
  await Future.wait([
    loadTasks(),
    loadSettings(),
    loadUser(),
  ]);
}

8.5 生命周期

StatefulWidget 生命周期

constructor
    │
    ▼
createState()
    │
    ▼
initState() ───────▶ 初始化(只执行一次)
    │
    ▼
didChangeDependencies()
    │
    ▼
build() ───────────▶ 构建 UI(可能执行多次)
    │
    ▼
didUpdateWidget() ─▶ 父组件重建时调用
    │
    ▼
deactivate()
    │
    ▼
dispose() ─────────▶ 销毁(清理资源)

常用生命周期方法

class _MyWidgetState extends State {
  @override
  void initState() {
    super.initState();
    // 初始化:加载数据、设置监听器
    loadData();
  }
  
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // 依赖变化时调用
  }
  
  @override
  void didUpdateWidget(covariant MyWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 父组件重建时调用
  }
  
  @override
  void dispose() {
    // 清理资源:取消订阅、释放控制器
    _controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

8.6 小结

技术 用途
Material Design 3 现代美观的 UI 设计
SharedPreferences 本地数据持久化
Provider 状态管理和数据共享
async/await 异步编程
生命周期 管理组件的创建和销毁

下一步第九章:常见问题排查

Related Articles