首先效果上图:

now,let‘s go。
分为三节:
本节为第二节底部中间页面的滑动切换
思想:放置三个页面到一个布局,使用Stack包住,一个在最左边,一个在最右边,最后一个在中间,他们三宽高是一致的,同样是通过Transform改变方位,你只需要在中间的页面上通过手势来控制左边的页面即可,通过设置阴影达到翻书式效果,当每次翻页完成时,再还原三个布局的位置并且重新设置三个页面的内容即可,毫无违和感。
老规矩:只上核心代码,其余的自己去看,你们别看那些看不清晰的,只需要知道核心原理即可,不要只见树木不见森林。
top、center、bottom即左中右三个页面,此处使用别的布局包裹,是想着写出一个插件来着,但是后来发现这个专用性太强,我就懒得写了。
Widget build(BuildContext context) {
return SliverPageSelf(
topChild: QuestionPage(QuestionLayer.top),
centerChild: QuestionPage(QuestionLayer.center, selfControll: widget.controll),
bottomChild: QuestionPage(QuestionLayer.bottom),
controll: widget.controll,
);
下面偏移和阴影,最底部的写再最开始让他被覆盖
child: Stack(
children: <Widget>[
Container(
width: double.infinity,
height: double.infinity,
child: widget.bottomChild,
),
Transform.translate(
offset: Offset(centerOffsetDistance, 0.0),
child: _centerLayer(),
),
Align(
alignment: Alignment.centerLeft,
child: Transform.translate(
offset: Offset(leftOffsetDistance, 0.0),
child: Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
boxShadow: (leftOffsetDistance != -ScreenUtil.getInstance().setWidth(350) ) ? [
BoxShadow(
color: Colors.black54, blurRadius: 10.0, spreadRadius: 2.0)
] : null,
color: Colors.white,
),
child: widget.topChild,
),
),
),
],
),
);
中间那个_centerLayer()加上滑动
Widget _centerLayer() {
return RawGestureDetector(
gestures: {CenterPicDragGestureRecognizer: getRecognizer()},
child: Container(
width: double.infinity,
height: double.infinity,
child: widget.centerChild,
decoration: BoxDecoration(
boxShadow: (centerOffsetDistance != 0) ? [
BoxShadow(
color: Colors.black54, blurRadius: 10.0, spreadRadius: 2.0)
] : null,
color: Colors.white,
)),
);
}
下面时滑动时的,和上节很像,要做其他限制。
void _onUpdate(DragUpdateDetails details) {
print('水平滑动{details.delta}');
currentDrag = currentDrag + details.delta.dx;
//初始化时,当下标为0的page在中间时,左侧不允许滑动
if (currentDrag >= 0) {
//向右滑动
if (Provider.of<QuestionSourceChange>(context).current == 0) {
//如果当前图为第一张则不让右滑
currentDrag = -1;
return;
}
leftOffsetDistance = leftOffsetDistance + details.delta.dx;
} else {
//向左滑动
if (Provider.of<QuestionSourceChange>(context).current ==
Provider.of<QuestionSourceChange>(context).questionMap.length - 1) {
//如果当前图为第一张则不让右滑
currentDrag = 1;
return;
}
centerOffsetDistance = centerOffsetDistance + details.delta.dx;
}
setState(() {});
}
一下是抬手的时候,要确定是向左还是向右,和上一节一样,只是多了一个整体方向问题。
///手指离开屏幕
void _onEnd(DragEndDetails details) {
print('离开屏幕');
double halfOffset = MediaQuery.of(context).size.width / 2;
double velocityX = details.velocity.pixelsPerSecond.dx;
if (currentDrag > 0) {
//向右滑动,动leftOffsetDistance
if (velocityX.abs() > 400) {
if (velocityX > 0) {
endSliver(false);
} else {
endSliver(true);
}
} else {
if (leftOffsetDistance >= -halfOffset) {
endSliver(false);
} else {
endSliver(true);
}
}
} else {
//向左滑动,动centerOffsetDistance
if (velocityX.abs() > 400) {
if (velocityX > 0) {
//还原,即中间的图回退到0,0的位置
centerEndSliver(false);
} else {
centerEndSliver(true);
}
} else {
if (centerOffsetDistance >= -halfOffset) {
//还原,即中间的图回退到0,0的位置
centerEndSliver(false);
} else {
centerEndSliver(true);
}
}
}
}
同样centerEndSliver封装起来依然很好用,此处我最开始用 with SingleTicker 想用一个动画通过重新设置start和end解决,但是一直有bug, 后来改了 TickerProviderStateMixin,并且设置了两个动画控制器
void centerEndSliver(bool isShow) {
if (isShow) {
print('左平移');
start = centerOffsetDistance;
end = -ScreenUtil.getInstance().setWidth(350);
animalController2.value = 0.0;
animation2 = Tween(begin: start, end: end).animate(curve2)
..addListener(() {
centerOffsetDistance = animation2.value;
setState(() {});
});
animalController2.forward().then((_) {
Provider.of<QuestionSourceChange>(context).forward();
centerOffsetDistance = 0;
});
} else {
//还原0,0
print('还原');
start = centerOffsetDistance;
end = 0;
animalController2.value = 0.0;
animation2 = Tween(begin: start, end: end).animate(curve2)
..addListener(() {
centerOffsetDistance = animation2.value;
setState(() {});
});
animalController2.forward();
}
}
最后千万不要忘记重置值,其中的forword().then((){})中的
Provider.of<QuestionSourceChange>(context).forward();
//即通知所有人要换了, 左page为 count-1, 右page为 count+1
void forward(){
current++;
notifyListeners();
}
并且在每次再次按下的时候重置滑动距离
///接受触摸事件
void _onStart(DragStartDetails details) {
print('触摸屏幕${details.globalPosition}');
currentDrag = 0;
}
就是这么简单。如果觉得能看,请看下节==》这俩节配合使用。