Popping笔记。Github上搜索"Popping"即可下载源代码。
Decay Animation
首先分析动画。
1.屏幕中心一个圆。
2.拖动圆,圆会运动并逐渐减速直到停止。
3.在圆运动的过程中,如果点击圆,圆会立即停到点击的地方。
4.如果圆碰到屏幕边缘,会立即弹回到屏幕中点,并且感觉有一些弹性。
这个动画很简单,我们来看源代码。
打开DecayViewController.m。
addDragView方法的主要是构造我们的圆,并且设置点击圆时候的处理方法和进行pan手势所使用的方法。有一点需要注意的是dragView是一个UIControl,如果用的是UIView,则没有addTarget:action:这个方法。UIControl是UIView的子类,实现了更多的功能,比如刚才我们所说的。
touchDown:方法很简单,如果用户点击了圆,那么就移除这个圆的图层的所有pop动画,这也是我们在最开始时候分析的第3点的原因。
接下来看handlePan:方法。我们想一下,这个方法肯定是想要让这个圆跟着我们的手指(鼠标)走,那么圆的位置该如何计算呢?
很简单,圆的位置 = 圆现在的位置 + 手指在屏幕上滑动的距离。
所以首先我们得到手指滑动的距离,recognizer有很方便的方法返回一个CGPoint的值,分别代表x,y方向上平移的距离。有了手指滑动的距离,我们就可以设置圆的位置了。recognizer有一个view的属性可以得到它所监听的对象,也就是dragView。我们将drageView的中点设置成其原来的位置+平移的距离,就像刚才说的那样。
注意:
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
这行很重要。在每次移动的时候我们都要将recognizer的x,y方向上的translation设置成0,否则在下一次平移的时候translation就会叠加,我们的圆会直接飞出屏幕,因为这次移动不仅包含了这次平移的距离,还包含了以前平移的距离。
可以注意到如果我们手指一直在圆上,并不会出现减速效果,只有手指离开圆,即结束了pan的手势,那么圆开始减速运动。接下来这行代码就是解释这一现象的:判断recognizer的状态,如果已经结束(即手指离开了屏幕,不再拖动圆),那么给圆加上一个POPDecayAnimation动画。
if(recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [recognizer velocityInView:self.view];
POPDecayAnimation *positionAnimation = [POPDecayAnimation animationWithPropertyNamed:kPOPLayerPosition];
positionAnimation.delegate = self;
positionAnimation.velocity = [NSValue valueWithCGPoint:velocity];
[recognizer.view.layer pop_addAnimation:positionAnimation forKey:@"layerPositionAnimation"];
}
首先得到手指在屏幕上的移动速度,接着创建POPDecayAnimation动画,设置其速度为刚才得到的速度。那么设置动画的delegate是因为什么呢?
看看我们在开始的分析的第4点你可能就会明白了。我们需要判断每次移动的时候圆是不是超出边界了。POP为我们提供了一个代理方法:
- (void)pop_animationDidApply:(POPDecayAnimation *)anim
动画的每一帧都会调用这个方法,这也正是我们需要的,因为我们需要时时刻刻都注意圆是否超出边界了。
- (void)pop_animationDidApply:(POPDecayAnimation *)anim
{
BOOL isDragViewOutsideOfSuperView = !CGRectContainsRect(self.view.frame, self.dragView.frame);
if (isDragViewOutsideOfSuperView) {
CGPoint currentVelocity = [anim.velocity CGPointValue];
CGPoint velocity = CGPointMake(currentVelocity.x, -currentVelocity.y);
POPSpringAnimation *positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPosition];
positionAnimation.velocity = [NSValue valueWithCGPoint:velocity];
positionAnimation.toValue = [NSValue valueWithCGPoint:self.view.center];
[self.dragView.layer pop_addAnimation:positionAnimation forKey:@"layerPositionAnimation"];
}
}
在动画的每一帧,我们需要判断是否出界。
CGRectContainsRect(rect1,rect2)顾名思义,判断rect2是否在rect1中,返回一个BOOL值。
一旦dragView不在self.view中了,我们就让dragView回到view的中点,具体做法是:
得到currentVelocity(就是最开始手指移动的速度?),接着得到velocity:
CGPoint velocity = CGPointMake(currentVelocity.x, -currentVelocity.y);
这句我也不是很懂,为什么要这样做有没有朋友可以指教一下?3QQQQ
接着创建POPSpringAnimation,设置velocity和toValue。
这里有一点需要注意,在我自己写代码的时候因为这个马虎浪费了很多时间。。那就是我们刚才创建的decay animation的key和现在创建的spring animation的key需要保持一致。其实道理很简单:我们首先创建了decay animation,我们希望圆一碰壁就弹回,即使用spring animation,但是有的时候decay animation还未结束就已经碰壁,如果这两个key不一样,则spring animation需要等待decay animation结束才能执行,这也是为什么圆会出屏幕一段时间然后弹回屏幕中点。正确的做法是我们将两者的key设置成相同的,我的理解是:首先执行decay animation,一旦圆碰壁了,我们添加spring animation,因为两者是相同的key意思就是在原来动画的基础上变换成新的动画,所以圆会马上回到中点。而不是相同的key则相当于创建了两个完全不同的动画,那么就需要一个结束另一个才能执行,所以圆会一直等到decay结束才能spring。
在这个例子之前我对velocity和toValue属性并不是很理解,现在有了一些新的理解:
并不是每一个动画都需要toValue,比如说我们这个decay的例子,我们不需要设置toValue,只需要给decay animation一个velocity,它会自己根据这个速度来减速。
同样的,在我们第一个例子shake button这个方法里面,我们创建了一个spring animation,没有设置其toValue,而是给这个动画一个velocity让其根据这个速度移动。
所以我的理解是:toValue是我们给定一个目的地,是精确的。而velocity是我们给定一个速度让其根据这个速度走,而究竟在哪一点停下来(decay animation),是远是近(spring animation,时间不变,速度越快走的越远),都要根据这个velocity。如果既设置toValue又设置velocity,则是到达目的地的快慢不同。这只是自己的理解,可能有不准确的地方,希望不吝赐教。