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

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实现:

  1. 在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();
}
  1. 监听键盘状态变化,控制动画播放/反向播放:
@override
void didChangeDependencies() {
  super.didChangeDependencies();
  bool currentKeyboardState = MediaQuery.of(context).viewInsets.bottom != 0;
  if (currentKeyboardState != _isKeyboardOpen) {
    _isKeyboardOpen = currentKeyboardState;
    _isKeyboardOpen ? _controller.forward() : _controller.reverse();
  }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 18:10:35