【发布时间】:2017-10-02 08:58:17
【问题描述】:
有没有办法强制 Flutter 重绘所有小部件(例如在更改语言环境后)?
【问题讨论】:
有没有办法强制 Flutter 重绘所有小部件(例如在更改语言环境后)?
【问题讨论】:
您的Widget 应该有一个setState() 方法,每次调用此方法时,都会重绘小部件。
【讨论】:
这种类型的用例,您有孩子可以读取的数据,但您不想将数据显式传递给所有孩子的构造函数参数,通常需要InheritedWidget。 Flutter 将自动跟踪哪些小部件依赖于数据并重建树中已更改的部分。有一个 LocaleQuery 小部件旨在处理区域设置更改,您可以在 Stocks example app 中查看它是如何使用的。
简而言之,这就是 Stocks 正在做的事情:
StocksApp)设置回调以处理区域设置更改。这个回调做了一些工作,然后返回一个自定义的 LocaleQueryData 实例
onLocaleChanged 参数到MaterialApp 构造函数LocaleQuery.of(context)。如果您想跟踪区域设置更改以外的其他内容,您可以创建自己的扩展 InheritedWidget 的类,并将其包含在应用程序根附近的层次结构中。它的父级应该是一个StatefulWidget,其键设置为一个可供子级访问的GlobalKey。 StatefulWidget 的 State 应该拥有您要分发的数据并公开更改数据的方法,即调用 setState。如果子小部件想要更改State 的数据,他们可以使用全局键来获取指向State (key.currentState) 的指针并在其上调用方法。如果他们想读取数据,他们可以调用 InheritedWidget 子类的静态 of(context) 方法,这将告诉 Flutter 每当您的 State 调用 setState 时,这些小部件都需要重建。
【讨论】:
Flutter.redrawAllWidgetsBecauseISaidSo();?
老问题,但这里是解决方案:
在您的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。正如科林杰克逊在他的回答中解释的那样,一般来说你真的不应该这样做。
【讨论】:
我在这篇文章中解释了如何创建一个自定义的“AppBuilder”小部件。
您可以通过用它包装您的 MaterialApp 来使用该小部件,例如:
Widget build(BuildContext context) {
return AppBuilder(builder: (context) {
return MaterialApp(
...
);
});
}
您可以使用以下命令告诉应用重新构建:
AppBuilder.of(context).rebuild();
【讨论】:
context.findAncestorStateOfType<AppBuilderState>()
刷新整个小部件树可能会很昂贵,而且当您在用户眼前执行时,这看起来并不好。
为此,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(() {});的力量
【讨论】:
简单使用:
Navigator.popAndPushNamed(context,'/screenname');
当你需要刷新时:)
【讨论】:
可能对您的用例有用的是使用导航器重新加载页面。在我的应用程序中切换“真实”和“演示”模式时,我会这样做。这是一个例子:
Navigator.of(context).push(
new MaterialPageRoute(
builder: (BuildContext context){
return new SplashPage();
}
)
);
您可以将上述代码中的“new SplashPage()”替换为您想要重新加载的任何主小部件(或屏幕)。可以从您可以访问 BuildContext 的任何地方(这是 UI 中的大多数位置)调用此代码。
【讨论】:
使用 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(() {});
}
}
【讨论】:
简单使用:
Navigator.popAndPushNamed(context,'/xxx');
【讨论】:
为什么不直接使用 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}',
);
}
}
【讨论】:
如果您正在创建一个多语言应用程序,我建议您使用本地化库来处理它。甚至使用其他应用程序使用的约定。
flutter_i18n - 它还有一种在运行时重新生成的方法 - 记录在底部
await FlutterI18n.refresh(buildContext, languageCode, {countryCode});
它的作用是生成具有状态的小部件,该状态由处理动态数据和可见性的组合单条资源管理。
【讨论】: