You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现可调整大小、滚动、滑动关闭的Flutter底部组件?

问题描述

我正在学习某YouTube视频里的设计模式,目前我的Flutter页面核心代码如下:

Widget build(BuildContext context) {
    super.build(context);
    RideServices rideServices = ride_services(context);
    return Scaffold(
      resizeToAvoidBottomInset: true,
      key: scaffoldKey,
      extendBodyBehindAppBar: true,
      body: SwipeDrawer(
        radius: 20,
        key: drawerKey,
        drawer: Theme(
          data: Theme.of(context).copyWith(
            canvasColor: Colors.blue[400],
          ),
          child: Drawer_widget(),
        ),
        hasClone: false,
        bodyBackgroundPeekSize: 30,
        backgroundColor: Colors.blue[400],
        child: Stack(
          children: [
            GOOGLE_container(),
            RefreshWidget(),
            my_location(),
            BottomBar(
              mapController: osmController,
              saveRiderAndSearchNearRidecallback: (listDriver) async {
              },
            ),
            drawer(drawerKey: drawerKey),
            SaveLocationCheckMark(key: UniqueKey(), saveLocation: saveLocation),
            detail_driver(),
            splash_screen()
          ],
        ),
      ),
    );
  }

其中底部的BottomBar组件我用IndexedStack实现,代码如下:

@override
  Widget build(BuildContext context) {
    return Positioned(
      top: 67.5.h,
      bottom: 0,
      left: 0,
      right: 0,
      child: Selector<ProviderService, int>(
        selector: (ctx, prod) => prod.indexWidgetInformationPicker,
        shouldRebuild: (nPage, oldPage) => nPage != oldPage,
        builder: (ctx, indexPage, child) {
          return IndexedStack(
            index: indexPage,
            sizing: StackFit.passthrough,
            children: [
              pickUpDestination(
                  widget: widget,
                  my_loc_controller: my_loc_controller),
              BestRouteCalculator(
                  colorizeTextStyle: colorizeTextStyle,
                  colorizeColors: colorizeColors),
              pickRide(
                widget: widget,
                my_loc_controller: my_loc_controller,
              ),
              FindADriver(
                  key: UniqueKey(),
                  colorizeTextStyle: colorizeTextStyle,
                  colorizeColors: colorizeColors),

              RideStarted(),
            ],
          );
        },
      ),
    );
  }

现在遇到两个问题:

  1. 我的IndexedStack无法调整子组件大小(均为固定尺寸),想知道视频中使用的是什么组件?
  2. IndexedStack会同时构建所有子组件,但我需要按需显示/隐藏;另外想要实现视频中那种可调整大小、支持滚动/滑动、能关闭的底部弹窗效果,该怎么修改现有代码?

解决方案

一、视频中大概率使用的组件:DraggableScrollableSheet

这个组件是Flutter官方提供的,天生支持:

  • 拖拽调整底部弹窗的高度
  • 内部可嵌套滚动组件(如ListView)实现内容滚动
  • 仅构建当前显示的内容,不会一次性加载所有子组件
    完全匹配你提到的视频中底部弹窗的特性。

二、替换IndexedStack的具体实现

1. 核心修改:用DraggableScrollableSheet替代Positioned+IndexedStack

修改BottomBar的代码,结合Provider的Selector实现按需渲染和动态切换:

@override
Widget build(BuildContext context) {
  return Selector<ProviderService, int>(
    selector: (ctx, prod) => prod.indexWidgetInformationPicker,
    shouldRebuild: (nPage, oldPage) => nPage != oldPage,
    builder: (ctx, indexPage, child) {
      // 控制弹窗是否显示,比如index为-1时隐藏
      if (indexPage == -1) return const SizedBox.shrink();
      
      return DraggableScrollableSheet(
        initialChildSize: 0.325, // 对应原Positioned的top:67.5h(屏幕高度占比32.5%)
        minChildSize: 0.1, // 最小高度,拖拽到此处可触发关闭
        maxChildSize: 0.9, // 最大可展开高度
        expand: false,
        builder: (context, scrollController) {
          // 根据当前index返回对应组件
          Widget currentChild;
          switch(indexPage) {
            case 0:
              currentChild = pickUpDestination(
                widget: widget,
                my_loc_controller: my_loc_controller,
              );
              break;
            case 1:
              currentChild = BestRouteCalculator(
                colorizeTextStyle: colorizeTextStyle,
                colorizeColors: colorizeColors,
              );
              break;
            case 2:
              currentChild = pickRide(
                widget: widget,
                my_loc_controller: my_loc_controller,
              );
              break;
            case 3:
              currentChild = FindADriver(
                key: UniqueKey(),
                colorizeTextStyle: colorizeTextStyle,
                colorizeColors: colorizeColors,
              );
              break;
            case 4:
              currentChild = RideStarted();
              break;
            default:
              currentChild = const SizedBox.shrink();
          }

          // 包裹ListView实现滚动,添加拖拽指示器和关闭按钮
          return ListView(
            controller: scrollController,
            shrinkWrap: true,
            children: [
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    // 拖拽指示器
                    Container(
                      width: 40,
                      height: 4,
                      decoration: BoxDecoration(
                        color: Colors.grey[300],
                        borderRadius: BorderRadius.circular(2),
                      ),
                    ),
                    // 关闭按钮
                    IconButton(
                      icon: const Icon(Icons.close),
                      onPressed: () {
                        // 触发关闭逻辑,比如更新Provider的index为-1
                        Provider.of<ProviderService>(context, listen: false)
                            .updateIndex(-1);
                      },
                    ),
                  ],
                ),
              ),
              currentChild,
            ],
          );
        },
      );
    },
  );
}

2. 关键特性说明

  • 按需构建:通过switch-case仅返回当前index对应的组件,解决了IndexedStack一次性构建所有子组件的问题
  • 大小调整:通过initialChildSize/minChildSize/maxChildSize控制弹窗的初始、最小、最大高度,用户还能拖拽调整
  • 滚动支持:内部用ListView包裹子组件,配合scrollController实现内容滚动
  • 滑动关闭:拖拽弹窗到minChildSize的高度时,可在Provider中监听高度变化触发关闭;也可通过顶部的关闭按钮手动关闭

内容的提问来源于stack exchange,提问作者Pannam T

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:13:37