Riverpod - 如何将 PreferredSizeWidget 包装在 Consumer 中

Riverpod - How to wrap a PreferredSizeWidget inside a Consumer

我有一个 DefaultTabController 并且我有一个 returns List<PreferredSizeWidget> 的方法,它们是 AppBar 的页面。我希望他们观看 ChangeNotifier 中的状态,因此我想将它们包装在 Consumer 中。当我尝试这样做时,出现如下错误:

“无法将参数类型 'Widget' 分配给参数类型 'PreferredSizeWidget'。”

我该如何解决这个问题?

感谢和问候。

错误来自 ScaffoldappBar 参数需要 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()
                       )
                ]
       );