分享一个实现侧滑菜单的Flutter页面所遇到的问题与解决思路

最近做了一个需要实现侧滑菜单相关的Flutter新页面,页面布局结构稍微比较复杂。因此,做完之后就对研发的过程做出一些整理。
以下主要整理跟侧滑菜单相关的内容。直奔主题,首先,要实现侧滑菜单,有以下几个方案。而本次选取的方案,是使用Scaffold 的 endDrawer的方式。在使用这个方案的同时,也是遇到了一些问题……

方案一:使用 Scaffold 的 endDrawer 属性

简单例子:

当需要在Scaffold中使用endDrawer时,可以按照以下简单例子进行操作:



class TestPage extends StatelessWidget {
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

  
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text('Scaffold with endDrawer'),
      ),
      body: Center(
        child: Text('Hello, World!'),
      ),
      endDrawer: Drawer(
        child: ListView(
          children: [
            ListTile(
              leading: Icon(Icons.settings),
              title: Text('Settings'),
            ),
            ListTile(
              leading: Icon(Icons.help),
              title: Text('Help'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _scaffoldKey.currentState!.openEndDrawer();
        },
        child: Icon(Icons.menu),
      ),
    );
  }
}

在这个例子中,我们创建了一个简单的Scaffold,其中包含一个AppBar、一个中心对齐的文本和一个FloatingActionButton。
通过设置endDrawer属性,我们在Scaffold的右侧添加了一个抽屉式的侧边栏。当用户点击FloatingActionButton时,通过_scaffoldKey.currentState!.openEndDrawer()方法打开endDrawer。

项目实现卡点与解决方案

事实证明,需求永远没有那么简单实现,比如,现在需要实现的是:类似Android中,viewpager的方式。这可以这么实现:page中套着一层ExtendedNestedScrollView,存在着多个TabBarView。
而卡点就在于,弹出侧滑菜单的点击事件是在TabBarView中。因此,如果直接在TabBarView中通过Scaffold 的 endDrawer 实现抽屉效果。则会发现,抽屉的大小只有TabBarView那么大,无法铺满屏幕。
因此,解决方案是:
将抽屉布局移到最外层的Scaffold 去实现。然后,内部TabBarView布局里的widget点击事件去调用这个打开这个弹窗。这个方案是可行的,但是麻烦的是内外层之间的数据交互,但是也有两个方案去实现:

  1. 通过回调的方式实现。但是,实践之后,会发现,回调存在一个问题。就是抽屉布局里的操作,需要更新到最外层page的UI效果和数据,同时也需要更新到内部TabBarView布局里的数据进行刷新相关处理。而TabBarView内的数据,产品又要求需要根据抽屉布局内部的筛选数据,及时更新,加上TabBarView有很多tab,因此,这块从性能上考虑,是必须做懒加载处理的。而Flutter其实已经默认实现了,这是一个点。
    因此,回调的方式,一来,我需要在外部去持有这个内部page,二来,需要在page内部处理回调更新相关操作。而一个比较复杂的场景是:首次进入页面,TabBarView内部的数据是懒加载,此时是没有更新数据,也没有执行相关操作。因此,如果使用回调的方式,会导致非当前页面的tab数据出现错乱(没有使用筛选条件请求接口)。而如果每个页面都加载过了之后,后续又难以响应筛选条件的变化。因此,这个方案不适用当前业务场景。

  2. 通过状态管理的方式实现。通过外部去持有内部的ViewModel,当数据筛选的时候,再去调用内部的ViewMode的对应方法。并在这个过程中去处理上面所说的两个问题。通过标志进行判断是否初次加载,通过筛选项变化监听去判断是否需要重新加载数据。但是,这里有一个需要值得注意的地方。因为在使用Scaffold中使用endDrawer时,页面会重新加载:

  • 当我们在Scaffold中设置了endDrawer属性时,endDrawer实际上是作为Scaffold的子Widget存在的。
  • 当我们打开或关闭endDrawer时,Scaffold的状态会发生变化,因为endDrawer的显示状态是Scaffold的一部分。
  • 当Scaffold的状态发生变化时,Flutter框架会调用build方法来重新构建Scaffold及其子树。
  • 在重新构建过程中,Flutter会重新创建endDrawer及其子树,以确保它们与新的状态匹配。

因此,当我们打开或关闭endDrawer时,Scaffold会重新加载页面,因为endDrawer是Scaffold的一部分,而Scaffold的状态发生了变化。而又因为这个需求业务的需要,页面是需要请求接口,才能确定tab数据,然后再进行创建ExtendedNestedScrollView、TabBarView等系列Widget的。因此,这里的创建内部Page,需要唯一性,不能在builder属性中进行创建,否则这会导致重复创建,进而显示异常问题。需要把这段创建的代码抽出来,并只在接口请求结束后执行一次。

方案二:使用 showModalBottomSheet。

Flutter的showModalBottomSheet函数是一个非常有用的方法,它可以在屏幕底部显示一个模态底部表单或菜单。它通常用于显示与当前页面相关的附加信息或操作。

showModalBottomSheet函数有几个参数,下面是它们的详细说明:

  1. context:BuildContext对象,用于获取当前页面的上下文。
  2. builder:WidgetBuilder函数,用于构建底部表单或菜单的内容。它接收一个BuildContext参数,并返回一个Widget。
  3. isScrollControlled:一个可选的布尔值,用于指定是否将底部表单或菜单的高度限制为屏幕高度的一部分。默认情况下,它为false,即底部表单或菜单的高度将适应内容的高度。
  4. backgroundColor:一个可选的颜色值,用于设置底部表单或菜单的背景颜色。
  5. shape:一个可选的ShapeBorder对象,用于设置底部表单或菜单的形状。
  6. elevation:一个可选的double值,用于设置底部表单或菜单的阴影高度。

下面是一个使用showModalBottomSheet函数的例子:

void _showBottomSheet(BuildContext context) {
  showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      builder: (BuildContext context) {
        return Test(
            isFree: isFree,
            goodsLiningModel: data,
            fabricContent: fabricContent);
      });
}

缺点:少了侧滑效果,另外状态栏的处理相比Scaffold 的 endDrawer,处理起来笔记麻烦点。更适合于从底部弹起。可以作为备选方案。

方案三:通过Stack的方式

将主布局和抽屉布局包裹起来。根据操作来判断抽屉布局的显示还是隐藏。
这个就不细说了。主要就是通过Visibility来进行控制。

你可能感兴趣的:(#,Flutter——实操,flutter)