Flutter实现点击箭头展开AppBar的方案咨询
Flutter 实现可点击展开/收起的自定义AppBar
问题描述
我想在Flutter中实现一个点击箭头图标可向下展开的AppBar,当前已有基础的AppBar实现代码,但不知道如何添加展开功能:
AppBar homeAppbar(HomeProvider homeprovider) { return AppBar( leading: Builder(builder: (context) { return IconButton( icon: ImageUtils.imageUtilsInstance .showSVGIcons(menuIcon, height: 30.h), onPressed: () { Scaffold.of(context).openDrawer(); }, ); }), title: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(homeprovider.currentLocation.city!, style: textSemiBold(size: 18, color: colortextBlack)), IconButton( onPressed: () { homeprovider.getCurrentCity(); }, icon: homeprovider.gettingCurrentcity ? SizedBox( height: 20.h, width: 20.h, child: const CircularProgressIndicator( strokeWidth: 1, )) : const Icon( Icons.keyboard_arrow_down_rounded, color: colorGrey74797D, )) ], ), actions: [ AuthService().getUserUID != null ? homeprovider.gettingProfile ? SizedBox( height: 20.h, width: 20.h, child: const CircularProgressIndicator(strokeWidth: 1)) : IconButton( onPressed: () { homeprovider.profilePressed(); }, icon: const Icon( Icons.person, color: colortextBlack, )) : IconButton( onPressed: () { changeScreenReplacement(routeName: signInRoute); }, icon: const Icon( Icons.person, color: colortextBlack, ), ), Padding( padding: EdgeInsets.only(right: 15.w), ) ], ); }
初步尝试方案
通过AppBar的bottom属性结合状态变量控制展开/收起,实现了基础展开效果:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter AppBar 演示', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { // 控制展开状态 bool isOpen = false; // 模拟演示用变量 final String city = '巴黎'; final Color colortextBlack = Colors.black; final bool isLoadingCity = false; final bool isLoadingProfile = false; final bool isUserLoggedIn = false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: Builder( builder: (context) { return IconButton( icon: Icon(Icons.menu), // 演示用图标替换原SVG onPressed: () { Scaffold.of(context).openDrawer(); }, ); }, ), title: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text("应用标题"), IconButton( icon: Icon(isOpen ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down), onPressed: () { setState(() { isOpen = !isOpen; }); }) ], ), bottom: PreferredSize( preferredSize: Size.fromHeight(isOpen ? 120 : 0), child: ClipRRect( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(24.0), bottomRight: Radius.circular(24.0), ), child: isOpen ? Container( height: 120, padding: EdgeInsets.symmetric(horizontal: 16.0), child: Column( children: [ TextField( decoration: InputDecoration( hintText: "输入内容...", ), ), SizedBox(height: 8.0), TextField( decoration: InputDecoration( hintText: "继续输入...", ), ), ], ), decoration: BoxDecoration( color: Colors.white, ), ) : Container(), ), ), actions: [ isUserLoggedIn ? isLoadingProfile ? SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 1)) : IconButton( onPressed: () { // 个人页逻辑 }, icon: Icon(Icons.person, color: colortextBlack), ) : IconButton( onPressed: () { // 跳转登录页 }, icon: Icon(Icons.person, color: colortextBlack), ), Padding( padding: EdgeInsets.only(right: 15), ) ], ), body: Center(child: Text('页面主体内容')), ); } }
最终解决方案
通过自定义AppBar组件结合动画控制器,实现带平滑过渡和遮罩的展开效果:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: '自定义AppBar', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 250)); _animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut); } @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Stack( children: [ ListView.builder( itemCount: 50, itemBuilder: (context, index) { return ListTile( title: Text('条目 $index'), ); }, ), AnimatedBuilder( animation: _controller, builder: (context, child) { return Stack( children: [ if (_controller.value > 0) GestureDetector( onTap: () { if (_controller.isCompleted) { _controller.reverse(); } }, child: Container( color: Colors.black.withOpacity(0.3 * _controller.value), width: double.infinity, height: MediaQuery.of(context).size.height, ), ), child!, ], ); }, child: CustomAppBar( animation: _animation, onArrowTap: () { if (_controller.isCompleted) { _controller.reverse(); } else { _controller.forward(); } }, ), ), ], ), ), ); } @override void dispose() { _controller.dispose(); super.dispose(); } } class CustomAppBar extends StatelessWidget { final Animation<double> animation; final VoidCallback onArrowTap; CustomAppBar({required this.animation, required this.onArrowTap}); @override Widget build(BuildContext context) { return Positioned( top: 0, left: 0, right: 0, child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24), ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(Icons.menu, color: Colors.black), GestureDetector( onTap: onArrowTap, child: Row( children: [ Text("菜单"), SizedBox(width: 8), RotationTransition( turns: animation, child: Icon(Icons.arrow_drop_down, color: Colors.black), ), ], ), ), Icon(Icons.person, color: Colors.black), ], ), ), ClipRRect( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24), ), child: SizeTransition( sizeFactor: animation, child: Container( color: Colors.white, height: 100.0, child: Center(child: Text("展开内容")), ), ), ), ], ), ), ); } }
关键实现点
- 用
AnimationController和CurvedAnimation实现平滑的展开/收起过渡动画 - 自定义
CustomAppBar组件,通过SizeTransition控制展开区域的显示/隐藏 - 添加半透明遮罩,点击遮罩可快速收起展开内容
- 箭头图标通过
RotationTransition实现旋转动画,提升交互体验
内容的提问来源于stack exchange,提问作者MICHEL Baptiste
相关产品推荐
相关产品推荐

