Flutter BaseScreen 不断重建/不想要的重建

Posted

技术标签:

【中文标题】Flutter BaseScreen 不断重建/不想要的重建【英文标题】:Flutter BaseScreen keeps rebuilding / Not wanted rebuilds 【发布时间】:2021-06-13 20:36:52 【问题描述】:

我有 a simple app,底部有 3 个导航项。 但是,当我单击下面的底部导航项之一时,整个页面都在移动,ios 从右到左,android 从底部移动。 我希望我的底部导航应用程序保持在同一个位置,如traditionnal Bottom Nav Bar

我知道 I can find 使用StatefulWidgetint _selectedIndex = 0;static const List<Widget> _widgetOptions 的底部导航栏的工作实现,

但我的应用程序已经很大,并且在 3 个步骤中使用不同的机制定义如下:(您可以在我的 github repo 上找到):

1。 MaterialApp 定义了 _onGenerateRoute :

main.dart

MaterialApp(
      title: 'Keep bottom navigation',
      themeMode: ThemeMode.dark,
      onGenerateRoute: _generateRoute,
    );

main.dart

  Route<dynamic> _generateRoute(RouteSettings settings) 
    switch (settings.name) 
      case AppRoutes.computer:
        return MaterialPageRoute(builder: (context) => ComputerScreen());
      case AppRoutes.phone:
        return MaterialPageRoute(builder: (context) => PhoneScreen());
      case AppRoutes.person:
      default:
        return MaterialPageRoute(builder: (context) => PersonScreen());
    
  

route_config.dart

class AppRoutes 
  static const String computer = '/computer';
  static const String phone = '/phone';
  static const String person = '/person';

2。每个屏幕都将 BaseScreen 扩展为一个状态:

screens/computer_screen.dart


class ComputerScreen extends StatefulWidget 
  @override
  _ComputerScreenState createState() => _ComputerScreenState();


class _ComputerScreenState extends BaseScreenState<ComputerScreen> 
  @override
  String get currentRoute => AppRoutes.computer;

  @override
  Widget buildScreen(BuildContext context) 
    return Center(
      child:
          Text("Computer screen", style: Theme.of(context).textTheme.headline3),
    );
  

3。 Base Sceen 定义了屏幕的基础:

base_screen.dart

import 'package:flutter/material.dart';
import 'package:keep_bottomnav/bottom_nav_bar.dart';

abstract class BaseScreenState<T extends StatefulWidget> extends State<T> 
  String get currentRoute;

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: this.buildAppBar(context),
      body: this.buildScreen(context),
      bottomNavigationBar: this.buildBottomNavigationBar(context),
      floatingActionButton: this.buildFloatingActionButton(context),
    );
  

  Widget buildScreen(BuildContext context);

  PreferredSizeWidget buildAppBar(BuildContext context) 
    return null;
  

  Widget buildBottomNavigationBar(BuildContext context) 
    return BottomNavBar(
      selectedRoute: currentRoute,
      onSelectRoute: _onItemTapped,
    );
  

  Widget buildFloatingActionButton(BuildContext context) 
    return null;
  

  bool get centerFloatingActionButton => false;

  void _onItemTapped(String route) 
    Navigator.pushNamedAndRemoveUntil(context, route, (route) => false);
  

4。 BottomNavBar 界面

import 'package:flutter/material.dart';
import 'package:keep_bottomnav/bottom_nav_bar_item.dart';
import 'package:keep_bottomnav/route_config.dart';

class BottomNavBar extends StatelessWidget 
  final String selectedRoute;
  final ValueChanged<String> onSelectRoute;

  BottomNavBar(this.selectedRoute, this.onSelectRoute);

  @override
  Widget build(BuildContext context) 
    double screenWidth = MediaQuery.of(context).size.width;
    double itemWidth = (screenWidth / 3);
    return Material(
      color: Colors.white,
      child: SafeArea(
        child: Container(
          padding: const EdgeInsets.only(bottom: 10),
          color: Colors.white,
          height: 65,
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              _buildNavigationItem(
                AppRoutes.computer,
                icon: Icon(Icons.computer),
                title: "Computer 1",
                itemWidth: itemWidth,
              ),
              _buildNavigationItem(
                AppRoutes.phone,
                icon: Icon(Icons.phone),
                title: "Phone 2",
                itemWidth: itemWidth,
              ),
              _buildNavigationItem(
                AppRoutes.person,
                icon: Icon(Icons.person),
                title: "Person 3",
                itemWidth: itemWidth,
              ),
            ],
          ),
        ),
      ),
    );
  

  Widget _buildNavigationItem(String route,
      Widget icon, String title, itemWidth) 
    return BottomNavBarItem(
      isSelected: selectedRoute == route,
      icon: icon,
      title: title,
      minWidth: itemWidth,
      onTap: () 
        if (onSelectRoute != null) 
          onSelectRoute(route);
        
      ,
    );
  

import 'package:flutter/material.dart';

class BottomNavBarItem extends StatelessWidget 
  final bool isSelected;
  final VoidCallback onTap;
  final Widget icon;
  final String title;
  final double minWidth;

  BottomNavBarItem(
      Key key,
      @required this.icon,
      @required this.title,
      this.isSelected = false,
      this.onTap,
      this.minWidth = 50)
      : super(key: key);

  @override
  Widget build(BuildContext context) 
    return InkWell(
      onTap: this.onTap,
      child: ConstrainedBox(
        constraints: BoxConstraints(minWidth: 0),
        child: Container(
          decoration: BoxDecoration(
              border: Border(
                  top: BorderSide(
                      color: isSelected ? Colors.yellow : Colors.transparent,
                      width: 3.0))),
          padding: const EdgeInsets.only(top: 10, left: 10, right: 10),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              icon,
              Text(
                title.toUpperCase(),
                style: TextStyle(color: Colors.white, fontSize: 9.0),
              )
            ],
          ),
        ),
      ),
    );
  


非常感谢您的帮助

在 BottomNavItems 上使用 Hero 有问题

【问题讨论】:

它来自左/下的原因是因为您使用的是导航 @quoci 有没有办法用 Navigation 伪造底部导航栏的行为? 如果你不想要动画,你可以看到这个post 非常感谢! 【参考方案1】:

动画来自MaterialPageRoute。导航到Widget 和从Widget 导航时,您需要提供PageRoutePageRoute 负责告诉框架如何从一个屏幕转换到另一个屏幕。通过使用MaterialPageRoute,您将使用其预定义的值来添加您在上面看到的动画。要删除该animation,您可以创建自己的PageRoute,不带动画:

Route _createRoute() 
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => MyPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) 
      return child;
    ,
  );

在导航器中使用_createRoute() 而不是MaterialPageRoute

Navigator.of(context).push(_createRoute());

问题是如何解决的:

  Route<dynamic> _generateRoute(RouteSettings settings) 
    switch (settings.name) 
      case AppRoutes.computer:
        return _createRoute(ComputerScreen());
      case AppRoutes.phone:
        return _createRoute(PhoneScreen());
      case AppRoutes.person:
      default:
        return _createRoute(PersonScreen());
    
  


Route _createRoute(Widget screen) 
  return PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => screen,
    transitionsBuilder: (context, animation, secondaryAnimation, child) 
      return child;
    ,
  );

【讨论】:

非常感谢! 不客气。我很高兴它帮助了@DimitriLeurs

以上是关于Flutter BaseScreen 不断重建/不想要的重建的主要内容,如果未能解决你的问题,请参考以下文章

Flutter Streambuilder 不断重建

Flutter 令人费解的流行为:setState 调用但不重建 widget 树

Flutter:StreamProvider 的奇怪行为,使用不完整数据重建的小部件

Flutter:检测任何在屏幕上不可见但在小部件树中的小部件的重建

Flutter - 无需用户交互即可重建 GestureDetector 小部件

Flutter Provider-重建列表项而不是列表视图