Riverpod - 如何在消费者中包装 PreferredSizeWidget

Posted

技术标签:

【中文标题】Riverpod - 如何在消费者中包装 PreferredSizeWidget【英文标题】:Riverpod - How to wrap a PreferredSizeWidget inside a Consumer 【发布时间】:2021-03-24 07:18:50 【问题描述】:

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

“参数类型'Widget'不能分配给参数类型'PreferredSizeWidget'。”

我该如何解决这个问题?

感谢和问候。

【问题讨论】:

【参考方案1】:

错误来自appBar 的参数Scaffold 需要PreferredSizeWidget。我可以想到两个解决方案:

您可以用PreferredSize 包裹您的Consumer,并将Size.fromHeight() 用作preferredSize。也就是说,如果您的应用栏之间的高度是恒定的。 您可以完全避免使用appBar 参数,方法是将Scaffold 的主体与Expanded 包裹在Column 中,并使Consumer 成为其第一个子代。

【讨论】:

【参考方案2】:

这是我基于 Mickael 建议的实现:

首先我创建了一个 AppBarParams 类来保存 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
  

【讨论】:

【参考方案3】:

我建议再次将整个 AppBar 包装在 Consumer 中。您应该使用消费者将小部件包装在 appbar 字段(如前导、标题等)中。 Flutter 的文档中提到将 Consumer 保持在尽可能低的级别。如果您希望它们有条件地出现,您可以遵循以下做法:

AppBar(actions:[
    Consumer<Provider>(builder: (context,value,child) => value.toShow() ? 
         MyWidget() : const SizedBox()
                       )
                ]
       );

【讨论】:

以上是关于Riverpod - 如何在消费者中包装 PreferredSizeWidget的主要内容,如果未能解决你的问题,请参考以下文章

RiverPod - 如何在不在小部件中的 AsyncValue 上等待使用 FutureProvider

如何在 Riverpod 中显示来自 StateNotifier 的小吃店?

如何在不传递参数的情况下使用riverpod 系列提供程序

如何在php中包装没有空格的长文本[重复]

如何在 Javascript 中包装函数?

如何在函数中包装一段代码? [关闭]