背景

我在用flutter仿写微信APP时,遇到一个问题,那就是搜索功能里有一个页面设置按钮,

  1. 点击页面设置,会从底部弹出一个页面配置弹框:BottomSheet弹框层(该层也是一个路由)
  2. 点击搜索指定内容Tile,会跳转到一个页面。包含各种开关的配置
  3. 点击配置页的返回按钮,又会返回到页面配置。

在这里插入图片描述

为方便描述,我们将页面描述为别名

  • 页面A:页面配置
  • 页面B:开关配置详情

我们发现

  • 页面A和页面B,都在BottomSheet层级中,且Bottom层级高度固定;
  • 从页面A到页面B,有从右到左的动画效果
  • 点击页面B的返回按钮,有从左到右然后消失的动画效果

思路

我们都知道,flutter中从一个路由跳转到另一个路由,有两种常用的方式

  • Navigator.push:直接跳转到路由的组件页面
  • Navigator.pushNamed:命名路由,更优雅

分析

但是上面的两种方式,都是基于页面级别的路由,如果还用Navigator路由跳转的方式,那么会把页面全屏化。
而现在需要把路由放在showModalBottomSheet中,有人想这么做:

  1. 点击页面设置按钮时,我调用showModalBottomSheet显示页面A;
  2. 点击搜索指定内容Tile时,我再次调用showModalBottomSheet显示页面B;
  3. 把高度设置成一样的就行了;

上述方法能实现功能,但是体验会大打折扣,因为:

  • 2次调用showModalBottomSheet虽然不会有太大的性能问题,但是不优雅;
  • 页面A、B之间的跳转与返回,无动画效果,每次都是从底部弹出BottomSheet层;
  • 且每次AB页面切换时,BottomSheet层的顶部圆角,会从无到有,过渡非常不自然;

解决方案

需要使用 PageRouteBuilder 结合动画来实现上述功能,把页面A、页面B放在封装的SharedBottomSheetNavigator组件中,默认显示页面A,跳转时指向页面B,且使用自定义的动画效果,页面B返回页面A同理,其代码如下

import 'package:flutter/material.dart';
import './page_setting.dart'; // 页面A
import './switch_list_page.dart'; // 页面B

class SharedBottomSheetNavigator extends StatelessWidget {
  const SharedBottomSheetNavigator({super.key});

  static void show(BuildContext context) {
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.white,
      constraints: BoxConstraints(maxHeight: 600),
      builder: (context) {
        return ClipRRect(
          borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
          child: ColoredBox(
            color: Colors.white,
            child: Navigator(
              key: GlobalKey<NavigatorState>(),
              initialRoute: '/',
              onGenerateRoute: (settings) {
                return PageRouteBuilder(
                  settings: settings,
                  pageBuilder: (context, animation, secondaryAnimation) {
                    switch (settings.name) {
                      case '/':
                        return const PageSetting();
                      case '/switch':
                        return const MultipleSwitchList();
                      default:
                        return const PageSetting();
                    }
                  },
                  transitionsBuilder: (context, animation, secondaryAnimation, child) {
                    const begin = Offset(1.0, 0.0);
                    const end = Offset.zero;
                    const curve = Curves.ease;

                    final tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));

                    return SlideTransition(
                      position: animation.drive(tween),
                      child: child,
                    );
                  },
                );
              },
            ),
          ),
        );
      },
    );
  }

  
  Widget build(BuildContext context) {
    return const SizedBox.shrink();
  }
}

点击页面A进入页面B时,只需要跳转这个特定的路由即可

Navigator.pushNamed(context, '/switch');

其最终效果如下,有兴趣的同学可以试试
在这里插入图片描述

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐