【问题标题】:setState from child widget (drawer) not updating a widget above it in the tree (FloatingAppBar)来自子小部件(抽屉)的 setState 未更新树中其上方的小部件(FloatingAppBar)
【发布时间】:2021-01-28 12:21:38
【问题描述】:

我有一个来自this library (pub.dev)FloatingSearchBar 的颤振项目。我有四个按钮,其中一个是GestureDetector(这并不重要,但解释了一些幕后信息)。

这一切看起来像这样:

或者这个:

或其他变体。

这些按钮都可以正常工作,尽管我花了很多时间来让它们这样做。它们是可切换的,它们的Icons 由一个可以使用setState 更新的变量决定。

不过,也有这样的布局:

应该可以通过菜单内的这个开关/按钮来打开和关闭:

它也只是更新了setState 中的offlineMode 变量。但是这个setState 不会更新搜索栏,直到我通过单击任何按钮强制搜索栏更新。

为什么setState 在这种特殊情况下不起作用?

搜索栏代码:

Widget buildFloatingSearchBar(AsyncSnapshot snapshot) {
    const bool isPortrait = true;

    return DescribedFeatureOverlay( //Don't worry about these I don't think
      featureId: 'search',
      tapTarget: Icon(Icons.search),
      title: Text('Search'),
      description: Text(
          'Tap the bar at the top to open the search interface, where you can search for GeoTags by name, approximate location or by author.'),
      overflowMode: OverflowMode.extendBackground,
      backgroundColor: Theme.of(context).primaryColor,
      targetColor: Colors.white,
      textColor: Colors.white,
      child: FloatingSearchBar(
        borderRadius: BorderRadius.all(
          Radius.circular(50),
        ),
        progress: loadingSearch,
        title: offlineMode //Checking offlineMode variable Should update with setState
            ? Row(
                children: [
                  Text(
                    'Geotagger',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 15,
                    ),
                  ),
                  Text(
                    ' - Offline Mode',
                    style: TextStyle(
                      //fontWeight: FontWeight.bold,
                      fontSize: 15,
                    ),
                  ),
                ],
              )
            : null,
        backgroundColor: Theme.of(context).backgroundColor,
        hint: 'Find a GeoTag...', //Shown if offlineMode is off (title is null)
        transitionDuration: const Duration(milliseconds: 800),
        transitionCurve: Curves.easeInOut,
        physics: const BouncingScrollPhysics(),
        axisAlignment: isPortrait ? 0.0 : -1.0,
        openAxisAlignment: 0.0,
        maxWidth: isPortrait ? 600 : 500,
        height: compassExpanded ? 62 : 48,
        debounceDelay: const Duration(milliseconds: 1500),
        onQueryChanged: (inQuery) {
          String query = inQuery.trim();
          if (offlineMode || query == null || query == '' || query == '@') {
            return;
          }
          if (query.startsWith('@')) {}
          setState(() {
            loadingSearch = true;
          });

          // Call your model, bloc, controller here.
          Future.delayed(const Duration(seconds: 3), () {
            setState(() {
              loadingSearch = false;
            });
          });
        },
        // Specify a custom transition to be used for
        // animating between opened and closed stated.
        transition: ExpandingFloatingSearchBarTransition(),
        accentColor: Theme.of(context).accentColor,
        actions: [
          FloatingSearchBarAction.searchToClear(
            showIfClosed: false,
          ),
          FloatingSearchBarAction( //This one is to check current GPS state
            showIfOpened: false,
            child: StreamBuilder<Object>(
                stream: Stream.periodic(Duration(seconds: 5), (x) => x),
                builder: (context, _) {
                  return FutureBuilder(
                      future: Geolocator.getCurrentPosition(
                        desiredAccuracy: LocationAccuracy.medium,
                        timeLimit: Duration(seconds: 5),
                      ),
                      builder: (context, compassDir) {
                        if (compassDir.hasError) {
                          //print('failure');
                          return Padding(
                            padding: const EdgeInsets.only(
                              right: 7,
                              bottom: 2,
                            ),
                            child: Icon(
                              Icons.wrong_location,
                              color: Colors.red,
                            ),
                          );
                        } else {
                          return Container(width: 0, height: 0);
                        }
                      });
                }),
          ),
          FloatingSearchBarAction(
            showIfOpened: false,
            child: DescribedFeatureOverlay(
              featureId: 'toggleLocationSnapping',
              tapTarget: Icon(Icons.control_camera),
              title: Text('Location Snapping'),
              description: Text(
                  'This button toggles the map mode between snapping (default) and free. When the icon shown is a circle with a line across it, tap it to switch to free mode, and you\'ll be able to move, zoom and rotate the map freely. In snapping mode, you will be snapped to your current location and orientation, and you\'ll also be unable to pan, rotate or zoom (usually).'),
              //overflowMode: OverflowMode.extendBackground,
              backgroundColor: Theme.of(context).primaryColor,
              targetColor: Colors.white,
              textColor: Colors.white,
              child: CircularButton(
                icon: Icon(moveMapToUser
                    ? Icons.location_disabled
                    : Icons.my_location),
                onPressed: () {
                  setState(() {
                    moveMapToUser = !moveMapToUser;
                    rotateMapToUser = false;
                  });
                },
              ),
            ),
          ),
          FloatingSearchBarAction(
            showIfOpened: false,
            child: Visibility(
              visible: moveMapToUser,
              child: DescribedFeatureOverlay(
                featureId: 'rotationAndNorth',
                tapTarget: Icon(Icons.screen_rotation),
                title: Text('Rotation & Panning Mode'),
                description: Text(
                    'This button has three states. When showing an upward facing arrow in free mode, tap it to orientate the map north, and remain in free mode. When showing a lock symbol in snapping mode, tap it to renable automatic rotation and prevent zooming. When showing an unlock symbol in snapping mode, tap it to allow rotation and zooming, but still prevent panning away from your current location.'),
                //overflowMode: OverflowMode.extendBackground,
                backgroundColor: Theme.of(context).primaryColor,
                targetColor: Colors.white,
                textColor: Colors.white,
                child: CircularButton(
                  icon: Icon(rotateMapToUser ? Icons.lock_open : Icons.lock),
                  onPressed: () {
                    setState(() {
                      rotateMapToUser = !rotateMapToUser;
                      if (!rotateMapToUser) {
                        controller.rotate(0);
                      }
                    });
                  },
                ),
              ),
            ),
          ),
          FloatingSearchBarAction(
            showIfOpened: false,
            child: Visibility(
              visible: !moveMapToUser,
              child: CircularButton(
                icon: Icon(Icons.north),
                onPressed: () {
                  setState(() {
                    controller.rotate(0);
                  });
                },
              ),
            ),
          ),
          FloatingSearchBarAction(
            showIfOpened: false,
            child: Visibility(
              visible: !offlineMode,
              child: DescribedFeatureOverlay(
                featureId: 'refresh',
                tapTarget: Icon(Icons.refresh),
                title: Text('Refresh'),
                description: Text(
                    'Tap this button to search the area visible on your device for GeoTags after panning the map.'),
                //overflowMode: OverflowMode.extendBackground,
                backgroundColor: Theme.of(context).primaryColor,
                targetColor: Colors.white,
                textColor: Colors.white,
                child: CircularButton(
                    icon: const Icon(Icons.refresh),
                    onPressed: () {
                      //setState(() {
                      paintGeoTags(
                          true,
                          () => setState(() {
                                loadingSearch = true;
                              }),
                          () => setState(() {
                                loadingSearch = false;
                              }));
                      //});
                    }),
              ),
            ),
          ),
          FloatingSearchBarAction( //Profile Pic icon
            showIfOpened: !compassExpanded, //Don't worry about compassExpanded
            child: Visibility(
              visible: !offlineMode, //Should update on setState
              child: DescribedFeatureOverlay(
                featureId: 'profile',
                tapTarget: Icon(Icons.account_circle),
                title: Text('Public Profile'),
                description: Text(
                    'Tap this button to view your public profile, and view information such as rank, total points and various other information. You can manage your profile by tapping the settings cog icon in that screen, or by choosing Manage My Profile from the menu. The colored border represents the color of your current rank, and the point will always face north. You can hold down on this icon to toggle the visibility of the compass point and to expand or reduce the height of the search bar. \n\nAfter you\'ve tapped the icon above to move to the next button introduction, tap the menu button in the top left, and your introduction will continue there!'),
                overflowMode: OverflowMode.extendBackground,
                backgroundColor: Theme.of(context).primaryColor,
                targetColor: Colors.white,
                textColor: Colors.white,
                child: Hero(
                  tag: 'profileImage',
                  child: GestureDetector(
                    onTap: () => Navigator.pushNamed(
                      context,
                      ProfileScreen.routeName,
                      arguments: ScreenArguments(
                        globals.userData.displayName,
                        //'test user',
                        FirebaseFirestore.instance
                            .doc('users/' + globals.userData.uid),
                        true,
                      ),
                    ),
                    onLongPressStart: (e) {
                      setState(() {
                        compassExpanding = true;
                      });
                    },
                    onLongPressEnd: (e) => {
                      setState(() {
                        compassExpanded = !compassExpanded;
                        compassExpanding = false;
                      }),
                      //showLogoutAlert(context)
                    },
                    child: StreamBuilder<Object>(
                        stream: FlutterCompass.events,
                        builder: (context, compassDir) {
                          if (snapshot.hasError) {
                            return Center(
                                child: Text('Error: ${snapshot.error}'));
                          } else {
                            if (snapshot.data == null) {
                              return Container();
                            }
                          }
                          return Padding(
                            padding: const EdgeInsets.only(
                              left: 4,
                            ),
                            child: Container(
                              width: 41,
                              height: 41,
                              transform: Matrix4Transform()
                                  .rotateDegrees(
                                      double.parse(((compassDir.data) ?? 0)
                                                  .toString()) *
                                              -1 +
                                          45,
                                      origin: Offset(41 / 2, 41 / 2))
                                  .matrix4,
                              decoration: ShapeDecoration(
                                  //shape: BoxShape.circle,
                                  shape: CustomRoundedRectangleBorder(
                                    borderRadius: BorderRadius.only(
                                      bottomLeft: Radius.circular(21),
                                      bottomRight: Radius.circular(21),
                                      topRight: Radius.circular(21),
                                      topLeft: Radius.circular(
                                          compassExpanded ? 0 : 21),
                                    ),
                                    leftSide: BorderSide(
                                      width: 2,
                                      color: changeMedalColor(
                                          snapshot.data.points),
                                    ),
                                    bottomLeftCornerSide: BorderSide(
                                      width: 2,
                                      color: changeMedalColor(
                                          snapshot.data.points),
                                    ),
                                    rightSide: BorderSide(
                                      width: 2,
                                      color: changeMedalColor(
                                          snapshot.data.points),
                                    ),
                                    bottomRightCornerSide: BorderSide(
                                      width: 2,
                                      color: changeMedalColor(
                                          snapshot.data.points),
                                    ),
                                    bottomSide: BorderSide(
                                      width: 2,
                                      color: changeMedalColor(
                                          snapshot.data.points),
                                    ),
                                    topSide: BorderSide(
                                      width: 2,
                                      color: changeMedalColor(
                                          snapshot.data.points),
                                    ),
                                    topRightCornerSide: BorderSide(
                                      width: 2,
                                      color: changeMedalColor(
                                          snapshot.data.points),
                                    ),
                                  ),
                                  color: //!compassExpanding
                                      /*?*/ changeMedalColor(
                                          snapshot.data.points)
                                  //: Theme.of(context).accentColor,
                                  /*border: Border.all(
                                width: 2,
                                color: changeMedalColor(snapshot.data.points),
                              ),*/
                                  ),
                              //duration: Duration(milliseconds: 250),
                              child: SizedBox(
                                child: !compassExpanding
                                    ? Transform.rotate(
                                        angle: vmath.radians(double.parse(
                                                ((compassDir.data) ?? 0)
                                                    .toString()) -
                                            45),
                                        child: CircleAvatar(
                                          minRadius: 1000,
                                          maxRadius: 5000,
                                          backgroundImage: NetworkImage(
                                            globals.userData.photoURL,
                                          ),
                                          backgroundColor: changeMedalColor(
                                              snapshot.data.points),
                                        ),
                                      )
                                    : Transform.rotate(
                                        angle: vmath.radians(double.parse(
                                                ((compassDir.data) ?? 0)
                                                    .toString()) -
                                            45),
                                        child: Icon(Icons.expand,
                                            color: Colors.white),
                                      ),
                              ),
                            ),
                          );
                        }),
                  ),
                ),
              ),
            ),
          ),
        ],
        builder: (context, transition) {
          return ClipRRect(
            //borderRadius: BorderRadius.circular(8),
            child: Material(
              color: Colors.white,
              //elevation: 4.0,
              child: !offlineMode
                  ? Column(
                      children: [
                        Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Text(
                            'Search for a GeoTag by name, approximate location. To search by author, use \'@\' in front of the search query.',
                            textAlign: TextAlign.center,
                          ),
                        ),
                        SizedBox(height: 15),
                        Column(
                          mainAxisSize: MainAxisSize.min,
                          children: Colors.accents.map((color) {
                            return Container(height: 112, color: color);
                          }).toList(),
                        ),
                      ],
                    )
                  : Column(
                      children: [
                        Center(
                          child: Icon(
                            Icons.cloud_off,
                            size: 40,
                          ),
                        ),
                        SizedBox(height: 10),
                        Text(
                          'You\'re in Offline Mode',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        Text(
                            'Whilst in Offline Mode, the search feature is disabled.'),
                      ],
                    ),
            ),
          );
        },
      ),
    );
  }

抽屉内的代码更新变量:

DescribedFeatureOverlay(
            featureId: 'offlineMode',
            tapTarget: Icon(Icons.file_download),
            title: Text('Offline Mode'),
            description: Text(
                'One of the most important and complex features of this app. Allows for fully offline functioning, including downloading large areas of maps. You can see more detail about it\'s features and drawbacks by tapping this menu item after the introduction is complete.'),
            overflowMode: OverflowMode.extendBackground,
            backgroundColor: Theme.of(widget.mainContext).primaryColor,
            targetColor: Colors.white,
            textColor: Colors.white,
            child: ListTile(
              title: Text(offlineMode
                  ? 'Offline Manager'
                  : (internetConnected
                      ? 'Enable Offline Mode'
                      : 'Offline Mode Disabled')),
              onTap: offlineMode
                  ? () {
                      Navigator.pushNamed(
                          widget.mainContext, OfflineManagerScreen.routeName);
                    }
                  : (internetConnected //Don't worry about internetConnected
                      ? () {
                          showAlert(
                              context,
                              widget.mainContext,
                              'Offline Mode',
                              <Widget>[
                                //Some text widgets
                              ],
                              'Agree & Enable Offline Mode',
                              'Cancel', () {
                            StorageManager.saveData('offlineMode', 'true');
                            setState(() {
                              offlineMode = true; //Should Update
                            });
                            if (!Directory(globals.saveDir.path + '/tiles')
                                .existsSync()) {
                              Directory(globals.saveDir.path + '/tiles')
                                  .createSync(recursive: true);
                            }
                          });
                        }
                      : null),
              leading: Icon(offlineMode
                  ? Icons.offline_pin
                  : (internetConnected
                      ? Icons.file_download
                      : Icons.cloud_off)),
              trailing: Switch(
                value: offlineMode,
                onChanged: offlineMode && internetConnected
                    ? (bool newVal) {
                        showAlert(
                            context,
                            widget.mainContext,
                            'Disable Offline Mode',
                            <Widget>[
                              const Text(
                                'Disabling Offline Mode will delete all saved tiles, and resync information with the server. Any GeoTags tagged will be uploaded, and the appropriate point value will be added to your account. Are you sure you want to disable Offline Mode?',
                                textAlign: TextAlign.justify,
                              ),
                            ],
                            'Disable',
                            'Stay Offline', () {
                          setState(() {
                            offlineMode = false; //Should update
                          });
                          StorageManager.saveData('offlineMode', 'false');
                          Directory(globals.saveDir.path + '/tiles')
                              .deleteSync(recursive: true);
                          setState(() {
                            totalSize = 0;
                            filesList = [];
                            sizeList = [];
                          });
                        });
                      }
                    : null,
                activeColor: Theme.of(widget.mainContext).primaryColorLight,
              ),
            ),
          ),

如果您还没有意识到,这是我的第一个大型 Flutter 项目,因此也非常感谢您提供其他方面的提示!

【问题讨论】:

  • 我已经解决了这个问题,请看下面我的回答。

标签: flutter flutter-layout setstate


【解决方案1】:

小部件正在更新内部小部件。但所有状态都没有改变。 你应该像这样将状态传递给你所有的内部小部件(在你的情况下是 showalert)

StatefulBuilder(builder: (context, newState) {
                           return 
                         showAlert(
                          context,
                          widget.mainContext,
                          'Offline Mode',
                          <Widget>[
                            //Some text widgets
                          ],
                          'Agree & Enable Offline Mode',
                          'Cancel', () {
                        StorageManager.saveData('offlineMode', 'true');
                        newState(() {
                          offlineMode = true; //Should Update
                        });
                        if (!Directory(globals.saveDir.path + '/tiles')
                            .existsSync()) {
                          Directory(globals.saveDir.path + '/tiles')
                              .createSync(recursive: true);
                        }
                      });
});

请试试这个!我没有测试,但逻辑是你应该通过状态。 也许你应该用新状态包装 listtile..

【讨论】:

  • 感谢您的回复!但是,我不确定您是否理解正确。当离线模式为时,列表图块正在更新。问题是当离线模式由列表磁贴中的开关或按钮切换时,搜索栏不会更新为新设计。如果我错了,请纠正我。
  • 是的,我猜对了,搜索栏的状态没有更新,你的 setstate 只影响你的内部小部件,所以将你的状态传递给内部小部件,如果这个传递的参数将被切换,这个状态会改变屏幕的所有状态
  • 好吧,但是代码不起作用,因为 showAlert 不是 statefulBuilder 想要的小部件。对不起,我对此很陌生,我不确定你的意思。 :|
猜你喜欢
  • 2019-08-31
  • 2019-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-18
  • 1970-01-01
  • 1970-01-01
  • 2021-09-02
相关资源
最近更新 更多