【问题标题】:Is it possible "extend" ThemeData in Flutter是否可以在 Flutter 中“扩展” ThemeData
【发布时间】:2018-08-16 18:50:12
【问题描述】:

我很可能会错过一些东西,因为我对 Flutter 很陌生,但我发现 ThemeData 的选项非常有限(至少在我理解如何实现它的情况下)。

如果您从 MaterialUp 看下面这个随机设计,我想粗略地建模一些东西:

Themedata.cyclingColor = Color.pink; ThemeData.runningColor = Color.green;

这样,我可以在我的应用程序的任何地方引用自行车、跑步、游泳、健身房的颜色(或任何在我的应用程序/设计上下文中有意义的颜色)并保持一致。

目前在 Flutter 中是否有推荐的方法来实现这一点?我有哪些选择?

【问题讨论】:

标签: dart flutter


【解决方案1】:

我推荐这种方法,它很简单,适用于热重载,并且可以轻松扩展以支持在深色和浅色主题之间切换。

首先创建您自己的ThemeData 模拟,我们称之为AppThemeData

class AppThemeData {
  final BorderRadius borderRadius = BorderRadius.circular(8);

  final Color colorYellow = Color(0xffffff00);
  final Color colorPrimary = Color(0xffabcdef);

  ThemeData get materialTheme {
    return ThemeData(
        primaryColor: colorPrimary
    );
  }
}

只要需要标准的ThemeData,就可以使用materialTheme

然后创建一个名为AppTheme 的小部件,它使用provider 包提供AppThemeData 的实例。

class AppTheme extends StatelessWidget {
  final Widget child;

  AppTheme({this.child});

  @override
  Widget build(BuildContext context) {
    final themeData = AppThemeData(context);
    return Provider.value(value: themeData, child: child);
  }
}

最后,用AppTheme 封装整个应用程序。要访问主题,您可以致电context.watch<AppThemeData>()。或者创建这个扩展...

extension BuildContextExtension on BuildContext {
  AppThemeData get appTheme {
    return watch<AppThemeData>();
  }
}

...并使用context.appTheme。我通常把final theme = context.appTheme;放在小部件构建方法的第一行。

【讨论】:

  • 这很漂亮,我喜欢提供者的用法。彻底解决了我的问题!
  • 等等,每次调用materialTheme都会生成ThemeData的新实例。
  • @AlexSemeniuk 是的,但实际上它只调用一次。
  • @DanielDodd,你是如何解决上下文问题的?
  • 是的,我还没有运行它并且是 Flutter 初学者,但我不明白 AppThemeData(context) 是如何工作的,因为没有定义接受 context 的构造函数?
【解决方案2】:

针对 null 安全性进行了更新

我扩展了标准的ThemeData 类,以便随时可以访问自己的主题字段:

Theme.of(context).own().errorShade

或者这样:

ownTheme(context).errorShade

可以使用以下新字段定义和扩展主题(通过在某个ThemeData 实例上调用addOwn()):

final ThemeData lightTheme = ThemeData.light().copyWith(
    accentColor: Colors.grey.withAlpha(128),
    backgroundColor: Color.fromARGB(255, 255, 255, 255),
    textTheme: TextTheme(
      caption: TextStyle(
          fontSize: 17.0, fontFamily: 'Montserrat', color: Colors.black),
    ))
  ..addOwn(OwnThemeFields(
      errorShade: Color.fromARGB(240, 255, 200, 200),
      textBaloon: Color.fromARGB(240, 255, 200, 200)));

final ThemeData darkTheme = ThemeData.dark().copyWith( ...
...

主题可以以常规方式应用于MaterialApp 小部件:

MaterialApp(
...
  theme: lightTheme,
  darkTheme: darkTheme,
)

想法是将主题化所需的所有自定义字段放在单独的类OwnThemeFields中。

然后用 2 个方法扩展 ThemeData 类:

  1. addOwn()ThemedData 的某个实例连接到OwnThemeFields 实例
  2. own() 允许查找与给定主题数据关联的自己的字段

还可以创建ownTheme 辅助方法来缩短自己字段的提取时间。

class OwnThemeFields {
  final Color? errorShade;
  final Color? textBaloon;

  const OwnThemeFields({Color? errorShade, Color? textBaloon})
      : this.errorShade = errorShade,
        this.textBaloon = textBaloon;

  factory OwnThemeFields.empty() {
    return OwnThemeFields(errorShade: Colors.black, textBaloon: Colors.black);
  }
}
    
extension ThemeDataExtensions on ThemeData {
  static Map<InputDecorationTheme, OwnThemeFields> _own = {};

  void addOwn(OwnThemeFields own) {
    _own[this.inputDecorationTheme] = own;
  }

  static OwnThemeFields? empty = null;

  OwnThemeFields own() {
    var o = _own[this.inputDecorationTheme];
    if (o == null) {
      if (empty == null) empty = OwnThemeFields.empty();
      o = empty;
    }
    return o!;
  }
}

OwnThemeFields ownTheme(BuildContext context) => Theme.of(context).own();

完整来源:https://github.com/maxim-saplin/dikt/blob/master/lib/ui/themes.dart

【讨论】:

  • 很好的解决方案!
  • 它不会在应用中切换主题时改变颜色,除非你热重载
  • @Maxim 我喜欢你的解决方案,但唯一的问题是可以说我有 2 个不同的主题数据变量,每个变量都有自己的 '..addOwn' 函数。当我在应用程序中更改主题时,OwnThemeFields 不会更改,而是保持固定颜色。请问您知道解决方法吗?通过共享屏幕对我来说会更容易解释
  • 由于 ThemedData 是不可变的,因此您无法更改给定实例中的任何内容。我假设您使用 copyWith() 克隆它并丢失以前设置的 Own Fields。由于字段与 ThemedData 的某个实例紧密耦合,您可以尝试在 copyWith() 之后立即调用 ownFields() 以将字段绑定到新的 TD 实例。
  • 智能解决方案。但需要修改为 Null 安全
【解决方案3】:

您不能扩展ThemeData,因为这样材料组件就找不到它了。

除了 Flutter 中包含的 ThemeData 之外,您还可以以同样的方式创建和提供 MyThemeData

创建一个扩展 InheritedWidget 的小部件 CustomThemeWidget,并在那里提供您的自定义主题。

当您想从当前主题中获取值时使用

myTheme = CustomThemeWidget.of(context).myTheme;

要更改当前主题,请更改 MyThemeData 中的 CustomThemeWidget.myTheme

更新

https://github.com/flutter/flutter/pull/14793/files 所示,应该可以扩展ThemeData 并通过覆盖runtimeType 将其提供为ThemeData

另见https://github.com/flutter/flutter/issues/16487#event-1573761656中的评论

【讨论】:

  • 有趣的想法。我们如何以另一种类型作为键将自定义类型添加到该映射中?
  • 我想我现在明白了。您想要创建一个支持该用例的自定义 InheritedWidget。听起来是个好主意。
  • 听起来很有希望:)
  • 对不起,我得准备开会了
  • 你能解释一下更新吗?从评论github.com/flutter/flutter/issues/16487#event-1573761656 这就是我要找的。但不明白您的更新有何帮助?
【解决方案4】:

Dart 2.7 以后,扩展支持

你可以为系统类添加扩展

只添加实例属性很容易,但如果你会得到动态颜色

你需要考虑一下。例如,使用常量来获取明暗模式下的颜色

判断是否为暗模式

两种方式

  • MediaQuery.of(context).platformBrightnes == Brightness.dark;
  • Theme.of(context).brightness == Brightness.dark;

As you can see, you need the context, the context

为 BuildContext 添加扩展

这里是代码

extension MYContext on BuildContext {
  Color dynamicColor({int light, int dark}) {
    return (Theme.of(this).brightness == Brightness.light)
        ? Color(light)
        : Color(dark);
  }

  Color dynamicColour({Color light, Color dark}) {
    return (Theme.of(this).brightness == Brightness.light)
        ? light
        : dark;
  }

  /// the white background
  Color get bgWhite => dynamicColor(light: 0xFFFFFFFF, dark: 0xFF000000);
}

如何使用

import 'package:flutter/material.dart';
import 'buildcontext_extension.dart';

class Test extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: context.bgWhite,
    );
  }
}

还有

这个颜色可能需要多个文件,所以你可以创建一个public.dart文件来管理它

Like This

public.dart


library public;

// Export some common header files

// extensions
export 'buildcontext_extension.dart';

暗模式图像支持

将浅色图像与深色图像归为同一类别

some code

static String getImgPath(String name, {
    String folder = '', 
    String format = 'png', 
    bool isDark = false, 
    bool needDark = true
  }) {
    String finalImagePath;
    if (needDark) {
      final folderName = isDark ? '${folder}_dark' : folder;
      finalImagePath = 'assets/images/$folderName/$name.$format';
    } else {
      finalImagePath = 'assets/images/$folder/$name.$format';
    }
    String isDarkPath = isDark ? "? DarkMode" : "? LightMode";
    print('$isDarkPath imagePath ? $finalImagePath');
    return finalImagePath;
  }

【讨论】:

    【解决方案5】:

    我还发现ThemeData 有限制。我所做的以及将来为我的所有应用所做的就是创建我自己的ThemeData

    我创建了一个名为color_themes.dart 的文件,并创建了一个名为ColorThemesclass,其构造函数具有我想要的颜色名称。比如cyclingColor

    class ColorThemes {
        const static cyclingColor = const Color(0xffb74093); 
    }
    

    然后您可以通过导入文件并调用ColorThemes.cyclingColor 来调用这些颜色。您可以在ThemeData 中分配这些值,以使这些颜色默认为您的ColorThemes。使用此方法的好处之一是您不需要像 ThemeData.of(context) 那样使用/引用 context,这使得在提取的小部件中使用代码变得更加容易。

    【讨论】:

    • 在应用运行时切换主题怎么样?
    • 然后,您避免了多重主题的好处,以及黑暗模式。
    【解决方案6】:

    使用这个库adaptive_theme 进行主题切换。 并创建 ColorSheme 的扩展

    extension MenuColorScheme on ColorScheme {
          Color get menuBackground => brightness == Brightness.light
              ? InlLightColors.White
              : InlDarkColors.Black;
    }
    

    在小部件中使用它

    Container(
          color: Theme.of(context).colorScheme.menuBackground,
    ...
    )
    

    这种方式非常简单优雅。很高兴编码。

    【讨论】:

      【解决方案7】:

      我还通过创建这样的CustomThemeData 类为多个主题解决了这个问题:

      class CustomThemeData {
          final double imageSize;
      
          CustomThemeData({
              this.imageSize = 100,
          });
      }
      

      然后,为每个主题创建实例:

      final _customTheme = CustomThemeData(imageSize: 150);
      final _customDarkTheme = CustomThemeData();
      

      并在ThemeData上写一个扩展:

      extension CustomTheme on ThemeData {
          CustomThemeData get custom => brightness == Brightness.dark ? _customDarkTheme : _customTheme;
      }
      

      最后,可以像这样访问值:

      Theme.of(context).custom.imageSize
      

      欲了解更多信息,请参阅:https://bettercoding.dev/flutter/tutorial-themes-riverpod/#custom_attributes_extending_themedata

      【讨论】:

      • 谢谢。只是澄清一下,这是否允许用户可以手动选择多个不同的主题,例如。红色主题,蓝色主题,深色主题,黑色主题?还是只允许明暗主题?
      • 不幸的是,Flutter 仅支持开箱即用的深色和浅色主题(通过使用 themedarkThemeof MaterialApp)。您可以动态设置theme(例如使用StateNotifierProvider),但解决方案会复杂得多。
      【解决方案8】:

      一个简单的解决方法,如果您不使用所有 textTheme 标题,您可以设置其中一些颜色并像通常使用其他颜色一样使用它们。

      设置标题1颜色: ThemeData(textTheme: TextTheme(headline1: TextStyle(color: Colors.red),),),

      使用它: RawMaterialButton(fillColor: Theme.of(context).textTheme.headline1.color,onPressed: onPressed,)

      【讨论】:

        【解决方案9】:

        我创建了一个类似于ThemeData 的实现:

        @override
        Widget build(BuildContext context) {
             final Brightness platformBrightness = Theme.of(context).brightness;
             final bool darkTheme = platformBrightness == Brightness.dark;
        
             return CustomAppTheme(
                       customAppTheme:
                           darkTheme ? CustomAppThemeData.dark : CustomAppThemeData.light,
                       child: Icon(Icons.add, color: CustomAppTheme.of(context).addColor,),
             );
        }
        
        import 'package:calendarflutter/style/custom_app_theme_data.dart';
        import 'package:flutter/material.dart';
        
        class CustomAppTheme extends InheritedWidget {
          CustomAppTheme({
            Key key,
            @required Widget child,
            this.customAppTheme,
          }) : super(key: key, child: child);
        
          final CustomAppThemeData customAppTheme;
        
          static CustomAppThemeData of(BuildContext context) {
            return context
                .dependOnInheritedWidgetOfExactType<CustomAppTheme>()
                .customAppTheme;
          }
        
          @override
          bool updateShouldNotify(CustomAppTheme oldWidget) =>
              customAppTheme != oldWidget.customAppTheme;
        }
        
        import 'package:flutter/material.dart';
        
        class CustomAppThemeData {
          final Color plusColor;
        
          const CustomAppThemeData({
            @required this.plusColor,
          });
        
          static CustomAppThemeData get dark {
            return CustomAppThemeData(
              plusColor: Colors.red,
            );
          }
        
          static CustomAppThemeData get light {
            return CustomAppThemeData(
              plusColor: Colors.green,
            );
          }
        }
        

        【讨论】:

        • 我将在我的博文中详细介绍:www.felixlarsen.com/blog/custom-themedata-flutter-extension
        猜你喜欢
        • 2020-12-22
        • 1970-01-01
        • 1970-01-01
        • 2021-06-24
        • 2010-12-21
        • 1970-01-01
        • 2012-03-12
        • 1970-01-01
        • 2021-11-23
        相关资源
        最近更新 更多