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

Flutter实现SingleChildScrollView底部联动DraggableScrollableSheet

Flutter:实现SingleChildScrollView底部联动DraggableScrollableSheet

需求说明

我需要在Flutter中实现以下效果:当SingleChildScrollView滚动至底部时,DraggableScrollableSheet恰好贴合在其底部,且可通过顶部手柄拖动调整高度,拖动过程中Sheet内部内容能正常滚动。

当前实现代码

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Where are you going today?'),
      ),
      body: Stack(
        children: [
          SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Text(
                    'Hi, Vidhan!',
                    style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 16.0),
                  child: Text(
                    'Your location',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Container(
                    height: 200,
                    color: Colors.grey[300],
                    child: Center(
                      child: Text('Map Placeholder'),
                    ),
                  ),
                ),
                SizedBox(height: 200), // 占位内容,用于展示滚动效果
              ],
            ),
          ),
          DraggableScrollableSheet(
            initialChildSize: 0.3,
            minChildSize: 0.1,
            maxChildSize: 0.7,
            builder: (BuildContext context, ScrollController scrollController) {
              return Container(
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(20),
                    topRight: Radius.circular(20),
                  ),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black12,
                      blurRadius: 10,
                      spreadRadius: 5,
                    ),
                  ],
                ),
                child: Column(
                  children: [
                    Container(
                      margin: EdgeInsets.symmetric(vertical: 10),
                      width: 40,
                      height: 5,
                      decoration: BoxDecoration(
                        color: Colors.grey,
                        borderRadius: BorderRadius.circular(5),
                      ),
                    ),
                    Expanded(
                      child: ListView(
                        controller: scrollController,
                        children: [
                          ListTile(
                            title: Text('Where to?'),
                            trailing: Text('Manage'),
                          ),
                          GridView.count(
                            padding: EdgeInsets.all(16),
                            shrinkWrap: true,
                            crossAxisCount: 2,
                            crossAxisSpacing: 8,
                            mainAxisSpacing: 8,
                            children: [
                              LocationCard(title: 'Home', location: 'Mathura, India'),
                              LocationCard(title: 'Office', location: 'Mumbai, India'),
                              LocationCard(title: 'Salon', location: 'Mathura, India'),
                              LocationCard(title: "Friend's Home", location: 'Agra, India'),
                            ],
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

解决方案

当前Stack布局会让DraggableScrollableSheet始终悬浮在SingleChildScrollView上方,无法实现“滚动到底部才贴合”的联动效果。推荐使用NestedScrollView重构布局,实现自然的滚动衔接:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Where are you going today?'),
      ),
      body: NestedScrollView(
        // 顶部滚动内容
        headerSliverBuilder: (context, innerBoxIsScrolled) => [
          SliverList(
            delegate: SliverChildListDelegate([
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Text(
                  'Hi, Vidhan!',
                  style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16.0),
                child: Text(
                  'Your location',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(16.0),
                child: Container(
                  height: 200,
                  color: Colors.grey[300],
                  child: Center(
                    child: Text('Map Placeholder'),
                  ),
                ),
              ),
              SizedBox(height: 200), // 占位内容
            ]),
          ),
        ],
        // 底部可拖动Sheet
        body: DraggableScrollableSheet(
          initialChildSize: 0.3,
          minChildSize: 0.1,
          maxChildSize: 0.7,
          builder: (BuildContext context, ScrollController scrollController) {
            return Container(
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(20),
                  topRight: Radius.circular(20),
                ),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black12,
                    blurRadius: 10,
                    spreadRadius: 5,
                  ),
                ],
              ),
              child: Column(
                children: [
                  // 拖动手柄
                  Container(
                    margin: EdgeInsets.symmetric(vertical: 10),
                    width: 40,
                    height: 5,
                    decoration: BoxDecoration(
                      color: Colors.grey,
                      borderRadius: BorderRadius.circular(5),
                    ),
                  ),
                  Expanded(
                    child: ListView(
                      controller: scrollController,
                      children: [
                        ListTile(
                          title: Text('Where to?'),
                          trailing: Text('Manage'),
                        ),
                        GridView.count(
                          padding: EdgeInsets.all(16),
                          shrinkWrap: true,
                          physics: NeverScrollableScrollPhysics(), // 禁用自身滚动,避免和外层控制器冲突
                          crossAxisCount: 2,
                          crossAxisSpacing: 8,
                          mainAxisSpacing: 8,
                          children: [
                            LocationCard(title: 'Home', location: 'Mathura, India'),
                            LocationCard(title: 'Office', location: 'Mumbai, India'),
                            LocationCard(title: 'Salon', location: 'Mathura, India'),
                            LocationCard(title: "Friend's Home", location: 'Agra, India'),
                          ],
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

// 补充LocationCard实现
class LocationCard extends StatelessWidget {
  final String title;
  final String location;

  const LocationCard({required this.title, required this.location, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.grey[100],
        borderRadius: BorderRadius.circular(12),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(title, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
          SizedBox(height: 4),
          Text(location, style: TextStyle(fontSize: 12, color: Colors.grey[600])),
        ],
      ),
    );
  }
}

关键修改点

  • 用NestedScrollView替代Stack,让顶部内容作为可滚动的Sliver组件,底部Sheet作为body,实现滚动到底部时自然衔接;
  • 给内部GridView添加NeverScrollableScrollPhysics(),避免和DraggableScrollableSheet的滚动控制器产生冲突;
  • 补充了LocationCard的完整实现,修复原代码中的字符串引号错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 07:44:57