animations 是一款flutter插件,提供了封装好的一些路由切换动画。最近闲来无事去看了一下它的代码,理解其关键部分代码,并实现一个精简版的animations插件。虽然不完善,但是总体思路还是对的。
首先看OpenContainer类,关键部分是传入两个Widget,一个openBuilder是打开后要展示的页面,closeBuilder被点击之后能触发动画的控件。
Widget build(BuildContext context) {
return OpenContainer(
tappable: true,
openBuilder: (context,VoidCallback _){
return const _DetailsPage();
},
closedBuilder: closedBuilder,
onClosed: onClosed,
useRootNavigator: true,
);
}
OpenContainer类里面有个openContainer函数,用于切换到指定路由。
Future<void> openContainer() async {
await Navigator.of(context,rootNavigator: widget.useRootNavigator).push(_OpenContainerRoute<bool>(
closedBuilder: widget.closedBuilder,
openBuilder: widget.openBuilder,
hideableKey: _hideableKey,
closedBuilderKey: _closedBuilderKey,
useRootNavigator: widget.useRootNavigator,
transitionDuration: Duration(milliseconds: 3000))
);
}
跟踪代码到类_OpenContainerRoute,看到其继承自ModolRoute类。继承此类事为了重写路由切换时的回调函数,以此来获取closeBuilder构建的控件的位置,上级路由的位置,用于指定路由切换的起点和终点。
class _OpenContainerRoute<T> extends ModalRoute<T> {
_OpenContainerRoute({
required this.closedBuilder,
required this.openBuilder,
required this.hideableKey,
required this.closedBuilderKey,
required this.useRootNavigator,
required this.transitionDuration,
});
那么重写的是哪个函数呢?关键位置如下:
TickerFuture didPush(){
_takeMeasurements(navigatorContext: hideableKey.currentContext!);
}
调用了_takeMeasurements测量动画的起始位置。其中hideableKey标记的是closeBuilder构造的组件的位置。
起始位置有了,控制路由切换的动画就简单了,关键代码如下:
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return Align(
alignment: Alignment.topLeft,
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget? child) {
final Animation<double> curvedAnimation = CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
reverseCurve:
false ? null : Curves.fastOutSlowIn.flipped,
);
final Rect rect = _rectTween.evaluate(curvedAnimation)!;
return SizedBox.expand(
child: Container(
color: Colors.transparent,
child: Align(
alignment: Alignment.topLeft,
child: Transform.translate(
offset: Offset(rect.left,rect.top),
child: SizedBox(
width: rect.width,
height: rect.height,
child: Material(
clipBehavior: Clip.antiAlias,
animationDuration: Duration.zero,
child: Stack(
fit: StackFit.passthrough,
children: [
openBuilder(context, (){})
],
),
),
),
)
),
),
);
},
),
);
}
运行效果: