我的应用程序的不同部分需要不同的主题。我的假设是 flutter 会应用它找到的 widget 树中最近的 ThemeData 并应用该主题。但这在导航到新页面时似乎不起作用。
例如,在下面的代码中,我希望 SecondRoute 和 ThirdRoute 都使用 FirstRoute 中提供的 ThemeData。但实际上只有SecondRoute使用了指定的ThemeData,ThirdRoute使用了MyApp中指定的ThemeData。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
/// Default ThemeData specified here:
theme: ThemeData.light().copyWith(
scaffoldBackgroundColor: Colors.lightGreen,
primaryColor: Colors.indigo,
),
home: const FirstRoute());
}
}
class FirstRoute extends StatelessWidget {
const FirstRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("FirstRoute"),
),
body: Center(
child: TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
/// New ThemeData specified here:
Theme(
data: ThemeData.light().copyWith(
scaffoldBackgroundColor: Colors.blue,
primaryColor: Colors.yellow,
),
child: const SecondRoute())),
);
},
child: Text(
"Go To SecondRoute",
style: TextStyle(color: Theme.of(context).primaryColor),
),
),
),
);
}
}
class SecondRoute extends StatelessWidget {
const SecondRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("SecondRoute"),
),
body: Center(
child: TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ThirdRoute()),
);
},
child: Text(
"Go To ThirdRoute",
style: TextStyle(color: Theme.of(context).primaryColor),
),
),
),
);
}
}
class ThirdRoute extends StatelessWidget {
const ThirdRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
"ThirdRoute",
),
),
body: Center(
child: Text(
"Lorem Ipsum",
style: TextStyle(color: Theme.of(context).primaryColor),
),
),
);
}
}
我想要的结果是所有子页面都继承新的主题数据。
对此最好的解决方案是什么?我是否应该使用第二个 MaterialApp 而不是主题小部件,以便其所有子项都继承其主题?或者这样做会产生其他问题吗?我是否应该在每个子页面中包含一个主题小部件并为每个子页面定义主题数据?还有别的吗?
为了确保子页面继承 Flutter 应用程序中预期的
ThemeData
,您无需定义新的 MaterialApp
或手动在每个子页面中包含 Theme
小部件即可实现它。
出现此问题的原因是,当您导航到新路线(如
ThirdRoute
)时,它不再属于 Theme
中提供的 FirstRoute
。 Theme
小部件仅影响其在小部件树中的后代,但是当您使用 Navigator.push
导航离开时,您实际上会移出应用了 Theme
的当前小部件树。
将导航包裹在
Theme
中:
为了确保 ThirdRoute
也继承 SecondRoute
提供的主题,您可以将导航包装到 ThirdRoute
小部件中的 Theme
,如下所示:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Theme(
data: Theme.of(context), // Pass the current theme forward
child: const ThirdRoute(),
),
),
);
这会将当前主题(从
SecondRoute
)传递到 ThirdRoute
。
使用继承的小部件或状态管理解决方案: 如果您的应用程序有很多页面,并且您想要动态更改主题或始终应用不同的主题,那么使用
Provider
、Riverpod
或 Bloc
等状态管理解决方案将会很有帮助。您可以使 ThemeData
在您的应用程序中全局可访问,并根据需要进行更新。
嵌套
MaterialApp
:
虽然用新的 MaterialApp
包装子页面可以通过应用单独的 ThemeData
来解决问题,但这不是推荐的方法。应用程序中存在多个 MaterialApp
可能会导致导航、主题和其他应用程序范围的行为(例如本地化和路由)出现问题。相反,请考虑上面的解决方案。
使用第一种方法(将导航包装在
Theme
小部件中)将是确保所有子页面继承所需主题而不增加不必要的复杂性的最干净、最简单的方法。
以下是将
SecondRoute
的导航修改为 ThirdRoute
的方法:
class SecondRoute extends StatelessWidget {
const SecondRoute({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("SecondRoute"),
),
body: Center(
child: TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Theme(
data: Theme.of(context), // Pass the current theme forward
child: const ThirdRoute(),
),
),
);
},
child: Text(
"Go To ThirdRoute",
style: TextStyle(color: Theme.of(context).primaryColor),
),
),
),
);
}
}
这将确保
ThirdRoute
也继承 ThemeData
中设置的 SecondRoute
。