iOS开发之Pop框架(二)

来源:互联网 发布:网络彩票牌照发放2家 编辑:程序博客网 时间:2024/05/29 17:24

本周早些时候,Ole Begemann写了一个很棒的教程:“UIScrollView是如何工作的”。并且作了详细解释,他甚至从头开始创建了一个非常简单的滚动视图

 
创建过程很简单:使用UIPanGestureRecognizer,然后改变边界的原点来响应拖拽手势的转化。
 
扩展Ole的自定义滚动视图,以包含UIScrollView内部惯性滚动似乎很自然,使用Facobook最近发布的Pop动画引擎,我认为编写一个减速自定义滚动视图将是一个很有意思的项目。
 
Pop是Facebook当前使用的开源动画框架,承载了Paper中所有的动画和交互。Pop的API与CoreAnimation很相似,所以并不难学习。
 
Pop中两个很好的地方:
1.除了基本的动画外,Pop还提供spring和decay动画;
2.你可以使用Pop对NSObject对象上的任何属性执行动画效果,而不仅仅是UIKit中声明的动画属性。
 
由于Pop内置支持decaying动画,实现UIScrollView的声明就不那么困难了。
 
主要思想是在动画结束时,从UIPanGestureRecognizer中去掉速度属性,以减缓的方式来设置边界原点的动画。
 
那么让我们开始吧!首先先不考虑Ole的CustomScrollView项目,先看看handlePanGesture--CustomScrollView.m. 的方法(为什么要先看我的项目而不是Ole的呢?因为,每次激发手势时,Ole都会将translation设置为零,这就重置了速度。然而,我们是想保存速度)。
 
我们想要在手势完成之后开始一个decay动画,因此我们对switch语句中的UIGestureRecognizerStateEnded实例很感兴趣。
 
以下是嵌入代码片段的基本流程:
 
我们使用velocityInView: 方法获得手势的速度:
CGPoint velocity = [panGestureRecognizer velocityInView:self];
 
如果没有足够的水平或者垂直内容,我们不希望在x或者y方向发生任何移动。因此,我们添加判断语句:
if (self.bounds.size.width >= self.contentSize.width) {     velocity.x = 0; } if (self.bounds.size.height >= self.contentSize.height) {     velocity.y = 0; }
 
事实证也证明,我们使用velocityInView: 方法获得的速度是我们真正想要的负值,所以我们来解决这个问题。
  
我们想要为边界设置动画(尤其是边界的原点),因此我们使用kPOPViewBounds属性创建一个新的POPDecayAnimation。
POPDecayAnimation *decayAnimation = [POPDecayAnimation animationWithPropertyNamed:kPOPViewBounds];
 
Pop希望这个速度的坐标空间与你打算设置动画的属性的坐标空间相同。这个值将会是alpha(CGFloat)的一个NSNumber,CGPoint封装在NSValue中作为中心值,
 
CGRect封装在NSValue中作为边界值。正如你或许已经猜到了,在动画期间,属性值的改变是相应的速度分量的一个因素。
 
在我们的例子中,为边界设置动画意味着速度将是一个CGRect,它的origin.x值和origin.y值对应边界原点的变化。同样地,size.width速度和size.height速度对应边界大小的变化。
 
我们不想改变边界的大小,所以我们将它的速度分量设置为0。将拖拽手势的速度分别赋值给origin.x和origin.y。将整个事情封装到NSValue中,然后赋值给decayAnimation.velocity。
decayAnimation.velocity = [NSValue valueWithCGRect:CGRectMake(velocity.x, velocity.y, 0, 0)];
最后,使用pop_addAnimation: 方法和你想用的任意键,将decayAnimation 添加到视图上。
[self pop_addAnimation:decayAnimation forKey:@"decelerate"];
 

以下是整合后的代码:

//get velocity from pan gestureCGPoint velocity = [panGestureRecognizer velocityInView:self];if (self.bounds.size.width >= self.contentSize.width) {    //make movement zero along x if no horizontal scrolling    velocity.x = 0; }if (self.bounds.size.height >= self.contentSize.height) {    //make movement zero along y if no vertical scrolling    velocity.y = 0; }//we need the negative velocity of what we get from the pan gesture, so flip the signsvelocity.x = -velocity.x;velocity.y = -velocity.y;POPDecayAnimation *decayAnimation = [POPDecayAnimation animationWithPropertyNamed:kPOPViewBounds];//last two components zero as we do not want to change bound's sizedecayAnimation.velocity = [NSValue valueWithCGRect:CGRectMake(velocity.x, velocity.y, 0, 0)];[self pop_addAnimation:decayAnimation forKey:@"decelerate"];


 

使用这个你应该能获得减速的基本功能。你将注意到,如果速度很高的话,视图滚动时会超出contentSize。但是,这个事情很好解决,只要重载setBound:并添加判断语句来检查边界原点没有超出阈值。我很惊讶的是Pop并没有自动添加这个判断。
 
Pop中一个真正有趣的部分是,它能够为所有的属性设置动画,而不仅仅是UIKit声明的动画属性。既然我们只为边界远点设置动画,我认为这是一个很好的计划去尝试Pop自定义属性动画。
 
通过在动画进程中发送固定的回调,Pop允许你做这件事情,因此,你可以相应地更新你的视图。Pop处理了最难的部分--时间处理,将时间转换成属性值,是动画减缓或者震荡等。
 
这里你做的事情跟我们之前做的事情是一样的,不同的是现在你使用的是自定义属性。尤其是我们将动画bounds.origin.x和bounds.origin.y。你会看到代码结构大体上没有变化,除了这个巨大的POPAnimatableProperty属性的初始化。
 
就我个人理解,属性的名称可以是任意的字符串。在这个例子中,我命名为:
@"com.rounak.bounds.origin"
 
还有一个将POPMutableAnimatableProperty作为参数的初始化block(我认为这个初始化模式可以称为builder pattern)。
 
POPMutableAnimatableProperty有两个重要属性:readBlock 和 writeBlock。在readBlock中,你需要向Pop中传数据,而在writeBlock中你需要检索这个数据,并更新你的视图。readBlock值调用一次,而writeBlock在每一帧更新显示的时候都会被调用(通过CADisplayLink)。
 
Pop内在地将所有变量转化为矢量,因此它以CGFloat的线性数组的形式请求和发送数据。
 

在readBlcok中,你只需分别地将bounds.origin.x和bounds.origin.y分配给 values[0] 和 values[1]。

prop.readBlock = ^(id obj, CGFloat values[]) {    values[0] = [obj bounds].origin.x;    values[1] = [obj bounds].origin.y;};

在writeBlock中,你需要读取values[0] (bounds.origin.x) 和 values[1] (bounds.origin.y) ,并更新你的视图边界。

prop.writeBlock = ^(id obj, const CGFloat values[]) {    CGRect tempBounds = [obj bounds];    tempBounds.origin.x = values[0];    tempBounds.origin.y = values[1];    [obj setBounds:tempBounds];};

writeBlock中有神奇的事情发生了,每次使用值调用writeBlock都会遵循一个减缓(或振动)曲线。

 
你会注意到,由于这里我们只是在二维中操作,我们的速度是CGPoint,而不是CGRect。
 

以下是整合后的代码:

//get velocity from pan gestureCGPoint velocity = [panGestureRecognizer velocityInView:self];if (self.bounds.size.width >= self.contentSize.width) {    //make movement zero along x if no horizontal scrolling    velocity.x = 0;}if (self.bounds.size.height >= self.contentSize.height) {    //make movement zero along y if no vertical scrolling    velocity.y = 0;}//we need the negative velocity of what we get from the pan gesture, so flip the signsvelocity.x = -velocity.x;velocity.y = -velocity.y;POPDecayAnimation *decayAnimation = [POPDecayAnimation animation];POPAnimatableProperty *prop = [POPAnimatableProperty propertyWithName:@"com.rounak.boundsY" initializer:^(POPMutableAnimatableProperty *prop) {    // read value, feed data to Pop    prop.readBlock = ^(id obj, CGFloat values[]) {        values[0] = [obj bounds].origin.x;        values[1] = [obj bounds].origin.y;    };    // write value, get data from Pop, and apply it to the view    prop.writeBlock = ^(id obj, const CGFloat values[]) {        CGRect tempBounds = [obj bounds];        tempBounds.origin.x = values[0];        tempBounds.origin.y = values[1];        [obj setBounds:tempBounds];    };    // dynamics threshold    prop.threshold = 0.01;}];decayAnimation.property = prop;decayAnimation.velocity = [NSValue valueWithCGPoint:velocity];[self pop_addAnimation:decayAnimation forKey:@"decelerate"];
0 0
原创粉丝点击