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

Flutter中实现按钮Hero动画至上方容器的方法求助

同一页面内按钮移动的Hero式动画实现方案

方案1:原生AnimatedPositioned实现(无第三方依赖)

这是最轻量化的原生实现方式,通过状态变量控制按钮位置,配合AnimatedPositioned完成平滑过渡,完全适配Flutter新版本。

核心逻辑:用Stack布局承载按钮和目标容器,通过setState切换按钮的位置状态,AnimatedPositioned自动处理过渡动画。

示例代码:

import 'package:flutter/material.dart';

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

  @override
  State<ButtonMoveAnimation> createState() => _ButtonMoveAnimationState();
}

class _ButtonMoveAnimationState extends State<ButtonMoveAnimation> {
  bool _isButtonInTopContainer = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('按钮移动动画')),
      body: Stack(
        children: [
          // 上方目标容器
          Positioned(
            top: 60,
            left: 24,
            child: Container(
              width: 120,
              height: 50,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.lightBlueAccent),
                borderRadius: BorderRadius.circular(8),
              ),
              child: _isButtonInTopContainer ? _buildActionButton() : const SizedBox.shrink(),
            ),
          ),
          // 可移动的按钮(初始在下方)
          AnimatedPositioned(
            duration: const Duration(milliseconds: 350),
            curve: Curves.easeInOut,
            top: _isButtonInTopContainer ? 60 : 320,
            left: 24,
            child: _isButtonInTopContainer 
                ? const SizedBox.shrink() 
                : _buildActionButton(),
          ),
        ],
      ),
    );
  }

  Widget _buildActionButton() {
    return ElevatedButton(
      onPressed: () => setState(() => _isButtonInTopContainer = !_isButtonInTopContainer),
      style: ElevatedButton.styleFrom(
        fixedSize: const Size(120, 50),
        backgroundColor: Colors.lightBlueAccent,
        foregroundColor: Colors.white,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
      ),
      child: Text(_isButtonInTopContainer ? '点击返回' : '点击移动'),
    );
  }
}

方案2:利用Hero组件模拟页面内过渡

如果需要复用Hero的共享元素过渡效果(比如阴影、缩放联动),可以通过临时插入透明路由的方式触发Hero动画,实现页面内的Hero效果。

示例代码:

import 'package:flutter/material.dart';

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

  @override
  State<HeroButtonTransition> createState() => _HeroButtonTransitionState();
}

class _HeroButtonTransitionState extends State<HeroButtonTransition> {
  bool _isButtonMoved = false;

  void _startHeroAnimation() {
    Navigator.of(context).push(
      PageRouteBuilder(
        opaque: false,
        transitionDuration: const Duration(milliseconds: 350),
        pageBuilder: (_, __, ___) => Scaffold(
          backgroundColor: Colors.transparent,
          body: Stack(
            children: [
              Positioned(
                top: 60,
                left: 24,
                child: Hero(
                  tag: 'moveable_button',
                  child: _buildActionButton(),
                ),
              ),
            ],
          ),
        ),
      ),
    ).then((_) => setState(() => _isButtonMoved = true));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Hero式按钮移动')),
      body: Stack(
        children: [
          // 上方目标容器
          Positioned(
            top: 60,
            left: 24,
            child: Container(
              width: 120,
              height: 50,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.lightBlueAccent),
                borderRadius: BorderRadius.circular(8),
              ),
              child: _isButtonMoved
                  ? Hero(tag: 'moveable_button', child: _buildActionButton())
                  : const SizedBox.shrink(),
            ),
          ),
          // 初始位置的按钮
          if (!_isButtonMoved)
            Positioned(
              top: 320,
              left: 24,
              child: Hero(
                tag: 'moveable_button',
                child: ElevatedButton(
                  onPressed: _startHeroAnimation,
                  style: ElevatedButton.styleFrom(
                    fixedSize: const Size(120, 50),
                    backgroundColor: Colors.lightBlueAccent,
                    foregroundColor: Colors.white,
                    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
                  ),
                  child: const Text('点击移动'),
                ),
              ),
            ),
        ],
      ),
    );
  }

  Widget _buildActionButton() {
    return ElevatedButton(
      onPressed: () {
        if (_isButtonMoved) setState(() => _isButtonMoved = false);
      },
      style: ElevatedButton.styleFrom(
        fixedSize: const Size(120, 50),
        backgroundColor: Colors.lightBlueAccent,
        foregroundColor: Colors.white,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
      ),
      child: const Text('点击返回'),
    );
  }
}

方案3:第三方库flutter_animate实现灵活动画

如果需要更丰富的动画组合(比如移动+缩放+淡入),可以使用flutter_animate库,它适配Flutter新版本,API简洁灵活。

  1. 先在pubspec.yaml添加依赖:
dependencies:
  flutter_animate: ^4.5.0
  1. 示例代码:
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';

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

  @override
  State<AnimateButtonMove> createState() => _AnimateButtonMoveState();
}

class _AnimateButtonMoveState extends State<AnimateButtonMove> {
  bool _isMoved = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('按钮移动动画')),
      body: Stack(
        children: [
          // 上方目标容器
          Positioned(
            top: 60,
            left: 24,
            child: Container(
              width: 120,
              height: 50,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.lightBlueAccent),
                borderRadius: BorderRadius.circular(8),
              ),
            ),
          ),
          // 可移动按钮
          Positioned(
            top: _isMoved ? 60 : 320,
            left: 24,
            child: ElevatedButton(
              onPressed: () => setState(() => _isMoved = !_isMoved),
              style: ElevatedButton.styleFrom(
                fixedSize: const Size(120, 50),
                backgroundColor: Colors.lightBlueAccent,
                foregroundColor: Colors.white,
                shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
              ),
              child: Text(_isMoved ? '点击返回' : '点击移动'),
            ).animate().move(
                  duration: 350.ms,
                  curve: Curves.easeInOut,
                ).scale(
                  begin: const Offset(1, 1),
                  end: const Offset(1, 1), // 如需缩放可修改end值
                  duration: 350.ms,
                ),
          ),
        ],
      ),
    );
  }
}

选型建议

  • 追求轻量化、无依赖:选方案1;
  • 需要Hero共享元素的过渡质感:选方案2;
  • 需组合多种动画效果:选方案3。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 08:26:32