引言
本篇文章我们讨论一些有趣的东西:使用另外一种流体模型替换LNScrollView中的一部分零件;这里有一些算式,最终他们会汇总到一个新的Simulator中,它将会替换掉LNScrollView中的DecelerateSimulator零件;我们可以用现实中的物料解释这步操作具体做了什么:在初始的时候,LNScrollView中充满了水,现在我们需要把它们替换成奶油或者牙膏。
一些理论
牛顿流体:摩擦力(或"剪切应力")和流体速度线性关系:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a = − c v a = -cv </math>a=−cv
非牛顿流体:摩擦力和流体速度非线性关系:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a = f ( v ) a = f(v) </math>a=f(v)
幂率流体:摩擦力和流体速度幂率关系:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a = − c v n a = -cv^n </math>a=−cvn
幂率流体是一种通用模型,它是非牛顿流体中的一种,n叫"流性指数":
- <math xmlns="http://www.w3.org/1998/Math/MathML"> n < 1 n < 1 </math>n<1:假塑性流体
- <math xmlns="http://www.w3.org/1998/Math/MathML"> n > 1 n > 1 </math>n>1:胀塑性流体
- <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n = 1 </math>n=1:牛顿流体 所以我们替换为幂率模型实际上是对牛顿流体进行了拓展;在实际的代码中,我们也会分情况进行讨论,其中的一种情况是牛顿流体。
一些运算
幂率流体我们同样遵循三个步骤:创建Simulator、累积时间、结束。 幂率我们称为PowerLaw;PowerLaw和Decelerate输入一致,我们需要当前velocity和位移两个变量;然后让velocity不断减少,offset不断增加。
我们应该确定每段时间velocity减少的量和每段时间offset增加的量;这个章节会讲解如何使用一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> Δ t \Delta t </math>Δt计算出 <math xmlns="http://www.w3.org/1998/Math/MathML"> Δ y \Delta y </math>Δy和 <math xmlns="http://www.w3.org/1998/Math/MathML"> Δ v \Delta v </math>Δv,从最基础的算式开始:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a = − c v n a = -cv^n </math>a=−cvn
做同样的事情:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v ′ = − c v n v' = -cv^n </math>v′=−cvn
两边积分:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v = [ ( n − 1 ) ( δ t − C ) ] 1 1 − n , C = v 0 1 − n 1 − n v = [(n - 1)(\delta t - C)]^{\frac{1}{1-n}}, C = \frac{v_0^{1-n}}{1-n} </math>v=[(n−1)(δt−C)]1−n1,C=1−nv01−n
OK,我们可以将时间间隔带入到公式中,计算出下一个时间点的速度:当前的速度是 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 0 v_0 </math>v0,经过一段时间后,这个速度会变成把 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t带入上面算式算出的速度 <math xmlns="http://www.w3.org/1998/Math/MathML"> v v </math>v。
除此之外,我们还需要计算出经过了时间 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t后的位移;当然,我们可以通过把 <math xmlns="http://www.w3.org/1998/Math/MathML"> y y </math>y和 <math xmlns="http://www.w3.org/1998/Math/MathML"> y ′ y' </math>y′移动到一端,然后两边积分以得到 <math xmlns="http://www.w3.org/1998/Math/MathML"> y y </math>y对 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t的表达式相减。这比较麻烦,我们可以稍微做个代换,使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> v v </math>v替换 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v = d l d t = d l d v d v d t = d l d v a = d l d v ( − δ v n ) v=\frac{dl}{dt}=\frac{dl}{dv}\frac{dv}{dt}=\frac{dl}{dv}a=\frac{dl}{dv}(-\delta v^n) </math>v=dtdl=dvdldtdv=dvdla=dvdl(−δvn)
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v 1 − n d v = − δ d l v^{1-n}dv = -\delta dl </math>v1−ndv=−δdl
在这个基础上进行俩边积分会非常容易:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> l = 1 δ ( 2 − n ) ∗ ( v 1 2 − n − v 2 2 − n ) l = \frac{1}{\delta(2-n)}*(v_1^{2-n} - v_2^{2-n}) </math>l=δ(2−n)1∗(v12−n−v22−n)
用这个公式,我们可以将上面的 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 0 v_0 </math>v0和 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t计算出的时间 <math xmlns="http://www.w3.org/1998/Math/MathML"> v v </math>v带入到 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 1 v_1 </math>v1和 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 2 v_2 </math>v2可以算出这个位移差;这会很简单,因为不需要从t再算了,相当于我们使用了第一步的结果。
这里有几个特殊情况:
-
<math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n = 1 </math>n=1时:不能使用上面的算式计算,因为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n − 1 n-1 </math>n−1在分母,无法计算;我们之前说了这种情况是牛顿流体,所以当 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n = 1 </math>n=1的时候,我们可以直接使用Decelerate的算式。
-
<math xmlns="http://www.w3.org/1998/Math/MathML"> n = 2 n = 2 </math>n=2时: <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 − n = 0 2-n = 0 </math>2−n=0出现在分母中,所以我们不能使用这个算式计算偏移量(或许在这里对n->0使用洛必达求极限是正确的,但是没有这样做);在代码中专门对这种情况进行了处理:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v = v 0 1 + δ v t v = \frac{v_0}{1 + \delta vt} </math>v=1+δvtv0
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> y = 1 δ l n 1 + δ v t y = \frac{1}{\delta}ln^{1+\delta vt} </math>y=δ1ln1+δvt
代码
这里有3部分代码: 初始化:
ini
- (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;
}
保存一下位置、速度、运动参数等。 累积时间:
objectivec
- (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)时:
ini
- (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;
}
}
<math xmlns="http://www.w3.org/1998/Math/MathML"> n = 2 n=2 </math>n=2时,使用ln形式的累积:
ini
- (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;
}
}
<math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1时,使用牛顿流体的累积:
ini
- (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;
}
完成(速度降低到很小):
objectivec
- (BOOL)isFinished
{
return fabs(self.velocity) < 1.0;
}
接入LNScrollView
在LNScrollView的代理中返回新的Simulator,它会替换掉DecelerateSimulator:
ini
- (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,只需要实现好环境初始化、时间累积函数和结束的阈值就可以正常工作。