-
PageReveal
(用于操作nextPage执行滑动动画)
PageReveal
主要是利用ClipOval
组件对该widget
进行圆形裁剪,从而和滑动手势关联起来实现滑动动画。
final double revealPercent;
final Widget child;
PageReveal({
this.revealPercent,
this.child
});
@override
Widget build(BuildContext context) {
return ClipOval(
clipper: new CircleRevealClipper(revealPercent),//自定义剪裁路径
child: child,
);
}
通过CircleRevealClipper
继承于CustomClipper
重写getClip
方法自定义剪裁路径:
Rect getClip(Size size) {
final epicenter = new Offset(size.width / 2, size.height * 0.9);//剪裁中心点
double theta = atan(epicenter.dy / epicenter.dx);
final distanceToCorner = epicenter.dy / sin(theta);
final radius = distanceToCorner * revealPercent;//圆形半径
final diameter = 2 * radius;//圆形的直径
return new Rect.fromLTWH(epicenter.dx - radius, epicenter.dy - radius, diameter, diameter);
}
-
PagerIndicator
指示器
Widget build(BuildContext context) {
List<PageBubble> bubbles = [];
for(var i = 0; i < viewModel.pages.length; ++i ){
final page = viewModel.pages[i];
var percentActive;
if(i == viewModel.activeIndex){//滑到当前的index
percentActive = 1.0 - viewModel.slidePercent;
} else if (i == viewModel.activeIndex - 1 && viewModel.slideDirection == SlideDirection.leftToRight){//从左往右滑动 而且是当前index-1
percentActive = viewModel.slidePercent;
} else if (i == viewModel.activeIndex + 1 && viewModel.slideDirection == SlideDirection.rightToLeft){//从右往左滑动 而且是当前index+1
percentActive = viewModel.slidePercent;
}else {//其他请求 不进行变化
percentActive = 0.0;
}
//isHollow 是否是未滑到的index 当前index 的后面
bool isHollow = i > viewModel.activeIndex || (i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight);
bubbles.add(
new PageBubble(//指示器原点
viewModel: new PageBubbleViewModel(
page.iconAssetPath,//icon imamge路径
page.color,//页面颜色
isHollow,//isHollow 是否是未滑到的index 当前index 的后面
percentActive,//滑动百分百
),
),
);
}
final bubbleWidth = 55.0 ;
final baseTranslation = ((viewModel.pages.length * bubbleWidth) / 2) - (bubbleWidth / 2) ;
var translation = baseTranslation - (viewModel.activeIndex * bubbleWidth);
if (viewModel.slideDirection == SlideDirection.leftToRight){
translation = bubbleWidth * viewModel.slidePercent + translation;
}else if (viewModel.slideDirection == SlideDirection.rightToLeft){
translation = bubbleWidth * viewModel.slidePercent - translation;
}
return new Column(
children: <Widget>[
new Expanded(child: new Container()),
new Transform(
transform: new Matrix4.translationValues(0, 0.0, 0.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: bubbles,
),
),
],
);
}
}
-
PageBubble
指示圆点
Widget build(BuildContext context) {
return new Container(
width: 55.0,
height: 65.0,
child: new Center(
child: new Container(
width: lerpDouble(20.0,45.0,viewModel.activePercent),//根据比例缩放
height: lerpDouble(20.0,45.0,viewModel.activePercent),//根据比例缩放
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: viewModel.isHollow
? const Color(0x88FFFFFF).withAlpha(0x88 * viewModel.activePercent.round())
: const Color(0x88FFFFFF),
border: new Border.all(
color: viewModel.isHollow
? const Color(0x88FFFFFF).withAlpha((0x88 * (1.0 - viewModel.activePercent)).round())
: Colors.transparent,
width: 3.0,
),
),
child: new Opacity(//透明度变化
opacity: viewModel.activePercent,
child: Image.asset(
viewModel.iconAssetPath,
color: viewModel.color,
),
),
),
),
);
}
代码中我加了比较详细的注释,大家看代码即可。
-
PageDragger
(负责滑动-->手势)
一些初始化方法:
final canDragLeftToRight;
final canDragRightToLeft;
final StreamController<SlideUpdate> slideUpdateStream;
PageDragger({
this.canDragLeftToRight,//是否能从左往右滑动
this.canDragRightToLeft,//是否能从右往左滑动
this.slideUpdateStream,//监听器
});
_PageDraggerState
用来监听组件GestureDetector
滑动相关信息:
static const FULL_TRANSTITION_PX = 300.0;//滑动最大距离
Offset dragStart;//开始滑动的点
SlideDirection slideDirection;//滑动方向
double slidePercent = 0.0;//滑动百分百
onDragStart(DragStartDetails details){//滑动开始
dragStart = details.globalPosition;
}
onDragUpdate(DragUpdateDetails details) {//滑动更新
if (dragStart != null) {
print(details.globalPosition);
final newPosition = details.globalPosition;
final dx = dragStart.dx - newPosition.dx;
if (dx > 0 && widget.canDragRightToLeft) {
slideDirection = SlideDirection.rightToLeft;
} else if (dx < 0 && widget.canDragLeftToRight) {
slideDirection = SlideDirection.leftToRight;
} else {
slideDirection = SlideDirection.none;
}
if (slideDirection != SlideDirection.none){//clamp :如果参数位于最小数值和最大数值之间的数值范围内,则该函数将返回参数值。如果参数大于范围,该函数将返回最大数值。如果参数小于范围,该函数将返回最小数值,通过这个函数为变量的赋值设置了取值范围
slidePercent = (dx / FULL_TRANSTITION_PX).abs().clamp(0.0, 1.0);
} else {
slidePercent = 0.0;
}
widget.slideUpdateStream.add(//发送正在滑动监听数据
new SlideUpdate(
UpdateType.dragging,
slideDirection,
slidePercent
));
}
}
onDragEnd(DragEndDetails details){//滑动结束
widget.slideUpdateStream.add(//发送滑动结束消息
new SlideUpdate(
UpdateType.doneDragging,
SlideDirection.none,
0.0,
)
);
dragStart = null;
}
-
FourthPageState
处理监听相关
StreamController<SlideUpdate> slideUpdateStream;////滑动监听 controller
AnimatedPageDragger animatedPageDragger;//动画执行者
int activeIndex = 0;//当前序号
SlideDirection slideDirection = SlideDirection.none;
int nextPageIndex = 0;//下一个序号
int waitingNextPageIndex = -1;
double slidePercent = 0.0;//滑动到下一页的百分百
FourthPageState() {
slideUpdateStream = new StreamController<SlideUpdate>();//滑动监听 controller
slideUpdateStream.stream.listen((SlideUpdate event) {//监听
if (mounted) {
setState(() {
if (event.updateType == UpdateType.dragging) {//手指滑动 进行滑动动画
slideDirection = event.direction;
slidePercent = event.slidePercent;
if (slideDirection == SlideDirection.leftToRight) {//从左往右滑动
nextPageIndex = activeIndex - 1;
} else if (slideDirection == SlideDirection.rightToLeft) {//从右往右滑动
nextPageIndex = activeIndex + 1;
} else {
nextPageIndex = activeIndex;
}
} else if (event.updateType == UpdateType.doneDragging) {//手指滑动完成 进行下一步动画(跳转下个页面 或者返回 0.5界限)
if (slidePercent > 0.5) {//跳转下个页面的动画
animatedPageDragger = new AnimatedPageDragger(
slideDirection: slideDirection,
transitionGoal: TransitionGoal.open,
slidePercent: slidePercent,
slideUpdateStream: slideUpdateStream,
vsync: this,
);
} else {//返回滑动前的页面动画
animatedPageDragger = new AnimatedPageDragger(
slideDirection: slideDirection,
transitionGoal: TransitionGoal.close,
slidePercent: slidePercent,
slideUpdateStream: slideUpdateStream,
vsync: this,
);
waitingNextPageIndex = activeIndex;
}
animatedPageDragger.run();//滑动完 完成接下来的动画
} else if (event.updateType == UpdateType.animating) {//动画进行中 进行百分百刷新
slideDirection = event.direction;
slidePercent = event.slidePercent;
} else if (event.updateType == UpdateType.doneAnimating) {//动画完成 置空百分百数据 销毁animatedPageDragger
if (waitingNextPageIndex != -1) {
nextPageIndex = waitingNextPageIndex;
waitingNextPageIndex = -1;
} else {
activeIndex = nextPageIndex;
}
slideDirection = SlideDirection.none;
slidePercent = 0.0;
animatedPageDragger.dispose();//销毁animatedPageDragger
}
});
}
});
}
这里要说一下AnimatedPageDragger
这个类,它是用来处理滑动完成是进行跳转下个页面或者返回上个页面的动画执行的。
static const PERCENT_PER_MILLISECOND = 0.005;//每毫秒的百分百
final slideDirection;//滑动方向
final transitionGoal;//动画类型 open: 跳转下个页面 close: 返回当前页面
AnimationController completionAnimationController;//动画监听controller
AnimatedPageDragger({
this.slideDirection,
this.transitionGoal,
slidePercent,
StreamController<SlideUpdate> slideUpdateStream,
TickerProvider vsync,
}) {//一些初始化操作
final startSlidePercent = slidePercent;
var endSlidePercent;
var duration;
if ( transitionGoal == TransitionGoal.open){//如果是跳转下个页面
endSlidePercent = 1.0;
final slideRemaining = 1.0 - slidePercent;
duration = new Duration(
milliseconds: (slideRemaining / PERCENT_PER_MILLISECOND).round()
);
} else {//返回当前页面
endSlidePercent = 0.0;
duration = new Duration(
milliseconds: (slidePercent / PERCENT_PER_MILLISECOND).round()
);
}
completionAnimationController = new AnimationController(
duration: duration,
vsync: vsync
)
..addListener((){//监听
slidePercent = lerpDouble( //lerpDouble(begin.height, end.height, t), t是百分百 在 begin 和end之间
startSlidePercent,
endSlidePercent,
completionAnimationController.value
);
slideUpdateStream.add(//发送动画执行监听
new SlideUpdate(
UpdateType.animating,
slideDirection,
slidePercent,
)
);
})
..addStatusListener((AnimationStatus status){//动画执行状态发生改变
if(status == AnimationStatus.completed){
slideUpdateStream.add(//发送动画完成监听
new SlideUpdate(
UpdateType.doneAnimating,
slideDirection,
endSlidePercent,
)
);
}
});
}
全在注释里。
结语
到这里,整个fluttergo项目的大体框架和实现思路基本都搞清楚了,以上分析仅是我个人理解,如有不对的地方欢迎指正。
最后再次感谢fluttergo项目开发人员的无私开源。
网友评论