-
PageReveal
(用于操作nextPage執(zhí)行滑動(dòng)動(dòng)畫)
PageReveal
主要是利用ClipOval
組件對(duì)該widget
進(jìn)行圓形裁剪,從而和滑動(dòng)手勢(shì)關(guān)聯(lián)起來實(shí)現(xiàn)滑動(dòng)動(dòng)畫砍聊。
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);//剪裁中心點(diǎn)
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){//滑到當(dāng)前的index
percentActive = 1.0 - viewModel.slidePercent;
} else if (i == viewModel.activeIndex - 1 && viewModel.slideDirection == SlideDirection.leftToRight){//從左往右滑動(dòng) 而且是當(dāng)前index-1
percentActive = viewModel.slidePercent;
} else if (i == viewModel.activeIndex + 1 && viewModel.slideDirection == SlideDirection.rightToLeft){//從右往左滑動(dòng) 而且是當(dāng)前index+1
percentActive = viewModel.slidePercent;
}else {//其他請(qǐng)求 不進(jìn)行變化
percentActive = 0.0;
}
//isHollow 是否是未滑到的index 當(dāng)前index 的后面
bool isHollow = i > viewModel.activeIndex || (i == viewModel.activeIndex && viewModel.slideDirection == SlideDirection.leftToRight);
bubbles.add(
new PageBubble(//指示器原點(diǎn)
viewModel: new PageBubbleViewModel(
page.iconAssetPath,//icon imamge路徑
page.color,//頁面顏色
isHollow,//isHollow 是否是未滑到的index 當(dāng)前index 的后面
percentActive,//滑動(dòng)百分百
),
),
);
}
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
指示圓點(diǎn)
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),//根據(jù)比例縮放
height: lerpDouble(20.0,45.0,viewModel.activePercent),//根據(jù)比例縮放
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,
),
),
),
),
);
}
代碼中我加了比較詳細(xì)的注釋舔腾,大家看代碼即可吟吝。
-
PageDragger
(負(fù)責(zé)滑動(dòng)-->手勢(shì))
一些初始化方法:
final canDragLeftToRight;
final canDragRightToLeft;
final StreamController<SlideUpdate> slideUpdateStream;
PageDragger({
this.canDragLeftToRight,//是否能從左往右滑動(dòng)
this.canDragRightToLeft,//是否能從右往左滑動(dòng)
this.slideUpdateStream,//監(jiān)聽器
});
_PageDraggerState
用來監(jiān)聽組件GestureDetector
滑動(dòng)相關(guān)信息:
static const FULL_TRANSTITION_PX = 300.0;//滑動(dòng)最大距離
Offset dragStart;//開始滑動(dòng)的點(diǎn)
SlideDirection slideDirection;//滑動(dòng)方向
double slidePercent = 0.0;//滑動(dòng)百分百
onDragStart(DragStartDetails details){//滑動(dòng)開始
dragStart = details.globalPosition;
}
onDragUpdate(DragUpdateDetails details) {//滑動(dòng)更新
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 :如果參數(shù)位于最小數(shù)值和最大數(shù)值之間的數(shù)值范圍內(nèi),則該函數(shù)將返回參數(shù)值。如果參數(shù)大于范圍瘾婿,該函數(shù)將返回最大數(shù)值恬惯。如果參數(shù)小于范圍向拆,該函數(shù)將返回最小數(shù)值,通過這個(gè)函數(shù)為變量的賦值設(shè)置了取值范圍
slidePercent = (dx / FULL_TRANSTITION_PX).abs().clamp(0.0, 1.0);
} else {
slidePercent = 0.0;
}
widget.slideUpdateStream.add(//發(fā)送正在滑動(dòng)監(jiān)聽數(shù)據(jù)
new SlideUpdate(
UpdateType.dragging,
slideDirection,
slidePercent
));
}
}
onDragEnd(DragEndDetails details){//滑動(dòng)結(jié)束
widget.slideUpdateStream.add(//發(fā)送滑動(dòng)結(jié)束消息
new SlideUpdate(
UpdateType.doneDragging,
SlideDirection.none,
0.0,
)
);
dragStart = null;
}
-
FourthPageState
處理監(jiān)聽相關(guān)
StreamController<SlideUpdate> slideUpdateStream;////滑動(dòng)監(jiān)聽 controller
AnimatedPageDragger animatedPageDragger;//動(dòng)畫執(zhí)行者
int activeIndex = 0;//當(dāng)前序號(hào)
SlideDirection slideDirection = SlideDirection.none;
int nextPageIndex = 0;//下一個(gè)序號(hào)
int waitingNextPageIndex = -1;
double slidePercent = 0.0;//滑動(dòng)到下一頁的百分百
FourthPageState() {
slideUpdateStream = new StreamController<SlideUpdate>();//滑動(dòng)監(jiān)聽 controller
slideUpdateStream.stream.listen((SlideUpdate event) {//監(jiān)聽
if (mounted) {
setState(() {
if (event.updateType == UpdateType.dragging) {//手指滑動(dòng) 進(jìn)行滑動(dòng)動(dòng)畫
slideDirection = event.direction;
slidePercent = event.slidePercent;
if (slideDirection == SlideDirection.leftToRight) {//從左往右滑動(dòng)
nextPageIndex = activeIndex - 1;
} else if (slideDirection == SlideDirection.rightToLeft) {//從右往右滑動(dòng)
nextPageIndex = activeIndex + 1;
} else {
nextPageIndex = activeIndex;
}
} else if (event.updateType == UpdateType.doneDragging) {//手指滑動(dòng)完成 進(jìn)行下一步動(dòng)畫(跳轉(zhuǎn)下個(gè)頁面 或者返回 0.5界限)
if (slidePercent > 0.5) {//跳轉(zhuǎn)下個(gè)頁面的動(dòng)畫
animatedPageDragger = new AnimatedPageDragger(
slideDirection: slideDirection,
transitionGoal: TransitionGoal.open,
slidePercent: slidePercent,
slideUpdateStream: slideUpdateStream,
vsync: this,
);
} else {//返回滑動(dòng)前的頁面動(dòng)畫
animatedPageDragger = new AnimatedPageDragger(
slideDirection: slideDirection,
transitionGoal: TransitionGoal.close,
slidePercent: slidePercent,
slideUpdateStream: slideUpdateStream,
vsync: this,
);
waitingNextPageIndex = activeIndex;
}
animatedPageDragger.run();//滑動(dòng)完 完成接下來的動(dòng)畫
} else if (event.updateType == UpdateType.animating) {//動(dòng)畫進(jìn)行中 進(jìn)行百分百刷新
slideDirection = event.direction;
slidePercent = event.slidePercent;
} else if (event.updateType == UpdateType.doneAnimating) {//動(dòng)畫完成 置空百分百數(shù)據(jù) 銷毀animatedPageDragger
if (waitingNextPageIndex != -1) {
nextPageIndex = waitingNextPageIndex;
waitingNextPageIndex = -1;
} else {
activeIndex = nextPageIndex;
}
slideDirection = SlideDirection.none;
slidePercent = 0.0;
animatedPageDragger.dispose();//銷毀animatedPageDragger
}
});
}
});
}
這里要說一下AnimatedPageDragger
這個(gè)類酪耳,它是用來處理滑動(dòng)完成是進(jìn)行跳轉(zhuǎn)下個(gè)頁面或者返回上個(gè)頁面的動(dòng)畫執(zhí)行的浓恳。
static const PERCENT_PER_MILLISECOND = 0.005;//每毫秒的百分百
final slideDirection;//滑動(dòng)方向
final transitionGoal;//動(dòng)畫類型 open: 跳轉(zhuǎn)下個(gè)頁面 close: 返回當(dāng)前頁面
AnimationController completionAnimationController;//動(dòng)畫監(jiān)聽controller
AnimatedPageDragger({
this.slideDirection,
this.transitionGoal,
slidePercent,
StreamController<SlideUpdate> slideUpdateStream,
TickerProvider vsync,
}) {//一些初始化操作
final startSlidePercent = slidePercent;
var endSlidePercent;
var duration;
if ( transitionGoal == TransitionGoal.open){//如果是跳轉(zhuǎn)下個(gè)頁面
endSlidePercent = 1.0;
final slideRemaining = 1.0 - slidePercent;
duration = new Duration(
milliseconds: (slideRemaining / PERCENT_PER_MILLISECOND).round()
);
} else {//返回當(dāng)前頁面
endSlidePercent = 0.0;
duration = new Duration(
milliseconds: (slidePercent / PERCENT_PER_MILLISECOND).round()
);
}
completionAnimationController = new AnimationController(
duration: duration,
vsync: vsync
)
..addListener((){//監(jiān)聽
slidePercent = lerpDouble( //lerpDouble(begin.height, end.height, t), t是百分百 在 begin 和end之間
startSlidePercent,
endSlidePercent,
completionAnimationController.value
);
slideUpdateStream.add(//發(fā)送動(dòng)畫執(zhí)行監(jiān)聽
new SlideUpdate(
UpdateType.animating,
slideDirection,
slidePercent,
)
);
})
..addStatusListener((AnimationStatus status){//動(dòng)畫執(zhí)行狀態(tài)發(fā)生改變
if(status == AnimationStatus.completed){
slideUpdateStream.add(//發(fā)送動(dòng)畫完成監(jiān)聽
new SlideUpdate(
UpdateType.doneAnimating,
slideDirection,
endSlidePercent,
)
);
}
});
}
全在注釋里。
結(jié)語
到這里碗暗,整個(gè)fluttergo項(xiàng)目的大體框架和實(shí)現(xiàn)思路基本都搞清楚了颈将,以上分析僅是我個(gè)人理解,如有不對(duì)的地方歡迎指正言疗。
最后再次感謝fluttergo項(xiàng)目開發(fā)人員的無私開源晴圾。