当前位置:

首页 > 编程开发 > flutter中如何使用和扩展ThemeData实现详解

flutter中如何使用和扩展ThemeData实现详解

本文目录

    Flutter使用ThemeData统一管理颜色、字体等主题配置,在MaterialApp的theme属性中注册,子控件通过Theme.of(context)读取。可通过extension扩展ThemeData添加自定义字段,再利用Provider封装ThemeData并通知主题变更,实现一键换肤功能。

    前言

    做过UI开发的朋友应该都有体会:我们通常会把颜色、字体大小这类配置单独抽出来,放在一个统一的管理类里,而不是直接写在UI代码中。这样后续调整时,改个配置文件就行,不用动源码。

    举个例子:

    • 定义常量存放
    /// 存放颜色常量
    abstract class ColorConfigs {
      static const Color background = Color(0xFFFF6600);
      static const Color textHint = Color(0xFFA0A4A7);
    }
    • 通过统一获取
    /// GOOD
    Container(
      //通过 ColorConfig 获取色值
      color: ColorConfigs.background,
    )
    /// BAD
    Container(
      //写死
      color: Color(0xFFFF6600),
    )

    Flutter 自带的 Theme 类,能帮我们省掉手动封装常量配置类的步骤。把色值、字体风格等存入 ThemeData,子控件统一通过 Theme.of(context) 读取各项配置。

    这篇通过一个换肤 Demo,聊聊如何在 Flutter 项目中使用 Theme,以及如何对 ThemeData 进行字段扩展,实现全局主题配置管理。

    Theme 的基本使用方式

    1. Theme 的注册

    MaterialApp(
      theme: myThemeData, //一个ThemeData的实例,下面提供具体代码
      home: BodyWidget(),
    )

    做全局主题配置,只需要在 MaterialApp 的 theme 字段传入一个 ThemeData 实例。myThemeData 就是实现实例,可以通过 ThemeData 的构造方法查看它支持保存的主题和样式信息,按需赋值即可。

    下面是一份实际项目中用到的 ThemeData 配置,定义了各种状态颜色、字体样式,供参考:

    myThemeData:

    val myThemeData = ThemeData(
      primaryColor: Colors.white,
      disabledColor: const Color(0xffcbced0),
      backgroundColor: const Color(0xfff3f4f5),
      hintColor: const Color(0xffe2e5e7),
      errorColor: const Color(0xffe21a1a),
      highlightColor: const Color(0xffa7d500),
      shadowColor: const Color(0xffa0a4a7),
      selectedRowColor: const Color(0xfff3f4f5),
      colorScheme: const ColorScheme.light(
        primary: Colors.white,
        secondary: Color(0xffa7d500),
        background: Color(0xfff3f4f5),
        error: Color(0xffe21a1a),
        onPrimary: Color(0xff242524),
        onError: Colors.white,
        onBackground: Color(0xffe2e5e7),
        onSecondary: Color(0xff707275),
      ),
      textTheme: TextTheme(
        headline1: TextStyle(
          fontSize: 17.sp,
          fontWeight: FontWeight.bold,
          color: const Color(0xff242524),
        ),
        headline2: TextStyle(
          fontSize: 16.sp,
          fontWeight: FontWeight.bold,
          color: const Color(0xff242524),
        ),
        ...中间省略 healin3 ~ headline5,只是配置不一样
        headline6: TextStyle(
          fontSize: 14.sp,
          fontWeight: FontWeight.bold,
          color: const Color(0xff707275),
        ),
        subtitle1: TextStyle(
          fontSize: 12.sp,
          fontWeight: FontWeight.w500,
          color: const Color(0xff242524),
        ),
        subtitle2: TextStyle(
          fontSize: 12.sp,
          fontWeight: FontWeight.w500,
          color: const Color(0xff707275),
        ),
        bodyText1: TextStyle(
          fontSize: 11.sp,
          fontWeight: FontWeight.normal,
          color: const Color(0xff242524),
        ),
        bodyText2: TextStyle(
          fontSize: 11.sp,
          fontWeight: FontWeight.normal,
          color: const Color(0xff242524),
        ),
      ),
    )

    2. 读取 ThemeData 里的配置

    @override
    Widget build(BuildContext context) {
      return Container(
        color: Theme.of(context).backgroundColor,
        child: Text(
            'hellow', 
             style: Theme.of(context).headline).bodyText1,
      );
    }
    • Theme.of(context).backgroundColor:读取背景颜色,在 myThemeData 中已赋值。
    • Theme.of(context).headline).bodyText1:读取键值为 bodyText1 的字体样式。

    小技巧介绍

    为方便阅读,可以用 extension 对 ThemeData 内的属性重命名获取:

    ///用于重命名颜色属性
    extension ThemeDataColorExtension on ThemeData {
      Color get bgColor => colorScheme.onBackground;
      ...
    }
    ///用于重命名字体样式属性
    extension ThemeDataTextStyleExtension on ThemeData {
      TextStyle get bodyStyle => textTheme.bodyText1!;
      ...
    }

    在 UI 页面导入并引用,上面的 demo 可以改为:

    import ./extension_theme.dart
    ...
    @override
    Widget build(BuildContext context) {
      return Container(
        color: Theme.of(context).bgColor,
        child: Text(
            'hellow', 
             style: Theme.of(context).bodyStyle,
      );
    }

    ThemeData 内置字段不够用,如何扩展?

    从 ThemeData 的构造函数可以看到,它内置的字段是有限的。如果 UI 设计包含的色值或字体样式数量超出了 ThemeData 的可设置范围,该怎么办?

    比如想新增一个色值配置,叫 connerColor,并且希望保持统一,全部通过 ThemeData 来读取和配置。怎么处理?

    常见的做法是:将 ThemeData 封装一层,以新增 connerColor 为例。具体代码结合下面的一键换肤一起看。

    如何实现一键换肤

    有了 ThemeData 作为统一的管理配置后,实现一键换肤的思路就很清晰了:

    flutter中如何使用和扩展ThemeData实现详解

    除了 ThemeData 存放配置信息,还需要一个监听类来通知主题变更。这里用 provider 来实现。

    1. 首先在 yaml 新增引入 provider

    dependencies:
      provider: ^6.0.2

    2. 创建主题枚举

    假设提供两种主题切换:

    ///主题类型
    enum ThemeEnum {
      yellow,
      red,
    }

    3. ThemeData 进行一层封装处理

    对 ThemeData 封装一层,添加 connerColor 扩展字段:

    ///自定义模型,包装一下 themeData
    class ThemeItem {
      final ThemeEnum themeEnum;
      final ThemeData themeData;
      // 扩展一个字段,用于表示自定义色值
      final Color connerColor;
      ThemeItem(
        this.themeEnum,
        this.themeData, {
        required this.connerColor,
      });
    }

    4. 创建一个主题管理类 ThemeConfig

    abstract class ThemeConfig {
      ///记录当前选中主题
      static late ThemeItem _currentTheme;
      static ThemeData get currentThemeData => _currentTheme.themeData;
      static ThemeEnum get currentTheme => _currentTheme.themeEnum;
      ///提供获取扩展的色值
      static Color? get connerColor => _currentTheme.connerColor;
      ///设置选中主题,提供外部调用,更换当前主题
      static void initTheme(ThemeItem theme) {
        _currentTheme = theme;
      }
    }

    5. 通过 ThemeData 进行读取保持统一

    为了保持统一,仍然通过 ThemeData 读取,使用 extension 对新增字段 connerColor 进行扩展:

    extension ExTheme on ThemeData {
      ///扩展获取自定义色值
      Color get connerColor => ThemeConfig.connerColor!;
    }

    6. 基于 provider 的使用

    添加一个工具类,用于通知主题变更:

    class AppInfoProvider with ChangeNotifier {
      ThemeData get currentTheme => ThemeConfig.currentThemeData;
      ///切换主题
      setTheme(ThemeItem theme) {
        ThemeConfig.initTheme(theme);
        notifyListeners();
      }
    }

    切换主题时直接调用:

    Provider.of(context, listen: false).setTheme(themeItem);

    7. 创建一个主题仓库,里面存放两套主题

    ///主题仓库
    abstract class ThemeStore {
      static List themes = [
        //红色主题
        ThemeItem(
          ThemeEnum.yellow,
          ThemeData(
            primaryColor: Colors.yellow,
            backgroundColor: Colors.yellow,
          ),
          connerColor: Colors.blue,
        ),
        //黄色主题
        ThemeItem(
          ThemeEnum.red,
          ThemeData(
            primaryColor: Colors.red,
            backgroundColor: Colors.red,
          ),
          connerColor: Colors.green,
        ),
      ];
    }

    8. 完整的 demo 代码及效果

    main.dart

    void main() {
      ///初始化主题
      ThemeConfig.initTheme(
        ThemeStore.themes.first,
      );
      runApp(const Material(
        child: MyApp(),
      ));
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
            ChangeNotifierProvider.value(value: AppInfoProvider()),
          ],
          child: Consumer(
            builder: (context, appInfo, _) {
              return MaterialApp(
                theme: appInfo.currentTheme,
                home: Column(
                  mainAxisSize: MainAxisSize.max,
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: const [
                    BodyWidget(),
                    SizedBox(
                      height: 30,
                    ),
                    _ThemePageButton(),
                  ],
                ),
              );
            },
          ),
        );
      }
    }
    
    class _ThemePageButton extends StatelessWidget {
      const _ThemePageButton({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return TextButton(
          onPressed: () {
            Na vigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const ThemeSetWidget(),
              ),
            );
          },
          child: const Text('打开主题设置页面'),
        );
      }
    }

    body_widget

    class BodyWidget extends StatelessWidget {
      const BodyWidget({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          height: 300,
          width: 300,
          //读取标准色值
          color: Theme.of(context).backgroundColor,
          child: Center(
            child: Container(
              height: 100,
              width: 150,
              //读取自定义色值
              color: Theme.of(context).connerColor,
            ),
          ),
        );
      }
    }

    两个方块:外层方块读取 ThemeData 标准字段色值,内层方块读取扩展字段色值,统一通过 ThemeData 读取。

    theme_set_widget

    extension ExThemeEnum on ThemeEnum {
      Color get value {
        switch (this) {
          case ThemeEnum.yellow:
            return Colors.yellow;
          case ThemeEnum.red:
            return Colors.red;
        }
      }
    }
    ///主题选择页面
    class ThemeSetWidget extends StatelessWidget {
      const ThemeSetWidget({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text("颜色主题"),
            backgroundColor: Theme.of(context).backgroundColor,
          ),
          body: ExpansionTile(
            leading: const Icon(Icons.color_lens),
            title: const Text('颜色主题'),
            initiallyExpanded: true,
            children: [
              Padding(
                padding: const EdgeInsets.only(
                  left: 10,
                  right: 10,
                  bottom: 10,
                ),
                child: Wrap(
                  spacing: 8,
                  runSpacing: 8,
                  children: ThemeStore.themes
                      .map((e) => _createItemWidget(context, e))
                      .toList(),
                ),
              )
            ],
          ),
        );
      }
    
      Widget _createItemWidget(
        BuildContext context,
        ThemeItem theme,
      ) {
        return InkWell(
          onTap: () {
            Provider.of(context, listen: false).setTheme(theme);
          },
          child: Container(
            width: 40,
            height: 40,
            color: theme.themeEnum.value,
            child: ThemeConfig.currentTheme == theme.themeEnum
                ? const Icon(
                    Icons.done,
                    color: Colors.white,
                  )
                : null,
          ),
        );
      }
    }

    flutter中如何使用和扩展ThemeData实现详解

    本文内容来源于网友投稿,如有侵权请联系删除。
    作者最新文章
    编程开发
    相关文章 更多
    PHP递归性能优化技巧与迭代替代方案
    PHP递归性能优化技巧与迭代替代方案

    解析PHP递归函数在树形数据处理中的性能瓶颈,提供预加载数据消除I/O、使用显式栈替代深层递归的实战方案,帮助开发者在代码可读性与执行效率间做出合理取舍。

    Java测试中怎么使用Mockito模拟依赖对象
    Java测试中怎么使用Mockito模拟依赖对象

    详细讲解在Java单元测试中如何使用Mockito模拟依赖对象,包括引入依赖、创建Mock、打桩返回值、行为验证以及Mock与Spy的核心差异和常见陷阱排查。

    链表删除节点的时间复杂度是多少及其详细分析
    链表删除节点的时间复杂度是多少及其详细分析

    详细分析链表删除节点的时间复杂度,深入探讨单链表与双向链表在不同已知前提下的查找与删除开销,并结合完整代码与清晰图解进行对比总结。

    codex如何配置模型参数及文件设置教程
    codex如何配置模型参数及文件设置教程

    想知道如何让AI写出的代码更贴合你的习惯?本文手把手教你在VS Code中调整Codex相关模型参数,通过修改配置文件优化温度值和令牌限制,解决代码建议不准确或响应慢的问题。

    Claude Code AI编程工具实力揭秘与编程助手实测
    Claude Code AI编程工具实力揭秘与编程助手实测

    通过实测展示Claude Code在终端中如何理解自然语言指令、自动修改代码文件并处理复杂编程任务,帮助开发者评估其实际辅助能力。

    winforms教程自学入门与基础开发步骤详解
    winforms教程自学入门与基础开发步骤详解

    本教程详细讲解如何使用Visual Studio创建WinForms项目,通过添加按钮和标签控件并编写点击事件代码,实现一个基础的计数器功能,适合C#初学者快速上手Windows窗体应用开发。

    Cursor自动补全设置教程教你快速开启代码补全功能
    Cursor自动补全设置教程教你快速开启代码补全功能

    详解Cursor编辑器中自动补全功能的开启与优化设置,涵盖Tab触发机制、上下文窗口调整及模型切换,帮助开发者解决补全延迟、干扰大等问题,提升编码流畅度。

    pandas的数据格式怎么转换和设置方法教程
    pandas的数据格式怎么转换和设置方法教程

    详解Pandas中数据格式转换的核心方法,包括astype强制转换、to_numeric容错处理及日期解析技巧,解决常见类型错误并提升数据处理效率。

    VS Code中文设置方法 简体语言包安装与切换教程
    VS Code中文设置方法 简体语言包安装与切换教程

    详细介绍在Visual Studio Code中安装Chinese (Simplified)语言包的方法,包括通过扩展市场搜索、安装及自动重启切换至简体中文界面的完整步骤,帮助开发者快速将编辑器本地化。

    cursor安装过程无法更改安装位置的解决方法
    cursor安装过程无法更改安装位置的解决方法

    针对Cursor安装包默认锁定C盘且无路径选择界面的问题,提供通过手动移动文件并创建目录联结(Symbolic Link)的解决方案,实现将软件安装在其他磁盘分区。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    macOS软件
    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    Mac软件 更多
    photoshop
    photoshop
    Windows、macOS 、 iPad

    Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

    Blender
    Blender
    Windows、macOS 和 Linux

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

    灵活计算器
    灵活计算器
    macOS/iOS/Android

    灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

    WINDOWS 更多
    3dmax(3ds max)
    3dmax(3ds max)
    Windows

    Autodesk 3ds Max 是一款专业的三维建模、动画与渲染软件,广泛应用于建筑可视化、游戏开发、影视动画、广告设计和产品展示等领域。

    photoshop
    photoshop
    Windows、macOS 、 iPad

    Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

    Blender
    Blender
    Windows、macOS 和 Linux

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。