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

如何实现小条状Widget全屏展开与收起功能?

实现始终显示在导航栏上方的可展开收起底部组件

要实现这种始终保留顶部小条、支持滑动/点击展开收起的底部组件,不需要依赖第三方BottomSheet类库,直接用Flutter原生组件就能完成,以下是两种可行方案:

方案一:基于DraggableScrollableSheet实现(推荐,自带滚动支持)

利用DraggableScrollableSheet的minChildSize属性控制收起时的最小高度,确保小条始终显示,同时支持滑动展开/收起,搭配控制器还能实现点击切换逻辑:

import 'package:flutter/material.dart';

class PersistentBottomBar extends StatefulWidget {
  const PersistentBottomBar({super.key});

  @override
  State<PersistentBottomBar> createState() => _PersistentBottomBarState();
}

class _PersistentBottomBarState extends State<PersistentBottomBar> {
  final DraggableScrollableController _scrollController = DraggableScrollableController();
  bool _isExpanded = false;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      setState(() {
        _isExpanded = _scrollController.size > 0.1;
      });
    });
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  void _toggleExpand() {
    if (_isExpanded) {
      _scrollController.animateTo(0.08, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
    } else {
      _scrollController.animateTo(0.7, duration: const Duration(milliseconds: 300), curve: Curves.easeIn);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Persistent Bottom Bar Demo')),
      body: Stack(
        children: [
          // 页面主体内容
          const Center(child: Text('主页面内容区域')),
          // 底部可展开组件
          DraggableScrollableSheet(
            controller: _scrollController,
            initialChildSize: 0.08, // 初始高度占屏幕8%
            minChildSize: 0.08, // 收起时最小高度,确保小条不消失
            maxChildSize: 0.7, // 展开时最大高度占屏幕70%
            expand: false,
            builder: (context, scrollController) {
              return Container(
                decoration: const BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
                  boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8)],
                ),
                child: Column(
                  children: [
                    // 顶部操作条(始终显示)
                    GestureDetector(
                      onTap: _toggleExpand,
                      child: Container(
                        padding: const EdgeInsets.symmetric(vertical: 8),
                        child: Column(
                          children: [
                            Container(
                              width: 40,
                              height: 4,
                              decoration: BoxDecoration(
                                color: Colors.grey[300],
                                borderRadius: BorderRadius.circular(2),
                              ),
                            ),
                            const SizedBox(height: 4),
                            Text(_isExpanded ? '点击收起' : '点击展开'),
                          ],
                        ),
                      ),
                    ),
                    // 展开后的滚动内容区
                    Expanded(
                      child: ListView.builder(
                        controller: scrollController,
                        itemCount: 20,
                        itemBuilder: (context, index) {
                          return ListTile(title: Text('列表项 $index'));
                        },
                      ),
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

关键说明

  • minChildSize:设置收起时的最小高度,确保组件不会完全隐藏
  • DraggableScrollableController:用于精准控制展开/收起的动画过渡
  • 顶部GestureDetector:添加点击切换逻辑,和滑动操作形成互补的交互体验

方案二:自定义手势控制的底部组件(无滚动需求时使用)

如果不需要滚动内容,可以直接用GestureDetector监听拖动事件,配合AnimatedContainer实现平滑高度变化:

import 'package:flutter/material.dart';

class CustomPersistentBottomBar extends StatefulWidget {
  const CustomPersistentBottomBar({super.key});

  @override
  State<CustomPersistentBottomBar> createState() => _CustomPersistentBottomBarState();
}

class _CustomPersistentBottomBarState extends State<CustomPersistentBottomBar> {
  double _height = 60; // 初始小条高度
  final double _minHeight = 60;
  final double _maxHeight = 400;

  void _onDragUpdate(DragUpdateDetails details) {
    setState(() {
      _height -= details.delta.dy;
      _height = _height.clamp(_minHeight, _maxHeight);
    });
  }

  void _toggleHeight() {
    setState(() {
      _height = _height == _minHeight ? _maxHeight : _minHeight;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('自定义底部组件')),
      body: Stack(
        children: [
          const Center(child: Text('主页面内容区域')),
          Positioned(
            left: 0,
            right: 0,
            bottom: 0,
            child: GestureDetector(
              onVerticalDragUpdate: _onDragUpdate,
              onTap: _toggleHeight,
              child: AnimatedContainer(
                duration: const Duration(milliseconds: 200),
                height: _height,
                decoration: const BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
                  boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 8)],
                ),
                child: Column(
                  children: [
                    Container(
                      padding: const EdgeInsets.symmetric(vertical: 8),
                      child: Column(
                        children: [
                          Container(
                            width: 40,
                            height: 4,
                            decoration: BoxDecoration(
                              color: Colors.grey[300],
                              borderRadius: BorderRadius.circular(2),
                            ),
                          ),
                          const SizedBox(height: 4),
                          Text(_height == _minHeight ? '点击/上滑展开' : '点击/下滑收起'),
                        ],
                      ),
                    ),
                    if (_height > _minHeight)
                      Expanded(
                        child: const Center(child: Text('展开后的内容展示')),
                      ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

关键说明

  • Positioned:将组件固定在屏幕底部,确保始终显示在导航栏下方
  • AnimatedContainer:实现高度变化的平滑过渡动画
  • clamp方法:限制高度在最小/最大值之间,避免超出边界

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 01:52:34