使用GetX实现Flutter物理模拟动画:同步AnimationController与dragAlignment
Flutter GetX 重写物理模拟示例:复位动画失效问题解决
问题描述
我是Flutter新手,了解到GetX可简化开发流程,尝试用GetX重写Flutter官方物理模拟示例。目前已实现拖拽动画,但通过_aniC.animateWith(simulation)实现的物理模拟复位动画无法正常运行。推测问题在于AnimationController执行animateWith(simulation)后,未将动画值同步至_dragAlignment.value,添加addListener()方法也未能解决问题。
问题代码
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter/physics.dart'; //pageview class Home extends StatelessWidget { const Home({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Physics'), actions: const [ Row(children: [Icon(Icons.share)]) ], centerTitle: true, elevation: 0, ), body: Center( child: DraggableCard(child: const FlutterLogo(size: 128)), ), ); } } // widget's view class DraggableCard extends GetView { DraggableCard({required this.child, super.key}); final Widget child; final logic = _DraggableCardLogic(); @override Widget build(BuildContext context) { final size = Get.size; return GetX( init: _DraggableCardLogic(), initState: (_) {}, builder: (_) { return GestureDetector( onPanDown: (details) { logic._aniC.stop(); }, onPanUpdate: (details) { logic._dragAlignment.value += Alignment( details.delta.dx / (size.width / 2), details.delta.dy / (size.height / 2), ); }, onPanEnd: (details) { logic._runAni(details.velocity.pixelsPerSecond, size); }, child: Align( alignment: logic._dragAlignment.value, child: Card(child: child), ), ); }, ); } } //logic&state class _DraggableCardLogic extends GetxController with GetSingleTickerProviderStateMixin { late AnimationController _aniC = AnimationController(vsync: this); final _dragAlignment = Alignment.center.obs; late final _aniBack = _aniC .drive(AlignmentTween( begin: _dragAlignment.value, end: Alignment.center, )) .obs; void _runAni(Offset pixelsPerSec, Size size) { _aniBack.value = _aniC.drive(AlignmentTween( begin: _dragAlignment.value, end: Alignment.center, )); print('DRAG:${_aniBack.value}'); final unitsPerSecX = pixelsPerSec.dx / size.width; final unitsPerSecY = pixelsPerSec.dy / size.height; final unitsPerSec = Offset(unitsPerSecX, unitsPerSecY); final unitVelocity = unitsPerSec.distance; const spring = SpringDescription(mass: 30, stiffness: 1, damping: 1); final simulation = SpringSimulation(spring, 0, 1, -unitVelocity); _aniC.animateWith(simulation); } @override void onInit() { super.onInit(); _aniC = AnimationController(vsync: this); _aniC.addListener(() { _dragAlignment.value = _aniBack.value.value; }); print('init!'); } @override void onClose() { _aniC.dispose(); super.onClose(); } }
效果对比
- 期望效果:拖拽卡片松手后,卡片通过弹簧物理效果平滑复位至屏幕中心
- 当前效果:拖拽松手后无复位动画,卡片停留在松手位置
问题根源与修复方案
1. 重复初始化Controller(核心问题)
在DraggableCard中,同时创建了logic = _DraggableCardLogic(),又在GetX的init参数中初始化新实例,导致视图监听的Controller和实际操作动画的Controller不是同一个,动画回调无法同步到UI。
修复:删除DraggableCard中的final logic = _DraggableCardLogic(),使用Get.put()确保全局单例Controller。
2. 冗余的_aniBack.obs
_aniBack作为Animation对象不需要被包装成obs,只需在动画回调中直接将动画值同步到_dragAlignment即可。
修复:移除_aniBack的obs包装,通过Alignment.lerp在动画监听中实时计算对齐值。
3. 优化AnimationController初始化
onInit中重复赋值_aniC属于冗余代码,可简化。
修复后的完整代码
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter/physics.dart'; class Home extends StatelessWidget { const Home({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Physics'), actions: const [Icon(Icons.share)], centerTitle: true, elevation: 0, ), body: const Center( child: DraggableCard(child: FlutterLogo(size: 128)), ), ); } } class DraggableCard extends StatelessWidget { const DraggableCard({required this.child, super.key}); final Widget child; @override Widget build(BuildContext context) { final size = Get.size; final logic = Get.put(_DraggableCardLogic()); return Obx(() => GestureDetector( onPanDown: (_) => logic._aniC.stop(), onPanUpdate: (details) { logic._dragAlignment.value += Alignment( details.delta.dx / (size.width / 2), details.delta.dy / (size.height / 2), ); }, onPanEnd: (details) => logic._runAni(details.velocity.pixelsPerSecond, size), child: Align( alignment: logic._dragAlignment.value, child: Card(child: child), ), )); } } class _DraggableCardLogic extends GetxController with GetSingleTickerProviderStateMixin { late final AnimationController _aniC = AnimationController(vsync: this); final _dragAlignment = Alignment.center.obs; Alignment _startAlignment = Alignment.center; void _runAni(Offset pixelsPerSec, Size size) { _startAlignment = _dragAlignment.value; final unitsPerSecX = pixelsPerSec.dx / size.width; final unitsPerSecY = pixelsPerSec.dy / size.height; final unitsPerSec = Offset(unitsPerSecX, unitsPerSecY); final unitVelocity = unitsPerSec.distance; const spring = SpringDescription(mass: 30, stiffness: 1, damping: 1); final simulation = SpringSimulation(spring, 0, 1, -unitVelocity); _aniC.reset(); _aniC.animateWith(simulation); } @override void onInit() { super.onInit(); _aniC.addListener(() { _dragAlignment.value = Alignment.lerp( _startAlignment, Alignment.center, _aniC.value, )!; }); } @override void onClose() { _aniC.dispose(); super.onClose(); } }
关键修改说明
- 使用
Get.put()确保Controller单例,避免重复初始化 - 移除冗余的
_aniBack.obs,通过Alignment.lerp在动画监听中实时计算对齐值 - 简化GestureDetector的回调写法,代码更简洁
- 添加
_aniC.reset()确保每次动画从头开始
内容的提问来源于stack exchange,提问作者Nolca
相关产品推荐
相关产品推荐

