如何在Flutter中不使用MediaQuery获取亮度?

13

我的目标是创建一个应用程序,用户可以选择自己喜欢的主题。我使用共享首选项保存用户的选择,以便在下次启动应用程序时加载它。用户可以选择以下任一选项: - 暗模式(独立于操作系统设置) - 亮模式(独立于操作系统设置) - 系统(根据操作系统设置在暗模式和亮模式之间切换) 借助BLoC的帮助,我几乎实现了自己的想法。但问题在于,我需要在我的Bloc事件中传递亮度。为了获取系统(操作系统)亮度,我需要使用

MediaQuery.of(context).platformBrightness

但是 Bloc 在 MaterialApp 之前启动,因此 MediaQuery 不可用。我可以稍后传递亮度(从 MaterialApp 的子小部件中),但是如果用户已经激活了深色模式,那么它将从明亮到暗,但对于用户来说显然很短的时间内可见(因为在 InitialState 中我传递了浅色模式)。

class MyApp extends StatelessWidget {
  final RecipeRepository recipeRepository;

  MyApp({Key key, @required this.recipeRepository})
      : assert(recipeRepository != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider<ThemeBloc>(create: (context) =>
        ThemeBloc(),),

      ],
      child: BlocBuilder<ThemeBloc, ThemeState>(
        builder: (context, state){

          return MaterialApp(
            theme: state.themeData,
            title: 'Flutter Weather',
            localizationsDelegates: [
              FlutterI18nDelegate(fallbackFile: 'en',),
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate
            ],
            supportedLocales: [
              const Locale("en"),
              const Locale("de"),
            ],
            home: Home(recipeRepository: recipeRepository),
          );
        },
      ),
    );
  }
}

主题模块:

class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
  @override
  ThemeState get initialState =>
      ThemeState(themeData: appThemeData[AppTheme.Bright]);

  @override
  Stream<ThemeState> mapEventToState(
    ThemeEvent event,
  ) async* {
    if (event is LoadLastTheme) {
      ThemeData themeData = await _loadLastTheme(event.brightness);
      yield ThemeState(themeData: themeData);
    }
    if (event is ThemeChanged) {
      await _saveAppTheme(event.theme);
      yield ThemeState(themeData: appThemeData[event.theme]);
    }
  }

  Future<ThemeData> _loadLastTheme(Brightness brightness) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    String themeString = prefs.getString(SharedPrefKeys.appThemeKey);
    print("saved theme: $themeString");
    if ((prefs.getString(SharedPrefKeys.appThemeKey) != null) &&
        themeString != "AppTheme.System") {
      switch (themeString) {
        case "AppTheme.Bright":
          {
            return appThemeData[AppTheme.Bright];
          }
          break;

        ///Selected dark mode
        case "AppTheme.Dark":
          {
            return appThemeData[AppTheme.Dark];
          }
          break;
      }
    }

    print("brightness: $brightness");
    if (brightness == Brightness.dark) {
      return appThemeData[AppTheme.Dark];
    } else {
      return appThemeData[AppTheme.Bright];
    }

  }

  Future<void> _saveAppTheme(AppTheme appTheme) async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString(SharedPrefKeys.appThemeKey, appTheme.toString());
  }
}
4个回答

12

如果您非常必须像这样做,您可以通过从低级窗口对象直接获取MediaQuery数据来实现:

final brightness = MediaQueryData.fromWindow(WidgetsBinding.instance.window).platformBrightness;

然而,我强烈建议您考虑一下,如果您需要从内访问MediaQuery,则应将提供程序移到在实例化MaterialApp后以便您可以正常访问MediaQuery


可以了!但是如果我的ThemeBloc在MaterialApp之后实例化,我如何更改整个应用程序的主题? - Just A Question
@JustAQuestion 这取决于您在应用程序中导航到不同页面的方式。最简单的方法是使用 onGenerateRoute 将导航到的每个页面包装在 BlocProvider 中,并在类级别保留对 bloc 的规范引用,而不是与提供程序一起创建它,这样您就不会一遍又一遍地重新创建 bloc。 - Abion47
好的,谢谢,我会尝试。还有一个问题,为什么你不建议使用低级窗口对象? - Just A Question
@JustAQuestion 如果您直接从低级对象中提取数据,则应用程序将不会收到任何系统更改的通知。例如,如果您的应用程序设置为遵守系统主题,并且用户进入首选项并更改系统主题,则您的应用程序不会对更改做出反应。 MediaQuery小部件确实会监听系统更改,因此如果您使用MediaQuery.of,则您的应用程序将自动重建以反映对系统主题的更改。 - Abion47

10

从Flutter 3.10开始,此答案已被弃用,您应该使用这个。

 WidgetsBinding.instance.platformDispatcher.platformBrightness

1
这个答案必须被“接受”。 - Alexey

4
升级到flutter 3.7后,现在有两个选项:
final mode = PlatformDispatcher.instance.platformBrightness;

// With context
final mode = View.of(context).platformDispatcher.platformBrightness;

旧的、已弃用的选项
final mode = SchedulerBinding.instance.window.platformBrightness;

错误: 信息:'window'已被弃用,不应再使用。通过View.of(context)从上下文中查找当前的FlutterView或直接咨询PlatformDispatcher来代替。此功能在v3.7.0-32.0.pre之后被弃用。(在[appname] lib/src/settings/settings.dart:88处的deprecated_member_use)

0

MediaQueryData.fromWindow已被弃用,不应再使用。您可以改用View.of(context)

MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.indigo,
        useMaterial3: true,
        brightness: View.of(context).platformDispatcher.platformBrightness,
     ),
);


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接