主要包含了一个拖拽效果,以及松开之后的组合动画。
拖拽效果使用了组件 Draggable,组合动画使用到了CurvedAnimation。
使用 GifImage 插件,来控制GIF动画的时间。
DraggableAnimation(
duration: 5,
landTime: 0.3,
landDuration: 0.04,
hover: Container(
width: 58,
height: 84,
child: GifImage(
controller: GifController(vsync: this)..repeat(min: 0, max: 5, period: Duration(milliseconds: 500)),
image: AssetImage("images/await.gif"),
),
),
drag: Container(
width: 54,
height: 107,
child: Image.asset("images/drag.gif"),
),
fall: Container(
width: 65,
height: 108,
child: Image.asset("images/fall.gif"),
),
run: Container(
width: 66,
height: 120,
child: GifImage(
controller: GifController(vsync: this)..repeat(min: 0, max: 3, period: Duration(milliseconds: 300)),
image: AssetImage("images/run.gif"),
),
),
land: Container(
width: 68,
height: 90,
child: GifImage(
controller: GifController(vsync: this)..repeat(min: 0, max: 2, period: Duration(milliseconds: 200)),
image: AssetImage("images/land.gif"),
),
),
),
class DraggableAnimation extends StatefulWidget {
final Widget hover;
final Widget drag;
final Widget fall;
final Widget land;
final Widget run;
final int duration;
final double landTime;
final double landDuration;
const DraggableAnimation({
Key key,
this.hover, // 静止样式
this.drag, // 拖拽样式
this.fall, // 下落样式
this.land, // 落地样式
this.run, // 消失样式
this.duration = 5, // 动画总时长
this.landTime = 0.3, // 落地开始时间
this.landDuration = 0.1, // 落地持续时间
}) : super(key: key);
@override
State createState() {
return DraggableAnimationState();
}
}
enum AnimationState { drag, fall, land, run }
class DraggableAnimationState extends State with SingleTickerProviderStateMixin {
AnimationState _state;
bool _isAnimation = false;
double _startLeft = 0;
double _startBottom = 0;
Size _containerSize = Size.zero;
// 用于获取容器的位置和大小,处理偏移问题
GlobalKey _keyContainer = GlobalKey(debugLabel: 'key_ontainer');
// 用于拖拽组件的位置和大小,获取动画开始的位置
GlobalKey _keyDragContainer = GlobalKey(debugLabel: 'key_drag_ontainer');
Map _contentMap = Map();
AnimationController _animationController;
Animation _dropAnimation;
Animation _runAnimation;
@override
void initState() {
super.initState();
_contentMap = {
AnimationState.drag: widget.drag,
AnimationState.fall: widget.fall,
AnimationState.land: widget.land,
AnimationState.run: widget.run,
};
_animationController = AnimationController(duration: Duration(seconds: widget.duration), vsync: this)
..addListener(() {
setState(() {
_isAnimation = _animationController.isAnimating;
if (_animationController.value <= widget.landTime) {
_state = AnimationState.fall;
} else if (_animationController.value >= widget.landTime + widget.landDuration) {
_state = AnimationState.run;
} else {
_state = AnimationState.land;
}
});
});
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Stack(
key: _keyContainer,
children: [
Positioned(
child: Visibility(
child: _draggableWidget(),
visible: !_isAnimation,
),
right: 0,
top: 120,
),
Positioned(
left: _runAnimation?.value ?? 0,
bottom: _dropAnimation?.value ?? 0,
child: Visibility(
visible: _isAnimation,
child: _contentMap[_state] ?? Container(),
),
)
],
),
);
}
Widget _draggableWidget() {
return Draggable(
child: widget.hover ?? Container(),
onDragStarted: () {
if (_state != AnimationState.drag) {
setState(() {
_state = AnimationState.drag;
});
}
},
onDraggableCanceled: (Velocity velocity, Offset offset) {
RenderBox renderContainer = _keyContainer.currentContext.findRenderObject();
_containerSize = renderContainer.size;
var containerPosition = renderContainer.localToGlobal(Offset.zero);
final RenderBox renderBoxRed = _keyDragContainer.currentContext.findRenderObject();
var position = renderBoxRed.localToGlobal(Offset.zero);
var size = renderBoxRed.size;
_startLeft = position.dx;
_startBottom = _containerSize.height - position.dy - size.height + containerPosition.dy;
print('${_containerSize.width - _startLeft}');
if (_containerSize.width - _startLeft < 2 * size.width) {
return;
}
_startAnimation();
},
childWhenDragging: Container(),
feedback: Container(
child: widget.drag ?? Container(),
key: _keyDragContainer,
),
);
}
void _startAnimation() {
_dropAnimation = Tween(begin: _startBottom, end: 0).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(0.0, widget.landTime, curve: Curves.easeInCubic),
));
_runAnimation = Tween(begin: _startLeft, end: _containerSize.width).animate(CurvedAnimation(
parent: _animationController,
curve: Interval(widget.landTime + widget.landDuration, 1.0, curve: Curves.easeOut),
));
_animationController.reset();
_animationController.forward();
}
}