引言
这篇文章同样来做一些有趣的事情,我们会在两个LNScrollView之间构建一些联系;我们使用冲量来实现这些联系,一些LNScrollView可以检测出达到边界后剩余的,并把这些动量传递给另外的LNScrollView;类似于打台球,所以它也会有两种传递模式,看起来分别是:
1.球杆撞击白球
2.白球撞击彩色球
一些原理
场景1的情况,简化处理为:直接把A的动量都给到B上,A的动量变成0。
场景2的情况下,同时满足动量守恒、能量守恒,用以下两个方程保证这两个守恒:
分层和正方向
我们对这种连接结构划分了三层:
1.generator:产生器,这个零件会在当前控件中检测到多余的动量,并把他们发射出去。
2.convertor:转换头,这个零件会把一次碰撞结果传递给Pulser或是反馈给generator。
3.pulser:发生器,这个零件会接收一个动量并且在自己的控件上产生效果。
我们需要规定一下正方向,下面这张图片展示了这里对正方向的指定:
在图中,绿色箭头的指向为产生器的正确箭头,他们以向外为正方向;红色箭头的指向为发生器,他们以向内为正方向。OK,也就是说,一个generator产生的向外的动量,转化到Pulser中时,它是向内的,论上下左右;例如:一个顶部generator检测到一个向上的冲击:在它被连接在一个左侧的Pulser时,Pulser会产生一个向右的动量;当他被连接在一个右侧的Pulser时,Pulser会产生一个向左的动量。
事实上,每个产生器只能检测到自己正方向的运动,而每个发生器可以感知到来自产生器的状态和自身控件当前的状态,自身的状态可能是正负两个方向。
代码
Generator:
@interface LNScrollViewPulseGenerator : NSObject
@property (nonatomic, assign) CGFloat mass;
@property (nonatomic, assign, readonly) BOOL isOpen;
@property (nonatomic, weak) NSObject<LNScrollViewPulseGeneratorDelegate> *delegate;
- (CGFloat)generate:(CGFloat)velocity;
- (void)open;
- (void)close;
@end
Generator的固有属性:
- mass:质量,这个属性决定碰撞时的强度;质量更大的对象碰撞时产生更强的冲击,也越不容易产生位移。
- isOpen:是否开启这个检查器;默认是关闭的,开启后会检测出剩余冲量并发射出去。
- generate:产生一个速度为V的动量,generator.mass的动量。
- delegate: 这个delegate通常是Convertor,把自己产生的动量传递过去。
Pulser:
@interface LNScrollViewPulser : NSObject
@property (nonatomic, weak) NSObject<LNScrollViewPulserDelegate> *delegate;
@property (nonatomic, assign) CGFloat mass;
@property (nonatomic, assign, readonly) BOOL isOpen;
- (LNScrollViewMomentum *)getCurrentMomentum;
- (void)updateMomentum:(LNScrollViewMomentum *)momentum;
- (void)open;
- (void)close;
@end
Pulser:
- mass:同Generator的质量,他决定Pulser受冲击的影响,质量大影响越小。
- iOpen:同Generator,开启时会把接收到的动量作用到自己的控件上。
- getCurrentMomentum:从接收方获取当前已存在的动量;这和台球的不太一样,台球一般只会撞击静止的彩色球,但是这里可以打移动的靶子。
- updateMomentum:一次撞击结束后,目标控件接收到的动量,Pulser把这个动量转化成动画效果。
- open/close:开启、关闭。
Convertor:
@interface LNScrollViewPulseConvertor : NSObject <LNScrollViewPulseGeneratorDelegate>
@property (nonatomic, assign) BOOL isConversationOfEnergy;
- (void)bindGenerator:(LNScrollViewPulseGenerator *)generator;
- (void)bindPulser:(LNScrollViewPulser *)pulser;
@end
isConversationOfEnergy:是否考虑能量转化;开启时,类似白球撞彩色球;关闭,类似球杆撞白色。 bindGenerator:绑定一个产生器(设置一下代理)。 bindPulser:绑定一个发生器(设置一下代理)。 转化代码:
- (LNScrollViewMomentum *)generatorHasDetectedMomentum:(LNScrollViewMomentum *)momentum
{
if (self.isConversationOfEnergy) {
LNScrollViewMomentum *pulserMomentum = [self.pulser getCurrentMomentum];
LNScrollViewMomentum *targetMomentum = [[LNScrollViewMomentum alloc] init];
targetMomentum.mass = pulserMomentum.mass;
targetMomentum.velocity = (2 * momentum.mass * momentum.velocity + pulserMomentum.mass * pulserMomentum.velocity - momentum.mass * pulserMomentum.velocity)/(momentum.mass + pulserMomentum.mass);
[self.pulser updateMomentum:targetMomentum];
LNScrollViewMomentum *feedbackMomentum = [[LNScrollViewMomentum alloc] init];
feedbackMomentum.mass = momentum.mass;
feedbackMomentum.velocity = (2 * pulserMomentum.mass * pulserMomentum.velocity + momentum.mass * momentum.velocity - pulserMomentum.mass * momentum.velocity)/(momentum.mass + pulserMomentum.mass);
return feedbackMomentum;
} else {
LNScrollViewMomentum *pulserMomentum = [**self**.pulser getCurrentMomentum];
LNScrollViewMomentum *targetMomentum = [[LNScrollViewMomentum alloc] init];
targetMomentum.mass = pulserMomentum.mass;
targetMomentum.velocity = (momentum.mass * momentum.velocity + pulserMomentum.mass * pulserMomentum.velocity)/pulserMomentum.mass;
[self.pulser updateMomentum:targetMomentum];
LNScrollViewMomentum *feedbackMomentum = [[LNScrollViewMomentum alloc] init];
feedbackMomentum.mass = momentum.mass;
feedbackMomentum.velocity = 0.f;
return feedbackMomentum;
}
}
有isConversationOfEnergy的时候,会产生一个反馈的动量;关闭的时候,反馈的冲量总是0,所以当你连接ScrollViewA和ScrollViewB时,当这个转接头开启了有isConversationOfEnergy的时候,A会接收到一个反向的动量;否则A会立即停止。
LNScrollView:
@interface LNScrollView : UIView
@property (nonatomic, strong, readonly) LNScrollViewPulseGenerator *topPulseGenerator;
@property (nonatomic, strong, readonly) LNScrollViewPulseGenerator *leftPulseGenerator;
@property (nonatomic, strong, readonly) LNScrollViewPulseGenerator *bottomPulseGenerator;
@property (nonatomic, strong, readonly) LNScrollViewPulseGenerator *rightPulseGenerator;
@property (nonatomic, strong, readonly) LNScrollViewPulser *topPulser;
@property (nonatomic, strong, readonly) LNScrollViewPulser *leftPulser;
@property (nonatomic, strong, readonly) LNScrollViewPulser *bottomPulser;
@property (nonatomic, strong, readonly) LNScrollViewPulser *rightPulser;
@end
每个ScrollView有4个Generator和4个Pulser;对应四个方向,每个方向的正方向此前我们已经定义好了。可以将任意方向的Generator和Pulser连接到一起去,像这样:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.collectionView1];
[self.view addSubview:self.collectionView2];
[self.rightToLeftConvertor bindGenerator:self.collectionView1.rightPulseGenerator];
[self.rightToLeftConvertor bindPulser:self.collectionView2.leftPulser];
self.rightToLeftConvertor.isConversationOfEnergy = YES;
self.collectionView1.rightPulseGenerator.mass = 1.f;
self.collectionView2.leftPulser.mass = 1.f;
[self.leftToRightConvertor bindGenerator:self.collectionView2.leftPulseGenerator];
[self.leftToRightConvertor bindPulser:self.collectionView1.rightPulser];
self.leftToRightConvertor.isConversationOfEnergy = YES;
self.collectionView1.rightPulser.mass = 1.f;
self.collectionView2.leftPulseGenerator.mass = 1.f;
}
上边的代码collectionView1的右Generator和collectionView2的左Generator绑定到了一个转接头上,这样collectionView1在滚到右侧时会产生一个动量,传递给collectionView2的左侧发生器,collectionView2会继承一个转化后的初始速度。
同样的,将collectionView2的左Generator和collectionView1的右Generator绑定到一个转接头上,这样collectionView2在滚到左侧的时候会产生一个动量,传递给collectionView1的右侧发生器,collectionView1会继承一个转化后的初速度。
(这个有什么用,它可以用在列表嵌套时做内外层互动)
总结
本文讲述了多个LNCollectionView之间互动的实现方法,主要思路是模拟LNcollectionView之间发生碰撞,从而对速度进行重新分配;使用Generator->convertor->pulser三层结构实现的不同方向任意组合,最终实现了不同控件之间的联动效果。 (这些算式不保证符合现实hhhh,大致做成这样)