Riverpod - 如何将 PreferredSizeWidget 包装在 Consumer 中
Riverpod - How to wrap a PreferredSizeWidget inside a Consumer
我有一个 DefaultTabController
并且我有一个 returns List<PreferredSizeWidget>
的方法,它们是 AppBar
的页面。我希望他们观看 ChangeNotifier
中的状态,因此我想将它们包装在 Consumer
中。当我尝试这样做时,出现如下错误:
“无法将参数类型 'Widget' 分配给参数类型 'PreferredSizeWidget'。”
我该如何解决这个问题?
感谢和问候。
错误来自 Scaffold
的 appBar
参数需要 PreferredSizeWidget
。我可以想到两个解决方案:
- 您可以用
PreferredSize
包装您的 Consumer
并将 Size.fromHeight()
用作 preferredSize
。也就是说,如果你的应用程序栏中的高度是恒定的。
- 您可以完全避免使用
appBar
参数,方法是将 Scaffold
的主体用 Expanded
包裹在 Column
内,并使 Consumer
第一个 child.
这是我根据 Mickael 的建议实现的:
首先我创建了一个 AppBarParams
class 来保存 AppBar 状态
@freezed
class AppBarParams with _$AppBarParams {
const factory AppBarParams({
required String title,
required List<Widget> actions,
}) = _AppBarParams;
}
然后我在全局提供程序文件中创建了一个 StateProvider
,如下所示:
final appBarParamsProvider = StateProvider<AppBarParams>((ref) {
return AppBarParams(title: "Default title", actions: []);
});
并使用 Consumer
:
将其附加到主应用程序中
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: "App Title",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SafeArea(
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Consumer(
builder: (context, watch, child) {
final appBarParams = watch(appBarParamsProvider).state;
return AppBar(
title: Text(appBarParams.title),
actions: appBarParams.actions
);
})
),
body: ... your body widget
)
)
)
}
}
然后你只需要编辑提供者状态来相应地更新 AppBar,当用户在页面之间切换时更新 AppBar,我创建了这个 mixin:
mixin AppBarHandler {
void updateAppBarParams(
BuildContext context, {
required String title,
List<Widget> Function()? actions
}) {
WidgetsBinding.instance!.addPostFrameCallback((_) async {
context.read(appBarParamsProvider).state = context
.read(appBarParamsProvider).state
.copyWith(
title: title,
actions: actions != null ? actions() : []
);
});
}
}
在每个必须更改标题或操作的主屏幕视图中,我都这样做了:
class Page1 extends HookWidget with AppBarHandler {
const Page1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
updateAppBarParams(context,
title: "Page 1 title",
actions: () => [
IconButton(icon: const Icon(Icons.refresh), onPressed: () {
//a custom action for Page1
context.read(provider.notifier).updateEntries();
})
]
);
... your screen widget
}
}
我会再次推荐将整个 AppBar 包装在 Consumer 中。您应该使用消费者将小部件包装在应用栏字段(如前导、标题等)中。 Flutter 的文档中提到要让 Consumer 保持在尽可能低的级别。如果你想让它们有条件地出现,你可以按照下面的做法:
AppBar(actions:[
Consumer<Provider>(builder: (context,value,child) => value.toShow() ?
MyWidget() : const SizedBox()
)
]
);
我有一个 DefaultTabController
并且我有一个 returns List<PreferredSizeWidget>
的方法,它们是 AppBar
的页面。我希望他们观看 ChangeNotifier
中的状态,因此我想将它们包装在 Consumer
中。当我尝试这样做时,出现如下错误:
“无法将参数类型 'Widget' 分配给参数类型 'PreferredSizeWidget'。”
我该如何解决这个问题?
感谢和问候。
错误来自 Scaffold
的 appBar
参数需要 PreferredSizeWidget
。我可以想到两个解决方案:
- 您可以用
PreferredSize
包装您的Consumer
并将Size.fromHeight()
用作preferredSize
。也就是说,如果你的应用程序栏中的高度是恒定的。 - 您可以完全避免使用
appBar
参数,方法是将Scaffold
的主体用Expanded
包裹在Column
内,并使Consumer
第一个 child.
这是我根据 Mickael 的建议实现的:
首先我创建了一个 AppBarParams
class 来保存 AppBar 状态
@freezed
class AppBarParams with _$AppBarParams {
const factory AppBarParams({
required String title,
required List<Widget> actions,
}) = _AppBarParams;
}
然后我在全局提供程序文件中创建了一个 StateProvider
,如下所示:
final appBarParamsProvider = StateProvider<AppBarParams>((ref) {
return AppBarParams(title: "Default title", actions: []);
});
并使用 Consumer
:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: "App Title",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SafeArea(
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Consumer(
builder: (context, watch, child) {
final appBarParams = watch(appBarParamsProvider).state;
return AppBar(
title: Text(appBarParams.title),
actions: appBarParams.actions
);
})
),
body: ... your body widget
)
)
)
}
}
然后你只需要编辑提供者状态来相应地更新 AppBar,当用户在页面之间切换时更新 AppBar,我创建了这个 mixin:
mixin AppBarHandler {
void updateAppBarParams(
BuildContext context, {
required String title,
List<Widget> Function()? actions
}) {
WidgetsBinding.instance!.addPostFrameCallback((_) async {
context.read(appBarParamsProvider).state = context
.read(appBarParamsProvider).state
.copyWith(
title: title,
actions: actions != null ? actions() : []
);
});
}
}
在每个必须更改标题或操作的主屏幕视图中,我都这样做了:
class Page1 extends HookWidget with AppBarHandler {
const Page1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
updateAppBarParams(context,
title: "Page 1 title",
actions: () => [
IconButton(icon: const Icon(Icons.refresh), onPressed: () {
//a custom action for Page1
context.read(provider.notifier).updateEntries();
})
]
);
... your screen widget
}
}
我会再次推荐将整个 AppBar 包装在 Consumer 中。您应该使用消费者将小部件包装在应用栏字段(如前导、标题等)中。 Flutter 的文档中提到要让 Consumer 保持在尽可能低的级别。如果你想让它们有条件地出现,你可以按照下面的做法:
AppBar(actions:[
Consumer<Provider>(builder: (context,value,child) => value.toShow() ?
MyWidget() : const SizedBox()
)
]
);