惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
小众软件
小众软件
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - Franky
Jina AI
Jina AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Y
Y Combinator Blog
V
Visual Studio Blog
C
Check Point Blog
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42
量子位
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
M
MIT News - Artificial intelligence
爱范儿
爱范儿
B
Blog RSS Feed
MyScale Blog
MyScale Blog
H
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
L
LangChain Blog
D
Docker

博客园 - 孤独的猫咪神

自定义贪吃蛇环境进行PPO模型训练 通过TensorBoard进行PPO参数优化 TensorBoard PPO log 解析 macos(M4)上跑强化学习 - PyTorch LunarLander-v3 Stable-Baselines3 部分机型下,Flutter的flutter_inappwebview在使用evaluateJavascript调用postMessage失败的情况 HarmonyOS中,html 与 ets 桥接沟通 Flutter中,html 与 dart 桥接沟通 Flutter 环境变量 Mole,清理Mac的小工具 Ubuntu 20.04 开机自动添加git的ssh 米家 + arduino + 自定义服务器 Flutter 第三方库 Jenkins CLI 通过ssh方式链接时的证书 阿里云Ubuntu 14.04 + Nginx + .net core + MySql 移动开发网络接口 经验总结 阿里云Ubuntu 14.04 + Nginx + let's encrypt 搭建https访问 Pathoto项目:AWS+golang+beego搭建 osx开发,skport项目记录 使用Jekyll在Github上搭建博客 iOS搜索附近的位置(类似微博朋友圈位置) retrofit2中ssl的Trust anchor for certification path not found问题 Android中的Semaphore React Native 在现有项目中的探路 博客园 Linux客户端 2.0 正式发布! 博客园 Windows客户端 2.0 正式发布! 博客园 Mac客户端 2.0 正式发布!
Flutter自定义主题Theme最佳实践
孤独的猫咪神 · 2026-03-04 · via 博客园 - 孤独的猫咪神

Flutter中使用主题,基本都是通过ThemeData来进行的(不会有人用CupertinoThemeData吧,如果有,我敬你是个汉子)。而ThemeData的编写和定义,都牢牢的和Material Design风格绑定在了一起。
那么,问题来了,我们在开发过程中,设计师给出的设计稿,通常带有设计师自己的一套风格主题。
这些风格主题和被强绑定的Material Design风格,基本都是天差地别,没有一丝丝的相似。

这就出现了一些解决方案,不过基本都是自定义一个AppColors之类的类,然后通过状态管理框架(例如:Getx、Bloc、Provider等等)进行全局刷新。
这里就出现了一些纠结的地方:

  • Flutter中默认的Theme已经提供了切换的方法,无需再通过状态管理框架再写一次了,但是强制使用了Material Design风格,基本上和设计师给的冲突了。
  • 通过状态管理框架自己实现一套颜色配置,完全抛弃Theme提供的所有帮助。

其实,我们可以通过ThemeExtension来进行扩展,把我们的theme进行扩展。
来,上教程!

1. 先来一个AppColors压压惊

当然,无论怎样,都需要定义我们的AppColors。在这里,用来存放所有的设计师给的规范颜色值。
例如:

import 'package:flutter/material.dart';

/// app_colors.dart

class AppLightColors {
  static const Color demo1 = Colors.red; // 红色的
  static const Color demo2 = Colors.green; // 绿色的
  // ... 这里接着写设计师提供的所有颜色值
}

class AppDarkColors {
  static const Color demo1 = Colors.black; // 黑色的
  static const Color demo2 = Colors.white; // 白色的
  // ... 这里接着写设计师提供的所有颜色值
}

2. 定义我们自己的Theme

接着,我们就要创建我们自己的Theme了,通过ThemeExtension来实现,这里有几点需要注意:

  • 如果颜色值很多(基本都会很多),尽量使用ai来帮你实现复制粘贴内容,这里的颜色值需要完完整整的都定义出来,跟着设计师给出的颜色表。
  • lerp方法是一个渐变方法,切换主题后,所有颜色会有一个渐变效果,很好看。可以直接使用Color.lerp方法进行。
    例如:
import 'package:flutter/material.dart';

/// app_colors_extension.dart

class AppColorsExtension extends ThemeExtension<AppColorsExtension> {
  AppColorsExtension({
    required this.demo1,
    required this.demo2,
    // ... 这里需要定义所有的值,如果写的累就找AI吧
  });

  final Color demo1; // 演示值1
  final Color demo2; // 演示值2
  // ... 这里需要定义所有的值,如果写的累就找AI吧

  @override
  ThemeExtension<AppColorsExtension> copyWith({Color? demo1, Color? demo2}) {
    return AppColorsExtension(
      demo1: demo1 ?? this.demo1,
      demo2: demo2 ?? this.demo2,
      // ... 这里需要定义所有的值,如果写的累就找AI吧
    );
  }

  @override
  ThemeExtension<AppColorsExtension> lerp(
    ThemeExtension<AppColorsExtension>? other,
    double t,
  ) {
    // 这里排除了不是我们自定义的情况
    if (other is! AppColorsExtension) {
      return this;
    }

    return AppColorsExtension(
      demo1: Color.lerp(demo1, other.demo1, t)!,
      demo2: Color.lerp(demo2, other.demo2, t)!,
      // ... 这里需要定义所有的值,如果写的累就找AI吧
    );
  }
}

3. 写我们的Theme吧

前两步,我们定义好了ThemeExtension和Color,现在,通过自定义的theme,我们把ThemeExtension和Color绑定在一起。
例如:

import 'package:flutter/material.dart';

import 'app_colors.dart';
import 'app_colors_extension.dart';

/// app_themes.dart

// 我们自定义的亮色主题
class LightTheme {
  static ThemeData theme = ThemeData.light().copyWith(
    extensions: [_lightAppColors],
  );

  static final _lightAppColors = AppColorsExtension(
    demo1: AppLightColors.demo1,
    demo2: AppLightColors.demo2,
  );
}

// 我们自定义的暗色主题
class DarkTheme {
  static ThemeData theme = ThemeData.dark().copyWith(
    extensions: [_darkAppColors],
  );

  static final _darkAppColors = AppColorsExtension(
    demo1: AppDarkColors.demo1,
    demo2: AppDarkColors.demo2,
  );
}

4. 把自定的Theme配置到MaterialApp中

上一步中我们写好了亮色主题和暗色主题,那么我们把这个主题绑定到Flutter的项目中。
例如:

import 'package:flutter/material.dart';

import 'home_page.dart';
import 'app_themes.dart';

/// main.dart

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ThemeMode _themeMode = ThemeMode.light; // 默认设置为light

  // 这里定义一个切换主题的方法,提供给HomePage使用,这里看大家的状态管理框架要怎么处理了
  void toggleTheme() {
    setState(() {
      _themeMode = _themeMode == ThemeMode.light
          ? ThemeMode.dark
          : ThemeMode.light;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Theme Demo',
      theme: AppLightTheme.theme, // 这里定义亮色主题
      darkTheme: AppDarkTheme.theme, // 这里定义暗色主题
      themeMode: _themeMode, // 设置主题模式,这里就要看大家的状态管理框架要怎么处理了
      home: HomePage(toggleTheme: toggleTheme),
    );
  }
}

5. 用起来用起来

至此,我们所有的配置工作就全部做完了,现在,就开始用起来。
获取颜色的方法:Theme.of(context).extension<AppColorsExtension>()?.demo1
例如:

import 'package:flutter/material.dart';

import 'app_colors_extension.dart';

/// home_page.dart

class HomePage extends StatelessWidget {
  const HomePage({super.key, required this.toggleTheme});

  final void Function() toggleTheme;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter Theme Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: .center,
          children: [
            Container(
              decoration: BoxDecoration(
                color: Theme.of(
                  context,
                ).extension<AppColorsExtension>()!.demo1, // 这里就是使用我们自定义的颜色
                border: Border.all(width: 1, color: Colors.grey),
              ),
              height: 120,
              width: 120,
            ),
            Container(
              decoration: BoxDecoration(
                color: Theme.of(
                  context,
                ).extension<AppColorsExtension>()?.demo2, // 这里就是使用我们自定义的颜色
                border: Border.all(width: 1, color: Colors.grey),
              ),
              height: 120,
              width: 120,
            ),
            ElevatedButton(
              onPressed: () {
                // 点击这里切换主题, 从light切换到dark, 从dark切换到light
                toggleTheme();
              },
              child: Text("切换主题"),
            ),
          ],
        ),
      ),
    );
  }
}

这样,我们的代码就都写好啦。

6. 来看看效果

这就是效果咯,来看一下,感受一下

浅色 深色 动画效果
Simulator Screenshot - iPhone 16e - 2026-03-04 at 11.17 Simulator Screenshot - iPhone 16e - 2026-03-04 at 11.17 Simulator Screen Recording - iPhone 16e - 2026-03-04 at 11.21

7. 一点点简化的小方法

我们看到,第5步中,获取颜色的方法Theme.of(context).extension<AppColorsExtension>()?.demo1,写起来有点长,我们来化简一下:
例如:
首先,得修改一下app_themes.dart文件,来提供默认的颜色

// ......
// 我们自定义的亮色主题
class AppLightTheme {
  static ThemeData theme = ThemeData.light().copyWith(
    extensions: [lightAppColors], // <-- 改了这里,把下划线去掉,变为public字段
  );

  // 改了这里,把下划线去掉,变为public字段
  static final lightAppColors = AppColorsExtension(
    demo1: AppLightColors.demo1,
    demo2: AppLightColors.demo2,
  );
}
// ......

然后,我们新增一个Extension类,来简化颜色的调用:

import 'package:flutter/material.dart';

import 'app_colors_extension.dart';
import 'app_themes.dart';

/// theme_extension.dart

extension AppThemeExtension on ThemeData {
  /// 用法: Theme.of(context).appColors;
  AppColorsExtension get appColors =>
      extension<AppColorsExtension>() ?? AppLightTheme.lightAppColors;
}

这时,我们调用的时候,可以使用Theme.of(context).appColors.demo1来进行。
例如:

import 'package:flutter/material.dart';

import 'theme_extension.dart'; // 这里就需要引入这个类来使用我们的简化方法
// import 'app_colors_extension.dart';   <-- 这个就不用啦

/// home_page.dart

class HomePage extends StatelessWidget {
  const HomePage({super.key, required this.toggleTheme});

  final void Function() toggleTheme;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter Theme Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: .center,
          children: [
            Container(
              decoration: BoxDecoration(
                color: Theme.of(context).appColors.demo1, // 这里就是使用我们自定义的颜色
                border: Border.all(width: 1, color: Colors.grey),
              ),
              height: 120,
              width: 120,
            ),
            Container(
              decoration: BoxDecoration(
                color: Theme.of(context).appColors.demo2, // 这里就是使用我们自定义的颜色
                border: Border.all(width: 1, color: Colors.grey),
              ),
              height: 120,
              width: 120,
            ),
            ElevatedButton(
              onPressed: () {
                // 点击这里切换主题, 从light切换到dark, 从dark切换到light
                toggleTheme();
              },
              child: Text("切换主题"),
            ),
          ],
        ),
      ),
    );
  }
}

8. 结束

至此,主题配置就完成了。祝大家玩得开心。