引言
本篇文章我们讨论一些有趣的东西:使用另外一种流体模型替换LNScrollView中的一部分零件;这里有一些算式,最终他们会汇总到一个新的Simulator中,它将会替换掉LNScrollView中的DecelerateSimulator零件;我们可以用现实中的物料解释这步操作具体做了什么:在初始的时候,LNScrollView中充满了水,现在我们需要把它们替换成奶油或者牙膏。
一些理论
牛顿流体:摩擦力(或“剪切应力”)和流体速度线性关系:
非牛顿流体:摩擦力和流体速度非线性关系:
幂率流体:摩擦力和流体速度幂率关系:
幂率流体是一种通用模型,它是非牛顿流体中的一种,n叫“流性指数”:
- :假塑性流体
- :胀塑性流体
- :牛顿流体 所以我们替换为幂率模型实际上是对牛顿流体进行了拓展;在实际的代码中,我们也会分情况进行讨论,其中的一种情况是牛顿流体。
一些运算
幂率流体我们同样遵循三个步骤:创建Simulator、累积时间、结束。 幂率我们称为PowerLaw;PowerLaw和Decelerate输入一致,我们需要当前velocity和位移两个变量;然后让velocity不断减少,offset不断增加。
我们应该确定每段时间velocity减少的量和每段时间offset增加的量;这个章节会讲解如何使用一个计算出和,从最基础的算式开始:
做同样的事情:
两边积分:
OK,我们可以将时间间隔带入到公式中,计算出下一个时间点的速度:当前的速度是,经过一段时间后,这个速度会变成把带入上面算式算出的速度。
除此之外,我们还需要计算出经过了时间后的位移;当然,我们可以通过把和移动到一端,然后两边积分以得到对的表达式相减。这比较麻烦,我们可以稍微做个代换,使用替换:
在这个基础上进行俩边积分会非常容易:
用这个公式,我们可以将上面的和计算出的时间带入到和可以算出这个位移差;这会很简单,因为不需要从t再算了,相当于我们使用了第一步的结果。
这里有几个特殊情况:
1.时:不能使用上面的算式计算,因为在分母,无法计算;我们之前说了这种情况是牛顿流体,所以当的时候,我们可以直接使用Decelerate的算式。
2.时:出现在分母中,所以我们不能使用这个算式计算偏移量(或许在这里对n->0使用洛必达求极限是正确的,但是没有这样做);在代码中专门对这种情况进行了处理:
代码
这里有3部分代码: 初始化:
- (instancetype)initWithPosition:(CGFloat)position
velocity:(CGFloat)velocity
k:(CGFloat)k
n:(CGFloat)n
{
self = [super init];
if (self) {
self.position = position;
self.velocity = velocity;
self.k = k;
self.n = n;
}
return self;
}
保存一下位置、速度、运动参数等。 累积时间:
- (void)accumulate:(NSTimeInterval)during
{
if (fabs(self.n - 1.f) < 0.000001) {
[self accumulateNewtonian:during];
} else if (fabs(self.n - 2.f) < 0.000001) {
[self accumulateNonNewtonianLn:during];
} else {
[self accumulateNonNewtonian:during];
}
}
常规场景(n!=1&n!=2)时:
- (void)accumulateNonNewtonian:(NSTimeInterval)during
{
self.currentTime += during;
if (self.velocity == 0) {
return;
} else if (self.velocity > 0) {
CGFloat c = pow(self.velocity, 1.0 - self.n)/(1.0 - self.n);
CGFloat vBase = (self.n - 1)*self.k*during - c*(self.n - 1);
CGFloat vExp = 1.0/(1.0 - self.n);
CGFloat v = pow(vBase, vExp);
CGFloat l = (1.0/(self.k*(2.0 - self.n)))*(pow(self.velocity, 2.0 - self.n) - pow(v, 2.0 - self.n));
self.velocity = v;
self.position = self.position + l;
} else {
CGFloat positiveVelocity = -self.velocity;
CGFloat c = pow(positiveVelocity, 1.0 - self.n)/(1.0 - self.n);
CGFloat vBase = (self.n - 1)*self.k*during - c*(self.n - 1);
CGFloat vExp = 1.0/(1.0 - self.n);
CGFloat v = pow(vBase, vExp);
CGFloat l = (1.0/(self.k*(2.0 - self.n)))*(pow(positiveVelocity, 2.0 - self.n) - pow(v, 2.0 - self.n));
self.velocity = -v;
self.position = self.position - l;
}
}
时,使用ln形式的累积:
- (void)accumulateNonNewtonianLn:(NSTimeInterval)during {
self.currentTime += during;
if (self.velocity == 0) {
return;
} else if (self.velocity > 0) {
CGFloat v = self.velocity/(1.0 + self.k * self.velocity * during);
CGFloat l = (1.0/self.k)*log(1.0 + self.k * self.velocity * during);
self.velocity = v;
self.position = self.position + l;
} else {
CGFloat positiveVelocity = -self.velocity;
CGFloat v = positiveVelocity/(1.0 + self.k * positiveVelocity * during);
CGFloat l = (1.0/self.k)*log(1.0 + self.k * positiveVelocity * during);
self.velocity = -v;
self.position = self.position - l;
}
}
时,使用牛顿流体的累积:
- (void)accumulateNewtonian:(NSTimeInterval)during
{
self.currentTime += during;
CGFloat v = self.velocity * exp(-self.k * during);
CGFloat l = (-1.f/self.k) * self.velocity * exp(- self.k * during) - (-1.f/self.k) * self.velocity;
if (self.velocity < 0.01) {
self.velocity = 0;
}
self.velocity = v;
self.position = self.position + l;
}
完成(速度降低到很小):
- (BOOL)isFinished
{
return fabs(self.velocity) < 1.0;
}
接入LNScrollView
在LNScrollView的代理中返回新的Simulator,它会替换掉DecelerateSimulator:
- (LNScrollViewDecelerateSimulator *)ln_scrollViewHorizontalDecelerateSimulatorForPosition:(CGFloat)position velocity:(CGFloat)velocity {
LNScrollViewPowerLawDecelerateSimulator *simulator = [[LNScrollViewPowerLawDecelerateSimulator alloc] initWithPosition:position velocity:velocity k:2 n:1.2];
return simulator;
}
- (LNScrollViewDecelerateSimulator *)ln_scrollViewVerticalDecelerateSimulatorForPosition:(CGFloat)position velocity:(CGFloat)velocity {
LNScrollViewPowerLawDecelerateSimulator *simulator = [[LNScrollViewPowerLawDecelerateSimulator alloc] initWithPosition:position velocity:velocity k:2 n:1.2];
return simulator;
}
总结
本文讲述了如何实现一个自定义的Simulator用来替换LNScrollView中已经定义好的一些Simulator,只需要实现好环境初始化、时间累积函数和结束的阈值就可以正常工作。