如何实现小条状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
相关产品推荐
相关产品推荐

