做flutter技术已经有小半年了,最近在使用Flutter自带的ViewPage组件,滑动效果有一丝丝微妙感。最后收尾效果是物理线形的弹簧的效果(就这么奇葩)。
先给大家感受一下原有的ViewPage效果吧。
关于修改这个动画效果的参考资料很少。今天简单说一下修改方式:
flutter动画基本上分为两大派系:补间动画 和 物理动画(比如失重、超重、动量守恒)。我就不展开说了,这个参考资料应该很多。
源码探索
从physics属性开始找起,它是一个控制物理动画的组件:
page_view.data
class _PageViewState extends State {
...
Widget build(BuildContext context) {
final AxisDirection axisDirection = _getDirection(context);
final ScrollPhysics physics = _ForceImplicitScrollPhysics(
allowImplicitScrolling: widget.allowImplicitScrolling,
).applyTo(widget.pageSnapping
? _kPagePhysics.applyTo(widget.physics)
: widget.physics);
...
}
从这里看出,如果不设置pageSnapping的话_kPagePhysics就是viewpage动画的实现类。再往下找:
const PageScrollPhysics _kPagePhysics = PageScrollPhysics();
/// Scroll physics used by a [PageView].
///
/// These physics cause the page view to snap to page boundaries.
///
/// See also:
///
/// * [ScrollPhysics], the base class which defines the API for scrolling
/// physics.
/// * [PageView.physics], which can override the physics used by a page view.
class PageScrollPhysics extends ScrollPhysics {
/// Creates physics for a [PageView].
const PageScrollPhysics({ ScrollPhysics parent }) : super(parent: parent);
@override
PageScrollPhysics applyTo(ScrollPhysics ancestor) {
return PageScrollPhysics(parent: buildParent(ancestor));
}
…
@override
Simulation createBallisticSimulation(ScrollMetrics position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
return super.createBallisticSimulation(position, velocity);
final Tolerance tolerance = this.tolerance;
final double target = _getTargetPixels(position, tolerance, velocity);
if (target != position.pixels)
return ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
return null;
}
...
}
嗯,在这里我们找到里每次滑动的实现类,ScrollSpringSimulation(漩涡弹簧模拟)对每次滑动进行渲染。
找到这个,我们就可以尝试修改一下源码:
return ScrollSpringSimulation(SpringDescription({
this.mass, //质量,控制滚动的惯性
this.stiffness,//刚性,滚动收尾速度
this.damping,//阻尼,俗称摩擦力
}), position.pixels, target, velocity, tolerance: tolerance);
要说明一下:(对物理感知敏感的小伙伴绕道)
mass:控制质量,数值越大越容易还原到静止状态
stiffness:控制滑动力度,数值越大滑动速度远快(劲越大)
damping:阻力,当小于1时则可以取消回弹动画
现在,可以整点骚东西:
毕竟我们改源码是没有意义的,git并不会记录我们对源码的修改。刚刚分析的途中,我们看到了部分猫腻
/// * [ScrollPhysics], the base class which defines the API for scrolling physics.
/// Set to false to disable page snapping, useful for custom scroll behavior.
final bool pageSnapping;
ok,只要我们重写一个PageScrollPhysics 再搭配上 pageSnapping 属性即可完成这一切:
怎么重写PageScrollPhysics?简单,command+c command+v。
调整实现
第一步:重写一个正常的PageScrollPhysics
class pageScrollPhysics extends PageScrollPhysics {
const pageScrollPhysics({ScrollPhysics parent}) : super(parent: parent);
@override
pageScrollPhysics applyTo(ScrollPhysics ancestor) {
return pageScrollPhysics(parent: buildParent(ancestor));
}
double _getPage(ScrollMetrics position) {
if (position is _PagePosition) return position.page;
return position.pixels / position.viewportDimension;
}
double _getPixels(ScrollMetrics position, double page) {
if (position is _PagePosition) return position.getPixelsFromPage(page);
return page * position.viewportDimension;
}
double _getTargetPixels(
ScrollMetrics position, Tolerance tolerance, double velocity) {
double page = _getPage(position);
if (velocity < -tolerance.velocity)
page -= 0.5;
else if (velocity > tolerance.velocity) page += 0.5;
return _getPixels(position, page.roundToDouble());
}
@override
Simulation createBallisticSimulation(
ScrollMetrics position, double velocity) {
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent))
return super.createBallisticSimulation(position, velocity);
final Tolerance tolerance = this.tolerance;
final double target = _getTargetPixels(position, tolerance, velocity);
if (target != position.pixels)
return ScrollSpringSimulation(SpringDescription(
mass: 8,
stiffness: 150,
damping: 15,
), position.pixels, target, velocity,
tolerance: tolerance);
return null;
}
@override
bool get allowImplicitScrolling => false;
}
……(剩下的自己copy)
第二步:pageView设置物理动画空间
PageView.builder(
pageSnapping:false,//必须
physics:ClampingScrollPhysics()
itemBuilder: (context, index) {
return item(data);
});
效果:
大功告成、可喜可贺。下次分享见!