Flutter:为什么在构建函数中基于条件语句在脚手架之间进行切换,但不适用于自定义窗口小部件

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter:为什么在构建函数中基于条件语句在脚手架之间进行切换,但不适用于自定义窗口小部件相关的知识,希望对你有一定的参考价值。

我创建了一个自定义Scaffold,以便无论显示哪个页面,都可以轻松显示SnackBar消息,称为MScaffold。但是,当我尝试有条件地构建两个可能的MScaffold中的一个或另一个时,我会再次获得相同的MScaffold。当我用常规的Scaffold执行此操作时,页面切换正常,使我相信它与MScaffold有关。不过,我不知道为什么。我唯一能猜到的是,当您扩展Widget时,某些属性必须更改,但是我无法弄清楚为什么这会阻止MyHomePage显示不同的MScaffold(当再次显示时,不同的Scaffold就可以了。

我还尝试了最大扩展名Scaffold的尝试,但我发现只有当我override使用build方法时,它才能做到这一点。

以某种方式,当我在自定义overridebuild中使用ScaffoldStateWidgetMScaffold方法时,它将阻止父窗口小部件使用相同类型的新Widget进行自身重建。我还发现,当其中一个选项是Scaffold,另一个选项是MScaffold时,无论顺序如何,它的切换效果都很好。只有这两个选项均为MScaffold时,它才不再切换。

[似乎每次MScaffoldState每次都返回相同的build结果。

这里是实现(如果将两个MScaffold替换为Scaffold,则可以使用:]

import 'package:flutter/material.dart';
import 'PageSwitcher.dart';
import 'MScaffold.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'MScaffold switch error',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'MScaffold switch error'),
    );
  


class MyHomePage extends StatefulWidget 
  MyHomePage(Key key, this.title) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  void _switchPage() 
    PageSwitcher().switchPage();
    setState(() );
  

  @override
  Widget build(BuildContext context) 
    return PageSwitcher().pageNum == 1
        ? MScaffold(
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(
                    'Page 1',
                  ),
                ],
              ),
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: _switchPage,
              tooltip: 'Switch',
              child: Icon(Icons.refresh),
            ), // This trailing comma makes auto-formatting nicer for build methods.
          )
        : MScaffold(
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(
                    'Page 2',
                  ),
                ],
              ),
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: _switchPage,
              tooltip: 'Switch',
              child: Icon(Icons.refresh),
            ), // This trailing comma makes auto-formatting nicer for build methods.
          );
  

这里是PageSwitcher类,用于跟踪应用程序在哪个页面上:

class PageSwitcher
  static final PageSwitcher _thisClass = PageSwitcher._internal();
  PageSwitcher._internal();
  factory PageSwitcher()
    return _thisClass;
  
  int pageNum = 1;
  void switchPage()
    pageNum = pageNum==1?2:1;
  

自定义WidgetMScaffoldScaffold的改动很小的版本,所以我不明白为什么会发生此错误。

这是MScaffold的页面:

import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class ShowSnackBar 
  SnackBar currentSnackBar;
  int lastHideTime = -1; //in millisecondsSinceEpoch
  String _msg;
  bool isSnackBarVisible = false;
  static final _thisClass = ShowSnackBar._internal();
  ShowSnackBar._internal();
  factory ShowSnackBar() 
    return _thisClass;
  
  ChangeNotifier showNotifier = ChangeNotifier();
  ChangeNotifier hideNotifier = ChangeNotifier();

  showText(String inputMsg) 
    _msg = inputMsg;
    currentSnackBar = SnackBar(
      content: Text(_msg));
    isSnackBarVisible = true;
    showNotifier.notifyListeners();
  

  show(SnackBar inputSnackBar) 
    currentSnackBar = inputSnackBar;
    isSnackBarVisible = true;
    showNotifier.notifyListeners();
  

  hide() 
    hideNotifier.notifyListeners();
  


class MScaffold extends Scaffold 
  Key key;
  var appBar;
  var body;
  var floatingActionButton;
  var floatingActionButtonLocation;
  var floatingActionButtonAnimator;
  var persistentFooterButtons;
  var drawer;
  var endDrawer;
  var bottomNavigationBar;
  var bottomSheet;
  var backgroundColor;
  var resizeToAvoidBottomPadding;
  var resizeToAvoidBottomInset;
  var primary;
  var drawerDragStartBehavior;
  var extendBody;
  var extendBodyBehindAppBar;
  var drawerScrimColor;
  var drawerEdgeDragWidth;

  MScaffold(
    Key key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  )  : assert(primary != null),
      assert(extendBody != null),
      assert(extendBodyBehindAppBar != null),
      assert(drawerDragStartBehavior != null),
      super(key: key);

  @override
  ScaffoldState createState() 
    return MScaffoldState(
      key: key,
      appBar: appBar,
      body: body,
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      floatingActionButtonAnimator: floatingActionButtonAnimator,
      persistentFooterButtons: persistentFooterButtons,
      drawer: drawer,
      endDrawer: endDrawer,
      bottomNavigationBar: bottomNavigationBar,
      bottomSheet: bottomSheet,
      backgroundColor: backgroundColor,
      resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
      resizeToAvoidBottomInset: resizeToAvoidBottomInset,
      primary: primary,
      drawerDragStartBehavior: drawerDragStartBehavior,
      extendBody: extendBody,
      extendBodyBehindAppBar: extendBodyBehindAppBar,
      drawerScrimColor: drawerScrimColor,
      drawerEdgeDragWidth: drawerEdgeDragWidth,
    );
  


class MScaffoldState extends ScaffoldState 
  Key key;
  var appBar;
  var body;
  var floatingActionButton;
  var floatingActionButtonLocation;
  var floatingActionButtonAnimator;
  var persistentFooterButtons;
  var drawer;
  var endDrawer;
  var bottomNavigationBar;
  var bottomSheet;
  var backgroundColor;
  var resizeToAvoidBottomPadding;
  var resizeToAvoidBottomInset;
  var primary;
  var drawerDragStartBehavior;
  var extendBody;
  var extendBodyBehindAppBar;
  var drawerScrimColor;
  var drawerEdgeDragWidth;

  MScaffoldState(
    Key key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  )  : assert(primary != null),
      assert(extendBody != null),
      assert(extendBodyBehindAppBar != null),
      assert(drawerDragStartBehavior != null);

  Function() _listenerShow;
  Function() _listenerHide;

  @override
  void initState() 
    super.initState();
    _listenerShow = () 
      if (mounted) 
        Scaffold.of(_scaffoldContext)
          .showSnackBar(ShowSnackBar().currentSnackBar)
          .closed
          .then((SnackBarClosedReason reason) 
          ShowSnackBar().isSnackBarVisible = false;
          ShowSnackBar().lastHideTime = DateTime.now().millisecondsSinceEpoch;
        );
      
    ;

    _listenerHide = () 
      if (mounted) 
        Scaffold.of(_scaffoldContext).hideCurrentSnackBar();
      
    ;

    Future.microtask(() 
      if (ShowSnackBar().isSnackBarVisible) _listenerShow();
      ShowSnackBar().showNotifier.addListener(_listenerShow);
      ShowSnackBar().hideNotifier.addListener(_listenerHide);
    );
  

  @override
  dispose() 
    ShowSnackBar().showNotifier?.removeListener(_listenerShow);
    ShowSnackBar().hideNotifier?.removeListener(_listenerHide);
    super.dispose();
  

  BuildContext _scaffoldContext;
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      key: key,
      appBar: appBar,
      body: Builder(
        builder: (context) 
          _scaffoldContext = context;
          return body;
        ,
      ),
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      floatingActionButtonAnimator: floatingActionButtonAnimator,
      persistentFooterButtons: persistentFooterButtons,
      drawer: drawer,
      endDrawer: endDrawer,
      bottomNavigationBar: bottomNavigationBar,
      bottomSheet: bottomSheet,
      backgroundColor: backgroundColor,
      resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
      resizeToAvoidBottomInset: resizeToAvoidBottomInset,
      primary: primary,
      drawerDragStartBehavior: drawerDragStartBehavior,
      extendBody: extendBody,
      extendBodyBehindAppBar: extendBodyBehindAppBar,
      drawerScrimColor: drawerScrimColor,
      drawerEdgeDragWidth: drawerEdgeDragWidth,
    );
  

如您所见,MScaffold接受与Scaffold相同的参数,并使用这些参数生成Scaffold。唯一不同的是使用ShowSnackBar类来处理SnackBar消息。除此之外,它只是使用Scaffold参数,然后使用这些参数构建Scaffold


SOLUTION

解决方案是Saed Nabil的建议,加上MScaffold(Key key...更改为MScaffold(this.key...MScaffoldState(Key key...更改为MScaffoldState(this.key...,因为它不接受密钥。

新演示文稿看起来像这样:

import 'package:flutter/material.dart';
import 'PageSwitcher.dart';
import 'MScaffold.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'MScaffold switch error',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'MScaffold switch error'),
    );
  


class MyHomePage extends StatefulWidget 
  MyHomePage(Key key, this.title) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  void _switchPage() 
    PageSwitcher().switchPage();
    setState(() );
  

  @override
  Widget build(BuildContext context) 
    return PageSwitcher().pageNum == 1
        ? MScaffold(
      key: ValueKey(1),
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(
                    'Page 1',
                  ),
                ],
              ),
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: _switchPage,
              tooltip: 'Switch',
              child: Icon(Icons.refresh),
            ),
          )
        : MScaffold(
      key:ValueKey(2),
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(
                    'Page 2',
                  ),
                ],
              ),
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: _switchPage,
              tooltip: 'Switch',
              child: Icon(Icons.refresh),
            ),
          );
  

新的MScaffold.dart看起来像这样:

import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

class ShowSnackBar 
  SnackBar currentSnackBar;
  int lastHideTime = -1; //in millisecondsSinceEpoch
  String _msg;
  bool isSnackBarVisible = false;
  static final _thisClass = ShowSnackBar._internal();
  ShowSnackBar._internal();
  factory ShowSnackBar() 
    return _thisClass;
  
  ChangeNotifier showNotifier = ChangeNotifier();
  ChangeNotifier hideNotifier = ChangeNotifier();

  showText(String inputMsg) 
    _msg = inputMsg;
    currentSnackBar = SnackBar(
      content: Text(_msg));
    isSnackBarVisible = true;
    showNotifier.notifyListeners();
  

  show(SnackBar inputSnackBar) 
    currentSnackBar = inputSnackBar;
    isSnackBarVisible = true;
    showNotifier.notifyListeners();
  

  hide() 
    hideNotifier.notifyListeners();
  


class MScaffold extends Scaffold 
  Key key;
  var appBar;
  var body;
  var floatingActionButton;
  var floatingActionButtonLocation;
  var floatingActionButtonAnimator;
  var persistentFooterButtons;
  var drawer;
  var endDrawer;
  var bottomNavigationBar;
  var bottomSheet;
  var backgroundColor;
  var resizeToAvoidBottomPadding;
  var resizeToAvoidBottomInset;
  var primary;
  var drawerDragStartBehavior;
  var extendBody;
  var extendBodyBehindAppBar;
  var drawerScrimColor;
  var drawerEdgeDragWidth;

  MScaffold(
    this.key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  )  : assert(primary != null),
      assert(extendBody != null),
      assert(extendBodyBehindAppBar != null),
      assert(drawerDragStartBehavior != null),
  super(key:key);

  @override
  ScaffoldState createState() 
    return MScaffoldState(
      key: key,
      appBar: appBar,
      body: body,
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      floatingActionButtonAnimator: floatingActionButtonAnimator,
      persistentFooterButtons: persistentFooterButtons,
      drawer: drawer,
      endDrawer: endDrawer,
      bottomNavigationBar: bottomNavigationBar,
      bottomSheet: bottomSheet,
      backgroundColor: backgroundColor,
      resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
      resizeToAvoidBottomInset: resizeToAvoidBottomInset,
      primary: primary,
      drawerDragStartBehavior: drawerDragStartBehavior,
      extendBody: extendBody,
      extendBodyBehindAppBar: extendBodyBehindAppBar,
      drawerScrimColor: drawerScrimColor,
      drawerEdgeDragWidth: drawerEdgeDragWidth,
    );
  


class MScaffoldState extends ScaffoldState 
  Key key;
  var appBar;
  var body;
  var floatingActionButton;
  var floatingActionButtonLocation;
  var floatingActionButtonAnimator;
  var persistentFooterButtons;
  var drawer;
  var endDrawer;
  var bottomNavigationBar;
  var bottomSheet;
  var backgroundColor;
  var resizeToAvoidBottomPadding;
  var resizeToAvoidBottomInset;
  var primary;
  var drawerDragStartBehavior;
  var extendBody;
  var extendBodyBehindAppBar;
  var drawerScrimColor;
  var drawerEdgeDragWidth;

  MScaffoldState(
    this.key,
    this.appBar,
    this.body,
    this.floatingActionButton,
    this.floatingActionButtonLocation,
    this.floatingActionButtonAnimator,
    this.persistentFooterButtons,
    this.drawer,
    this.endDrawer,
    this.bottomNavigationBar,
    this.bottomSheet,
    this.backgroundColor,
    this.resizeToAvoidBottomPadding,
    this.resizeToAvoidBottomInset,
    this.primary = true,
    this.drawerDragStartBehavior = DragStartBehavior.start,
    this.extendBody = false,
    this.extendBodyBehindAppBar = false,
    this.drawerScrimColor,
    this.drawerEdgeDragWidth,
  )  : assert(primary != null),
      assert(extendBody != null),
      assert(extendBodyBehindAppBar != null),
      assert(drawerDragStartBehavior != null);

  Function() _listenerShow;
  Function() _listenerHide;

  @override
  void initState() 
    super.initState();
    _listenerShow = () 
      if (mounted) 
        Scaffold.of(_scaffoldContext)
          .showSnackBar(ShowSnackBar().currentSnackBar)
          .closed
          .then((SnackBarClosedReason reason) 
          ShowSnackBar().isSnackBarVisible = false;
          ShowSnackBar().lastHideTime = DateTime.now().millisecondsSinceEpoch;
        );
      
    ;

    _listenerHide = () 
      if (mounted) 
        Scaffold.of(_scaffoldContext).hideCurrentSnackBar();
      
    ;

    Future.microtask(() 
      if (ShowSnackBar().isSnackBarVisible) _listenerShow();
      ShowSnackBar().showNotifier.addListener(_listenerShow);
      ShowSnackBar().hideNotifier.addListener(_listenerHide);
    );
  

  @override
  dispose() 
    ShowSnackBar().showNotifier?.removeListener(_listenerShow);
    ShowSnackBar().hideNotifier?.removeListener(_listenerHide);
    super.dispose();
  

  BuildContext _scaffoldContext;
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      key: key,
      appBar: appBar,
      body: Builder(
        builder: (context) 
          _scaffoldContext = context;
          return body;
        ,
      ),
      floatingActionButton: floatingActionButton,
      floatingActionButtonLocation: floatingActionButtonLocation,
      floatingActionButtonAnimator: floatingActionButtonAnimator,
      persistentFooterButtons: persistentFooterButtons,
      drawer: drawer,
      endDrawer: endDrawer,
      bottomNavigationBar: bottomNavigationBar,
      bottomSheet: bottomSheet,
      backgroundColor: backgroundColor,
      resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
      resizeToAvoidBottomInset: resizeToAvoidBottomInset,
      primary: primary,
      drawerDragStartBehavior: drawerDragStartBehavior,
      extendBody: extendBody,
      extendBodyBehindAppBar: extendBodyBehindAppBar,
      drawerScrimColor: drawerScrimColor,
      drawerEdgeDragWidth: drawerEdgeDragWidth,
    );
  

而且有效!

答案

这很麻烦,因为没有为您的MScaffold类型的有状态小部件提供键

您可以在构建这样的支架对象时通过提供适当的键来解决此问题

    MScaffold(
      key: ValueKey('1'),
      ...
)

    MScaffold(
      key: ValueKey('2'),
      ...
)

以上是关于Flutter:为什么在构建函数中基于条件语句在脚手架之间进行切换,但不适用于自定义窗口小部件的主要内容,如果未能解决你的问题,请参考以下文章

golang函数 和 条件语句

让移动开发更轻松 闲鱼基于Flutter构建跨端APP应用实践

基于多列需要窗函数的条件计算

我如何在 FutureBuilder Widget 的构建器函数中等待 - Flutter

Getx在flutter中响应式设计

R语言使用party包中的cforest函数基于条件推理决策树(Conditional inference trees)构建随机森林使用varimp函数查看特征重要度使用table函数计算混淆矩阵