最近项目中有一个类似购物车的动画,然后我用android实现之后就想到自己再学flutter,就写了一个flutter的版本,效果上跟原生差不多,如图
因为有了android实现思路的支持,我就想到用同样的思路来实现,但是上来就遇到了一个问题,在android中咱们可以创建一个那个移动的view动态的添加到父布局中,然后开始执行动画,但是flutter我不知道怎么用父widget动态的添加widget。
所以只能换思路了,查看flutter的widget,看到了一个stack,这个类似android中的FrameLayout,然后就想到父widget用stack,stack的也正好接收的是children。
在点击view的时候我要获取到这个widget的大小和位置,所以写了一个widget
class ItemChildWidget extends StatelessWidget {
final int position;
final Widget child;
final ItemClickCallBack callBack;
ItemChildWidget({this.position, this.child, this.callBack});
@override
Widget build(BuildContext context) {
// TODO: implement build
return _getItemChildWidget(context);
}
Widget _getItemChildWidget(BuildContext context) {
return GestureDetector(
child: child,
onTap: () {
RenderBox box = context.findRenderObject();
callBack(box);
},
);
}
}
这个widget响应点击和获取位置大小的作用,然后再定义一个移动widget所在的stack,和定义一个移动的widget添加相应的动画
class AnimationItemMove extends StatefulWidget {
final List children;
final bool show;
final Offset offset;
final Size size;
final double defferentX;
final double defferentY;
AnimationItemMove(
{this.children,
@required this.show,
@required this.offset,
@required this.size,
@required this.defferentX,
@required this.defferentY});
@override
State createState() => AnimationItemMoveState();
}
class AnimationItemMoveState extends State
with SingleTickerProviderStateMixin {
// Animation _animation;
// AnimationController _controller;
var newChildren = List();
@override
Widget build(BuildContext context) => _getRootWidget(context);
@override
void initState() {
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("didChangeDependencies");
}
@override
void didUpdateWidget(AnimationItemMove oldWidget) {
super.didUpdateWidget(oldWidget);
print("didUpdateWidget");
// _controller.reset();
// _controller.forward();
}
Widget _getRootWidget(BuildContext context) {
return Offstage(
offstage: widget.show,
child: Stack(
children: _getChild(),
),
);
}
List _getChild() {
for (var item in widget.children) {
var itemChild = AnimaPositioned(
child: item,
show: widget.show,
offset: widget.offset,
size: widget.size,
defferentX: widget.defferentX,
defferentY: widget.defferentY,
yOffset: _getYdifferet(),
callBack: (parent){
// newChildren.remove(parent);
newChildren.removeAt(0);
setState(() {
});
},
);
newChildren.add(itemChild);
}
return newChildren;
}
double _getYdifferet() {
RenderBox box = context.findRenderObject();
if (box != null) {
Offset offset = box.localToGlobal(Offset.zero);
return offset.dy;
} else {
return 0;
}
}
// Widget _getStackView() {
// var parent = Stack();
// parent.children.add();
// }
}
class AnimaPositioned extends StatefulWidget{
final Widget child;
final bool show;
final Offset offset;
final Size size;
final double defferentX;
final double defferentY;
final double yOffset;
final AnimationFinshCallBack callBack;
AnimaPositioned({
this.child,
this.defferentX,
this.defferentY,
this.offset,
this.show,
this.size,
this.yOffset,
this.callBack,
});
@override
State createState() => AnimaPositionedState();
}
class AnimaPositionedState extends State with SingleTickerProviderStateMixin{
Animation _animation;
AnimationController _controller;
@override
Widget build(BuildContext context) => _getRootWidget();
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 1000),
vsync: this,
);
_animation = Tween(begin: 1.0, end: 0.0).animate(_controller)
..addListener(() {
setState(() {});
})
..addStatusListener((state){
if(state == AnimationStatus.completed){
widget.callBack(widget);
}
});
var widgetsbinding = WidgetsBinding.instance;
widgetsbinding.addPostFrameCallback((callback){
_controller.forward();
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
void didUpdateWidget (AnimaPositioned oldWidget) {
super.didUpdateWidget(oldWidget);
}
Widget _getRootWidget(){
return Positioned(
top: _getOffset().dy -
widget.yOffset +
widget.defferentY * (1 - _animation.value),
// top: _getOffset().dy + widget.defferentY * (1 - _animation.value),
left: _getOffset().dx + widget.defferentX * (1 - _animation.value),
height: _getSize().height * _animation.value,
width: _getSize().width * _animation.value,
child: Opacity(
opacity: _animation.value,
child: widget.child,
),
);
}
Offset _getOffset() {
if (widget.offset != null) {
// print(widget.offset.dy);
return widget.offset;
} else {
return Offset(0, 0);
}
}
Size _getSize() {
if (widget.size != null) {
return widget.size;
} else {
return Size(0, 0);
}
}
}
AnimaPositionedState这个类是主要的移动widget,里面执行了相应的移动、缩小、透明度的动画,里面要注意
var widgetsbinding = WidgetsBinding.instance;
widgetsbinding.addPostFrameCallback((callback){
_controller.forward();
});
这段代码是在移动的widget渲染结束后被调用的,也正是咱们需要开始执行动画的时候。
大致思路就是这么多,其实当我做完之后又看到flutter里面自带的hero,才感觉动画很相似,然后就发现它里面的实现方式更好,有用到Overlay,这个东西能够更好和方便的实现咱们这个动画,所以下次我会再优化的
这个是整个界面的widget的文件地址https://download.csdn.net/download/bb3413830/11214826(我不会修改下载积分,要下载的也可以私信。。。)