【问题标题】:How to force Flutter to rebuild / redraw all widgets?如何强制 Flutter 重建/重绘所有小部件?
【发布时间】:2017-10-02 08:58:17
【问题描述】:

有没有办法强制 Flutter 重绘所有小部件(例如在更改语言环境后)?

【问题讨论】:

    标签: dart flutter


    【解决方案1】:

    您的Widget 应该有一个setState() 方法,每次调用此方法时,都会重绘小部件。

    Documentation : Widget setState()

    【讨论】:

    • 你是说我应该替换顶级小部件(根)的状态?如果我无权访问该小部件怎么办。我想告诉 Flutter 本质上重启。
    • 你想重新加载整个应用程序?
    • 我想强制重新绘制应用程序(包括视图堆栈中更深的视图)(如标题所示)。从我将触发这个的地方我无法访问根小部件。
    • 你的意思是如果我有 2 个 setState,那么小部件将重建两次?
    • @SamerAlkhatib,如果放在系列中,我会回答是的......
    【解决方案2】:

    这种类型的用例,您有孩子可以读取的数据,但您不想将数据显式传递给所有孩子的构造函数参数,通常需要InheritedWidget。 Flutter 将自动跟踪哪些小部件依赖于数据并重建树中已更改的部分。有一个 LocaleQuery 小部件旨在处理区域设置更改,您可以在 Stocks example app 中查看它是如何使用的。

    简而言之,这就是 Stocks 正在做的事情:

    • 在根窗口小部件(在本例中为StocksApp)设置回调以处理区域设置更改。这个回调做了一些工作,然后返回一个自定义的 LocaleQueryData 实例
    • 将此回调注册为onLocaleChanged 参数到MaterialApp 构造函数
    • 需要语言环境信息的子小部件使用LocaleQuery.of(context)
    • 当语言环境发生变化时,Flutter 仅重绘依赖于语言环境数据的小部件。

    如果您想跟踪区域设置更改以外的其他内容,您可以创建自己的扩展 InheritedWidget 的类,并将其包含在应用程序根附近的层次结构中。它的父级应该是一个StatefulWidget,其键设置为一个可供子级访问的GlobalKeyStatefulWidgetState 应该拥有您要分发的数据并公开更改数据的方法,即调用 setState。如果子小部件想要更改State 的数据,他们可以使用全局键来获取指向State (key.currentState) 的指针并在其上调用方法。如果他们想读取数据,他们可以调用 InheritedWidget 子类的静态 of(context) 方法,这将告诉 Flutter 每当您的 State 调用 setState 时,这些小部件都需要重建。

    【讨论】:

    • 哇,看了三遍,又看了三遍 Flutter Gallery 中的 Shrine 示例代码,还是有一个模糊的认识。试试这个。
    • 那个例子还在 Stock 例子中吗?
    • 您好 Collin,由于 localeQuery 小部件已被删除,您可能需要更新您的答案。详情见github.com/flutter/flutter/issues/1269
    • 为什么不只拥有Flutter.redrawAllWidgetsBecauseISaidSo();
    【解决方案3】:

    老问题,但这里是解决方案:

    在您的build 方法中,调用rebuildAllChildren 函数并将context 传递给它:

    @override
    Widget build(BuildContext context) { 
      rebuildAllChildren(context);
      return ...
    }
    
    void rebuildAllChildren(BuildContext context) {
      void rebuild(Element el) {
        el.markNeedsBuild();
        el.visitChildren(rebuild);
      }
      (context as Element).visitChildren(rebuild);
    }
    

    这将访问所有孩子并将他们标记为需要重建。 如果您将此代码放在小部件树中最顶层的小部件中,它将重新构建所有内容。

    还请注意,您必须订购该特定小部件来重建。您也可以使用一些布尔值,以便该小部件的重建仅在您真正需要时重建其所有子级(当然,这是一项昂贵的操作)。


    重要提示:这是一种 hack,只有在您知道自己在做什么并且有充分理由这样做的情况下才应该这样做。一个必要的例子是在我的国际化包中:i18_extension。正如科林杰克逊在他的回答中解释的那样,一般来说你真的不应该这样做。

    【讨论】:

    • 当我在底页中使用 table_calendar 小部件时,这仍然可以强制重建。不知何故,setState 不够强大,无法做到这一点
    • @Dika 您可能在打开底页的小部件中调用 setState 。底页位于叠加层中,而不是在打开它的小部件内。所以无法重建。如果您在 table_calendar 状态或 bottomsheet 状态中调用 setState,它可能会起作用。
    【解决方案4】:

    我在这篇文章中解释了如何创建一个自定义的“AppBuilder”小部件。

    https://hillelcoren.com/2018/08/15/flutter-how-to-rebuild-the-entire-app-to-change-the-theme-or-locale/

    您可以通过用它包装您的 MaterialApp 来使用该小部件,例如:

    Widget build(BuildContext context) {
      return AppBuilder(builder: (context) {
        return MaterialApp(
          ...
        );
      });
    }
    

    您可以使用以下命令告诉应用重新构建:

    AppBuilder.of(context).rebuild();
    

    【讨论】:

    • 'ancestorStateOfType' 已弃用,不应使用。
    • 您可以将该行替换为context.findAncestorStateOfType<AppBuilderState>()
    【解决方案5】:

    刷新整个小部件树可能会很昂贵,而且当您在用户眼前执行时,这看起来并不好。

    为此,flutter 有 ValueListenableBuilder<T> class。它允许您仅重建某些必要的小部件并跳过昂贵的小部件。

    你可以在这里查看文档ValueListenableBuilder flutter docs
    或者只是下面的示例代码:

      return Scaffold(
      appBar: AppBar(
        title: Text(widget.title)
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            ValueListenableBuilder(
              builder: (BuildContext context, int value, Widget child) {
                // This builder will only get called when the _counter
                // is updated.
                return Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    Text('$value'),
                    child,
                  ],
                );
              },
              valueListenable: _counter,
              // The child parameter is most helpful if the child is
              // expensive to build and does not depend on the value from
              // the notifier.
              child: goodJob,
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.plus_one),
        onPressed: () => _counter.value += 1,
      ),
    );
    

    也永远不要忘记setState(() {});的力量

    【讨论】:

    【解决方案6】:

    简单使用:

    Navigator.popAndPushNamed(context,'/screenname');
    

    当你需要刷新时:)

    【讨论】:

      【解决方案7】:

      可能对您的用例有用的是使用导航器重新加载页面。在我的应用程序中切换“真实”和“演示”模式时,我会这样做。这是一个例子:

      Navigator.of(context).push(
          new MaterialPageRoute(
              builder: (BuildContext context){
                return new SplashPage();
              }
          )
      );
      

      您可以将上述代码中的“new SplashPage()”替换为您想要重新加载的任何主小部件(或屏幕)。可以从您可以访问 BuildContext 的任何地方(这是 UI 中的大多数位置)调用此代码。

      【讨论】:

      • 它只是创建新的“屏幕”,而不是重建现有的。
      • 它只是添加了另一个页面
      【解决方案8】:

      使用 AppBuilder 小部件制作重绘小部件

      import 'package:flutter/material.dart';
      import 'package:flutter_redux/flutter_redux.dart';
      import 'package:redux/redux.dart';
      
      void main() {
        final store =
            Store<AppState>(reducer, initialState: AppState(enableDarkMode: false));
      
        runApp(MyApp(store: store));
      }
      
      class MyApp extends StatefulWidget {
        const MyApp({Key key, this.store}) : super(key: key);
      
        final Store<AppState> store;
      
        @override
        _MyAppState createState() => _MyAppState();
      }
      
      class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
        @override
        Widget build(BuildContext context) {
          final store = widget.store;
          return StoreProvider<AppState>(
            store: store,
            child: AppBuilder(
              builder: (BuildContext context) {
                return MaterialApp(
                  theme: ThemeData(
                      brightness: store.state.enableDarkMode
                          ? Brightness.dark
                          : Brightness.light),
                  home: Scaffold(
                    appBar: AppBar(
                      title: Text('Test Redux App'),
                    ),
                    body: SettingsView(),
                  ),
                );
              },
            ),
          );
        }
      }
      
      class SettingsView extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return StoreBuilder(builder: (BuildContext context, Store<AppState> store) {
            final state = store.state;
            return SwitchListTile(
              title: Text('Dark Mode'),
              value: state.enableDarkMode,
              onChanged: (value) {
                store.dispatch(UpdateDarkMode(enable: !state.enableDarkMode));
                AppBuilder.of(context).rebuild();
              },
              secondary: Icon(Icons.settings),
            );
          });
        }
      }
      
      // Redux Claaes
      class AppState {
        AppState({this.enableDarkMode});
      
        bool enableDarkMode;
      }
      
      class UpdateDarkMode {
        UpdateDarkMode({this.enable});
      
        final bool enable;
      }
      
      AppState reducer(AppState state, dynamic action) {
        if (action is UpdateDarkMode) {
          return AppState(
            enableDarkMode: action.enable,
          );
        }
        return state;
      }
      
      // App Builder
      class AppBuilder extends StatefulWidget {
        const AppBuilder({Key key, this.builder}) : super(key: key);
        final Function(BuildContext) builder;
      
        @override
        AppBuilderState createState() => new AppBuilderState();
      
        static AppBuilderState of(BuildContext context) {
          return context.ancestorStateOfType(const TypeMatcher<AppBuilderState>());
        }
      }
      
      class AppBuilderState extends State<AppBuilder> {
        @override
        Widget build(BuildContext context) {
          return widget.builder(context);
        }
      
        void rebuild() {
          setState(() {});
        }
      }
      

      【讨论】:

        【解决方案9】:

        简单使用:

        Navigator.popAndPushNamed(context,'/xxx');
        

        【讨论】:

          【解决方案10】:

          为什么不直接使用 Flutter.redrawAllWidgetsBecauseISaidSo();? – 蒂姆辛

          有点: 更改为重绘有状态子小部件的键。

          Jelena Lecic explained it good enough for me on medium.

          import 'package:flutter/material.dart';
          
          void main() {
            runApp(MyApp());
          }
          
          class MyApp extends StatelessWidget {
            @override
            Widget build(BuildContext context) {
              return MaterialApp(
                title: 'Flutter Demo',
                theme: ThemeData(
                  primarySwatch: Colors.blue,
                ),
                home: MyHomePage(),
              );
            }
          }
          
          class MyHomePage extends StatefulWidget {
            MyHomePage({Key? key}) : super(key: key);
            @override
            _MyHomePageState createState() => _MyHomePageState();
          }
          
          class _MyHomePageState extends State<MyHomePage> {
            int _counter = 0;
            var _forceRedraw; // generate the key from this
          
            void _incrementCounter() {
              setState(() {
                _counter++;
                _forceRedraw = Object();
              });
            }
          
            @override
            void initState() {
              _forceRedraw = Object();
              super.initState();
            }
          
            @override
            Widget build(BuildContext context) {
              return Scaffold(
                body: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      MyStatefullTextWidget(
                        key: ValueKey(_forceRedraw),
                        counter: _counter,
                      ),
                      Text(
                        '$_counter',
                        style: Theme.of(context).textTheme.headline4,
                      ),
                    ],
                  ),
                ),
                floatingActionButton: FloatingActionButton(
                  onPressed: _incrementCounter,
                  tooltip: 'Increment',
                  child: Icon(Icons.add),
                ),
              );
            }
          }
          
          class MyStatefullTextWidget extends StatefulWidget {
            final int counter;
            const MyStatefullTextWidget({
              required this.counter,
              Key? key,
            }) : super(key: key);
          
            @override
            _MyStatefullTextWidgetState createState() => _MyStatefullTextWidgetState();
          }
          
          class _MyStatefullTextWidgetState extends State<MyStatefullTextWidget> {
            @override
            Widget build(BuildContext context) {
              return Text(
                'You have pushed the button this many times:${widget.counter}',
              );
            }
          }
          
          
          

          【讨论】:

          • 那篇文章在付费墙后面。请解释你的代码。
          • @IanRajkumar 尝试删除浏览器中用于中等页面的所有 cookie。我感觉到你的痛苦。
          【解决方案11】:

          如果您正在创建一个多语言应用程序,我建议您使用本地化库来处理它。甚至使用其他应用程序使用的约定。

          flutter_i18n - 它还有一种在运行时重新生成的方法 - 记录在底部

          await FlutterI18n.refresh(buildContext, languageCode, {countryCode});
          

          它的作用是生成具有状态的小部件,该状态由处理动态数据和可见性的组合单条资源管理。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-10-29
            • 1970-01-01
            • 1970-01-01
            • 2020-07-25
            • 1970-01-01
            • 2017-09-10
            • 1970-01-01
            • 2019-08-23
            相关资源
            最近更新 更多