Flutter实现Logo大小切换的平滑过渡动画方案咨询
Flutter登录页Logo键盘状态切换时的平滑过渡动画实现
要解决Logo在键盘弹出/收起时的跳变问题,你可以用以下几种方案实现平滑过渡:
方案一:使用AnimatedContainer(最简单)
AnimatedContainer会自动对自身属性变化应用过渡动画,只需指定动画时长即可,非常适配这种简单的尺寸切换场景。
修改你的代码,将包裹Image的普通Container替换为AnimatedContainer:
@override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; bool isKeyboardOpen = MediaQuery.of(context).viewInsets.bottom != 0; return Scaffold( resizeToAvoidBottomInset: true, backgroundColor: AppColors.yellowLight, body: SingleChildScrollView( physics: BouncingScrollPhysics(), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ // 替换为AnimatedContainer实现平滑过渡 AnimatedContainer( duration: const Duration(milliseconds: 300), // 动画时长,可按需调整 curve: Curves.easeOut, // 动画曲线,让过渡更自然 padding: EdgeInsets.symmetric(vertical: isKeyboardOpen ? 30 : 90), child: Image( image: const ExactAssetImage('assets/icons/app_logo.png'), height: isKeyboardOpen ? size.height * 0.15 : size.height * 0.3, fit: BoxFit.fitHeight, alignment: Alignment.center, ), ), // 以下输入框、按钮部分保持不变 Padding( padding: EdgeInsets.symmetric(horizontal: 50), child: MainTextField( controller: emailController, labelText: 'E-post', hintText: 'Skriv e-post som abc@epost.no'), ), Padding( padding: const EdgeInsets.only( left: 50.0, right: 50.0, top: 30, bottom: 30), child: MainTextField( controller: passwordController, obscureText: true, labelText: 'Passord', hintText: 'Skriv inn sikkert passord', ), ), LoginButton( text: 'Logg inn', isLoading: isLoading, onPressed: _login, ), ], ), ), ), ); }
关键修改点:
- 替换
Container为AnimatedContainer,通过duration定义动画时长(推荐300-500ms) - 可选添加
curve参数自定义动画曲线,让过渡更符合视觉预期 - 同步调整
padding的动画,避免Logo大小变化时上下间距突然跳变,保证整体过渡协调
方案二:使用AnimationController自定义动画(更灵活)
如果需要更精细的动画控制(比如中途暂停、自定义动画轨迹),可以用AnimationController结合AnimatedBuilder实现:
- 在State类中定义动画相关变量:
late AnimationController _controller; late Animation<double> _logoHeightAnimation; late Animation<double> _paddingAnimation; bool _isKeyboardOpen = false; @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); // 初始化高度动画:从0.3倍屏幕高度过渡到0.15倍 _logoHeightAnimation = Tween<double>(begin: 0.3, end: 0.15).animate( CurvedAnimation(parent: _controller, curve: Curves.easeOut), ); // 初始化padding动画:从90过渡到30 _paddingAnimation = Tween<double>(begin: 90, end: 30).animate( CurvedAnimation(parent: _controller, curve: Curves.easeOut), ); } @override void dispose() { _controller.dispose(); super.dispose(); }
- 监听键盘状态变化,控制动画播放/反向播放:
@override void didChangeDependencies() { super.didChangeDependencies(); bool currentKeyboardState = MediaQuery.of(context).viewInsets.bottom != 0; if (currentKeyboardState != _isKeyboardOpen) { _isKeyboardOpen = currentKeyboardState; _isKeyboardOpen ? _controller.forward() : _controller.reverse(); } }
- 在build中使用AnimatedBuilder构建UI:
@override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; return Scaffold( resizeToAvoidBottomInset: true, backgroundColor: AppColors.yellowLight, body: SingleChildScrollView( physics: BouncingScrollPhysics(), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ AnimatedBuilder( animation: _controller, builder: (context, child) { return Container( padding: EdgeInsets.symmetric(vertical: _paddingAnimation.value), child: Image( image: const ExactAssetImage('assets/icons/app_logo.png'), height: size.height * _logoHeightAnimation.value, fit: BoxFit.fitHeight, alignment: Alignment.center, ), ); }, ), // 以下输入框、按钮部分保持不变 Padding( padding: EdgeInsets.symmetric(horizontal: 50), child: MainTextField( controller: emailController, labelText: 'E-post', hintText: 'Skriv e-post som abc@epost.no'), ), Padding( padding: const EdgeInsets.only( left: 50.0, right: 50.0, top: 30, bottom: 30), child: MainTextField( controller: passwordController, obscureText: true, labelText: 'Passord', hintText: 'Skriv inn sikkert passord', ), ), LoginButton( text: 'Logg inn', isLoading: isLoading, onPressed: _login, ), ], ), ), ), ); }
方案优势:
- 可完全自定义动画的曲线、时长,甚至添加多轨道动画
- 适配需要复杂交互逻辑的场景
注意事项:
- 保持
resizeToAvoidBottomInset: true,避免键盘遮挡输入框 - 动画时长建议设置在300-500ms之间,兼顾过渡自然度和用户等待体验
- 如果Logo有固定宽高比,建议同步调整宽度或使用
BoxFit.contain避免拉伸变形
内容的提问来源于stack exchange,提问作者Marius Sørensen
相关产品推荐
相关产品推荐

