Pop上手体验(i-v)
Facebook一直为开发者提供自己的开源代码库非常令人感激。最新的一个是Pop,在Github上不到24小时就已经获得3500个星了(目前是将近6000个)。
Pop上手体验(i)
Facebook一直为开发者提供自己的开源代码库非常令人感激。最新的一个是,在Github上不到24小时就已经获得3500个星了(目前是将近6000个)。
&(文中涉及动态图,可能会加载的慢,请耐心查看!)
Facebook官方阐述:
&Pop是一个适用于iOS和OS X平台的可扩展动画引擎。除了基本的静态动画,Pop还支持spring和decay动画,有助于打造一个逼真的,基于物理的交互。你可以通过Pop的API把Pop快速集成到现有的Objective-C代码库中,并在任何对象上实现动画的任何属性。这是一个成熟的并且经过良好测试的框架,承载了Paper中所有的动画和交互。
我使用Pop创建一个。我只是想看看它是如何很好地实现用户输入框带有的阴影效果,它确实做得很棒。我还想快速的创建我所知道的东西。在使用POPSpringAnimation这个例子中,我觉得这个代码跟很相似。
至于研究这个库,我的策略是查阅这些文件中的.***件(这库也有Objective-C++版本):
POPBasicAnimation
POPDecayAnimation
POPPropertyAnimation
POPSpringAnimation
POPCustomAnimation
POPAnimation
POPAnimatableProperty
Pop的一些东西确实很酷,即当你添加一个动画时,展示层和模型层是同步的。Sam Page()中使用Pop的就是这样。由于strokeEnd和strokeStart不是的一部分,所以你需要创建自己的自定义属性(不过我可能错失了什么),如下:
[POPAnimatableProperty&propertyWithName:@&strokeStart&&initializer:^(POPMutableAnimatableProperty&*prop)&{&&&&&&&&&prop.readBlock&=&^(id&obj,&CGFloat&values[])&{&&&&&&&&&&&&&values[0]&=&[obj&strokeStart];&&&&&&&&&};&&&&&&&&&prop.writeBlock&=&^(id&obj,&const&CGFloat&values[])&{&&&&&&&&&&&&&[obj&setStrokeStart:values[0]];&&&&&&&&&};&&&&&}];&
不得不说这个很强大,正如我之前所说的:表现层和模型层是同步的。
Pop上手体验(ii)
中让我困惑的是Brian Amerige()的一些话,尤其是他向我们示范如何跨过手势和动画之间的间隙()。直接从视频中获得的信息:
&基于手势速度旋转UIView:
-&(void)rotate:(UIPanGestureRecognizer*)recognizer&{&&&&&CGPoint&velocity&=&[recognizer&velocityInView:self.view];&&&&&&POPSpringAnimation&*spring&=&[POPSpringAnimation&animationWithPropertyNamed:kPOPLayerRotation];&&&&&spring.velocity&=&[NSValue&valueWithCGPoint:velocity];&&&&&&[_outletView.layer&pop_addAnimation:spring&forKey:@&rotationAnimation&];&}&
现在,当你开始上手体验时一切变得非常有趣:
当位置、大小和&dynamics&一同作用的时候会发生怎样的事情?
-&(void)rotate:(UIPanGestureRecognizer*)recognizer&{&&&&&CGPoint&velocity&=&[recognizer&velocityInView:self.view];&&POPSpringAnimation&*positionAnimation&=&[POPSpringAnimation&animationWithPropertyNamed:kPOPLayerPosition];&&&positionAnimation.velocity&=&[NSValue&valueWithCGPoint:velocity];&&&positionAnimation.dynamicsTension&=&5;&&&positionAnimation.dynamicsFriction&=&5.0f;&&&positionAnimation.springBounciness&=&20.0f;&&&[_outletView.layer&pop_addAnimation:positionAnimation&forKey:@&positionAnimation&];&&&POPSpringAnimation&*sizeAnimation&=&[POPSpringAnimation&animationWithPropertyNamed:kPOPLayerSize];&&&sizeAnimation.velocity&=&[NSValue&valueWithCGPoint:velocity];&&&sizeAnimation.springBounciness&=&1.0f;&&&sizeAnimation.dynamicsFriction&=&1.0f;&&&[_outletView.layer&pop_addAnimation:sizeAnimation&forKey:@&sizeAnimation&];&}&
移除与&dynamics&相关的代码后:
你仍然能看到轻微的弹跳效果,但这是POPSpringAnimation的默认值。
Pop上手体验(iii)
Pop或者任何其他出于消遣目的的库都会涉及到一点--我应该用它来做什么?在Paper by Facebook这篇文章中,作者Brian Lovin ()用动态图展示了Paper的设计细节,可以帮忙进行思考。下图来自Brian Lovin的(这里有该博客的译文: ):
我想创建可以展示小弹窗的东西,但还要带一点震动效果(好吧,因为我喜欢)。这个描述可能不是很准确(甚至相差甚远),但它给了我一点灵感,所以:
你也看的出来,它似乎不是那么迷人,但是用Pop很容易做出来。
-&(void)hidePopup&{&&&&&_isMenuOpen&=&NO;&&&&&POPBasicAnimation&*opacityAnimation&=&[POPBasicAnimation&animationWithPropertyNamed:kPOPLayerOpacity];&&&&&opacityAnimation.fromValue&=&@(1);&&&&&opacityAnimation.toValue&=&@(0);&&&&&[_popUp.layer&pop_addAnimation:opacityAnimation&forKey:@&opacityAnimation&];&&&&&&POPBasicAnimation&*positionAnimation&=&[POPBasicAnimation&animationWithPropertyNamed:kPOPLayerPosition];&&&&&positionAnimation.fromValue&=&[NSValue&valueWithCGPoint:VisiblePosition];&&&&&positionAnimation.toValue&=&[NSValue&valueWithCGPoint:HiddenPosition];&&&&&[_popUp.layer&pop_addAnimation:positionAnimation&forKey:@&positionAnimation&];&&&&&&POPSpringAnimation&*scaleAnimation&=&[POPSpringAnimation&animationWithPropertyNamed:kPOPLayerScaleXY];&&&&&&scaleAnimation.fromValue&&=&[NSValue&valueWithCGSize:CGSizeMake(1.0f,&1.0f)];&&&&&scaleAnimation.toValue&&=&[NSValue&valueWithCGSize:CGSizeMake(0.5f,&0.5f)];&&&&&[_popUp.layer&pop_addAnimation:scaleAnimation&forKey:@&scaleAnimation&];&}&
-&(void)showPopup&{&&&&&_isMenuOpen&=&YES;&&&&&&POPBasicAnimation&*opacityAnimation&=&[POPBasicAnimation&animationWithPropertyNamed:kPOPLayerOpacity];&&&&&opacityAnimation.fromValue&=&@(0);&&&&&opacityAnimation.toValue&=&@(1);&&&&&opacityAnimation.beginTime&=&CACurrentMediaTime()&+&0.1;&&&&&[_popUp.layer&pop_addAnimation:opacityAnimation&forKey:@&opacityAnimation&];&&&&&&POPBasicAnimation&*positionAnimation&=&[POPBasicAnimation&animationWithPropertyNamed:kPOPLayerPosition];&&&&&positionAnimation.fromValue&=&[NSValue&valueWithCGPoint:VisibleReadyPosition];&&&&&positionAnimation.toValue&=&[NSValue&valueWithCGPoint:VisiblePosition];&&&&&[_popUp.layer&pop_addAnimation:positionAnimation&forKey:@&positionAnimation&];&&&&&&&POPSpringAnimation&*scaleAnimation&=&[POPSpringAnimation&animationWithPropertyNamed:kPOPLayerScaleXY];&&&&&scaleAnimation.fromValue&&=&[NSValue&valueWithCGSize:CGSizeMake(0.5,&0.5f)];&&&&&scaleAnimation.toValue&&=&[NSValue&valueWithCGSize:CGSizeMake(1.0f,&1.0f)];&&&&&scaleAnimation.springBounciness&=&20.0f;&&&&&scaleAnimation.springSpeed&=&20.0f;&&&&&[_popUp.layer&pop_addAnimation:scaleAnimation&forKey:@&scaleAnimation&];&}&
三条注意事项:
1. 当添加类似[myView pop_addAnimation:animation forKey:@&animationKey&];的动画时,如果你用相同的key添加其他动画,那么新添加的动画将会取代先前的动画。
2. 当你想要开始一个动画时,使用CACurrentMediaTime()生成一个动画开始时间来初始化beginTime。所以它看起来应该像:animation.beginTimee = CACurrentMediaTime() + delayInS.我简单地添加了delay,当然不会凑效。感谢Kimon(@kimon) 的警告。
3. 当你看到类似kPOPLayerScaleXY属性时,它将会有两个值。在这个例子中是CGSize。现在可能是有意义的,不过我传递了一个NSNumber(单一值),期待设置成X和Y值。
Pop上手体验(iv)
这是天才 (via ):
POPAnimatableProperty&*constantProperty&=&[POPAnimatableProperty&propertyWithName:@&constant&&initializer:^(POPMutableAnimatableProperty&*prop){&&&&&&&&&&&prop.readBlock&=&^(NSLayoutConstraint&*layoutConstraint,&CGFloat&values[])&{&&&&&&&&&&&&&values[0]&=&[layoutConstraint&constant];&&&&&&&&&};&&&&&&&&&prop.writeBlock&=&^(NSLayoutConstraint&*layoutConstraint,&const&CGFloat&values[])&{&&&&&&&&&&&&&[layoutConstraint&setConstant:values[0]];&&&&&&&&&};&&&&&}];&&POPSpringAnimation&*constantAnimation&=&[POPSpringAnimation&animation];&&&constantAnimation.property&=&constantP&&&constantAnimation.fromValue&=&@(_layoutConstraint.constant);&&&constantAnimation.toValue&=&@(200);&&&[_layoutConstraint&pop_addAnimation:constantAnimation&forKey:@&constantAnimation&];&
感谢Jake Marsh ().
这是一个小便签,我没有注意到kPOPLayoutConstraintConstant,所以你无需创建一个自定义POPAnimatableProperty。
Pop上手体验 (v)
在上手体验Pop几天后,有一点就是除了享受它,我应该做一点贡献。
在该系列的中,我为strokeStart和strokeEnd (两者均属于CAShapeLayer)创建了自定义属性:
[POPAnimatableProperty&propertyWithName:@&strokeStart&&initializer:^(POPMutableAnimatableProperty&*prop)&{&&&&&&&&&prop.readBlock&=&^(id&obj,&CGFloat&values[])&{&&&&&&&&&&&&&values[0]&=&[obj&strokeStart];&&&&&&&&&};&&&&&&&&&prop.writeBlock&=&^(id&obj,&const&CGFloat&values[])&{&&&&&&&&&&&&&[obj&setStrokeStart:values[0]];&&&&&&&&&};&&&&&}];&
这个过程有点工作量,但不用害怕。我的(希望不是最后一个)已经通过审核,并并入了主要的Pop repo。这意味着现在我让这两个属性应用在了CAShapeLayer上,没有添加任何逻辑。
简单几步即可为Pop添加属性,如果有人想要贡献的话,可以:
1.把NSString和你的属性名称添加到POPAnimatableProperty.h中,遵守它的命名惯例,看起来可能像kPOP&class name witout prefix&&propertyName&。如果它不止有一个值,那么它可能会像kPOP&class name witout prefix&&propertyName&XY。然后在POPAnimatableProperty.m上添加实际值,它可能会是NSString * const kPop&class name witout prefix&&propertyName& = @&&propertyName&&。如果你不确定如何命名,可以看看其他属性。
2.添加write/read blocks有效的方法,加上阀值。你可以看看。
3.我不需要这么做,因为我添加的属性非常简单。当读/写新值的时候,充分利用辅助属性会好很多。比如你可以看看kPOPViewBackgroundColor是如何实现的:
{kPOPViewBackgroundColor,&&&^(UIView&*obj,&CGFloat&values[])&{&&&&&POPUIColorGetRGBAComponents(obj.backgroundColor,&values);&&&},&&&^(UIView&*obj,&const&CGFloat&values[])&{&&&&&obj.backgroundColor&=&POPUIColorRGBACreate(values);&&&},&&&1.0&},&
这个例子使用了POPUIColorGetRGBAComponents和POPUIColorRGBACreate:
void&POPUIColorGetRGBAComponents(UIColor&*color,&CGFloat&components[])&&&{&&&return&POPCGColorGetRGBAComponents(color.CGColor,&components);&}&&UIColor&*POPUIColorRGBACreate(const&CGFloat&components[])&&&{&&&CGColorRef&colorRef&=&POPCGColorRGBACreate(components);&&&UIColor&*color&=&[[UIColor&alloc]&initWithCGColor:colorRef];&&&CGColorRelease(colorRef);&&&return&&}&
这个辅助方法位于POPCGUtils上,虽然POPLayerExtras上有很多。作为一个&良好公民&,你可以创建其他方法,所以用户可把它们用于其他相似的属性行为。
1.&为test suit添加你的属性!由于那些属性暗昧不明,所以我仅把它添加到了POPAnimatablePropertyTests.m的testProvidedExistence,以确保它的实现是确实存在的。
2. 如果你做了与众不同的事情,并且没有覆盖默认的test suit,那么你需要更多的测试。
随着我的需求的增长,我将会为Pop贡献更多。
推荐阅读:
CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocos Studio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!
请搜索微信号“CocoaChina”关注我们!
关注微信 每日推荐
扫一扫 浏览移动版#import "ViewController.h"
#import "POP.h"
@interface ViewController ()
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(log)];
[link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(50, 50, 50, 50)];
label.backgroundColor = [UIColor redColor];
[self.view addSubview:label];
//核心动画
CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
//keypath类型要和fromValue、toValue类型匹配
ani.fromValue
ani.toValue = [NSValue valueWithCGRect:CGRectMake(200, 300, 50, 50)];
//动画目标值
ani.toValue = @(3);
//动画时长
ani.duration = 3;
//动画结束是否移除
ani.removedOnCompletion = NO;
ani.fillMode = kCAFillModeF
//自动变回去
ani.autoreverses = YES;
//添加动画
[label.layer addAnimation:ani forKey:@"qwe"];
//通过key值取到animation
label.layer animationForKey:(NSString *)#&
//延时触发方法
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",NSStringFromCGRect(label.layer.frame));
[self performSelector:@selector(log) withObject:nil afterDelay:4];
- (void)log
NSLog(@"delay!");
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
- (IBAction)spring:(id)sender {
//弹性动画
//iOS7.0后新出的弹性动画,Damping阻尼,Velocity速度
[UIView animateWithDuration:3 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:2.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[sender setFrame:CGRectMake(200, 100, 80, 40)];
} completion:nil];
//frame变化
POPSpringAnimation *spring = [POPSpringAnimation animationWithPropertyNamed:kPOPViewFrame];
spring.toValue = [NSValue valueWithCGRect:CGRectMake(230, 40, 80, 40)];
//动画代理(代理函数包括:动画开始,动画结束,动画到达目标值、动画应用到控件上)
spring.delegate =
POPSpringAnimation *spring = [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY];
spring.fromValue默认从当前位置开始
spring.fromValue = [NSValue valueWithCGSize:CGSizeMake(1, 1)];
spring.toValue = [NSValue valueWithCGSize:CGSizeMake(3, 3)];
//背景色变化
POPSpringAnimation *spring = [POPSpringAnimation animationWithPropertyNamed:kPOPViewBackgroundColor];
spring.toValue = [NSValue valueWithCGRect:CGRectMake(0.1, 0.2, 0.3, 1.0)];
//设置振幅
spring.springBounciness = 20;
//振幅速度
spring.springSpeed = 20;
spring.dynamicsMass = 100;
spring.dynamicsTension = 100;
spring.dynamicsFriction = 100;
[sender pop_addAnimation:spring forKey:@"spring"];
//减速动画
- (IBAction)decay:(id)sender {
POPDecayAnimation *decay = [POPDecayAnimation animationWithPropertyNamed:kPOPViewCenter];
//减速不能设置toValue
decay.toValue = [NSValue valueWithCGPoint:CGPointMake(200, 100)];
decay.fromValue = [NSValue valueWithCGPoint:CGPointMake(50, 50)];
decay.velocity = [NSValue valueWithCGPoint:CGPointMake(200, 100)];
POPDecayAnimation *decay = [POPDecayAnimation animationWithPropertyNamed:kPOPViewScaleXY];
decay.velocity = [NSValue valueWithCGSize:CGSizeMake(4, 4)];
[sender pop_addAnimation:decay forKey:@"decay"];
- (IBAction)basic:(id)sender {
[POPBasicAnimation animationWithPropertyNamed:kPOPViewFrame];
POPBasicAnimation *basic =
[POPBasicAnimation animation];
basic.property = [POPAnimatableProperty propertyWithName:kPOPViewFrame];
basic.fromValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 80, 40)];
basic.toValue = [NSValue valueWithCGRect:CGRectMake(200, 200, 80, 40)];
//动画时长
basic.duration = 2;
[sender pop_addAnimation:basic forKey:@"basic"];
- (IBAction)Custom:(id)sender {
//block返回值为yes代表动画一直执行(block会继续执行),返回值no代表动画结束(block不会继续被执行)。
POPCustomAnimation *cus = [POPCustomAnimation animationWithBlock:^BOOL(id target, POPCustomAnimation *animation) {
//修改alpha值
CGFloat alpha = [target alpha];
alpha -= 0.005;
[target setAlpha:alpha];
//每次向右移动1
CGRect frame = [target frame];
frame.origin.x += 1;
[target setFrame:frame];
//如果x坐标大于等于200,则结束动画,否则继续动画
if (frame.origin.x &= 200) {
return NO;
return YES;
[sender pop_addAnimation:cus forKey:@"custom"];
//贝塞尔曲线创建圆路径(参数顺序:圆心、半径、起始角度、结束角度、是否顺时针绘制)
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(200, 200) radius:80 startAngle:M_PI endAngle:M_PI*(7/2) clockwise:YES];
//移动到某个点
[path moveToPoint:CGPointMake(120, 200)];
//添加到另外一个点之间的线
[path addLineToPoint:CGPointMake(180, 250)];
//再次移动到某个点
[path moveToPoint:CGPointMake(180, 250)];
[path addLineToPoint:CGPointMake(250, 140)];
//渲染路径的特殊layer
CAShapeLayer *layer = [CAShapeLayer layer];
//设置渲染路径
layer.path = path.CGP
//线条颜色
layer.strokeColor = [[UIColor redColor]CGColor];
//填充颜色
layer.fillColor = [[UIColor whiteColor]CGColor];
//线条宽度
layer.lineWidth = 5;
//添加到当前页面的layer上
[self.view.layer addSublayer:layer];
//核心动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.toValue = @(layer.strokeEnd);
animation.fromValue = @(layer.strokeStart);
animation.duration = 2;
[layer addAnimation:animation forKey:@"coreAnimation"];
//popcustom动画
POPCustomAnimation *layCus = [POPCustomAnimation animationWithBlock:^BOOL(id target, POPCustomAnimation *animation) {
CAShapeLayer *shapeLayer =
shapeLayer.strokeEnd += 0.01;
if (shapeLayer.strokeEnd &= 1) {
return NO;
return YES;
layer.strokeEnd = 0;
[layer pop_addAnimation:layCus forKey:@"custom"];
- (IBAction)property:(id)sender {
//自定义property
POPBasicAnimation *animation = [POPBasicAnimation animation];
//设置动画规则
animation.property = [POPAnimatableProperty propertyWithName:@"customProperty" initializer:^(POPMutableAnimatableProperty *prop) {
//从values数组中取值显示到target上
[prop setWriteBlock:^(id target, const CGFloat *values) {
[target setAlpha:values[0]];
//从target取值赋值到values数组中
[prop setReadBlock:^(id target, CGFloat *values) {
values[0] = [target alpha];
//让button变成透明
animation.toValue = @(0);
animation.property = [POPAnimatableProperty propertyWithName:@"increase" initializer:^(POPMutableAnimatableProperty *prop) {
[prop setWriteBlock:^(id target, const CGFloat *values) {
[target setTitle:[NSString stringWithFormat:@"%.f",values[0]] forState:UIControlStateNormal];
[prop setReadBlock:^(id target, CGFloat *values) {
values[0] = [target titleLabel].text.floatV
animation.toValue = @(1000);
animation.property = [POPAnimatableProperty propertyWithName:@"size" initializer:^(POPMutableAnimatableProperty *prop) {
//writeblock和readblock正好是相反的两个过程
[prop setWriteBlock:^(id target, const CGFloat *values) {
CGRect frame = [target frame];
frame.size.width = values[0];
frame.size.height = values[1];
[target setFrame:frame];
[prop setReadBlock:^(id target, CGFloat *values) {
CGRect frame = [target frame];
values[0] = frame.size.
values[1] = frame.size.
animation.fromValue = [NSValue valueWithCGSize:CGSizeMake(80, 40)];
animation.toValue = [NSValue valueWithCGSize:CGSizeMake(200, 200)];
animation.property = [POPAnimatableProperty propertyWithName:@"qwer" initializer:^(POPMutableAnimatableProperty *prop) {
[prop setReadBlock:^(id target, CGFloat *values) {
CGRect frame = [target frame];
values[0] = frame.origin.x;
values[1] = frame.origin.y;
values[2] = [target titleLabel].text.floatV
values[3] = 0;
[prop setWriteBlock:^(id target, const CGFloat *values) {
CGRect frame = [target frame];
NSLog(@"%f-%f-%f-%f",values[0],values[1],values[2],values[3]);
frame.origin.x = values[0];
frame.origin.y = values[1];
[target setFrame:frame];
[target setTitle:[NSString stringWithFormat:@"%.f",values[2]] forState:UIControlStateNormal];
animation.fromValue = [NSValue valueWithCGRect:CGRectMake(0, 0, 0, 0)];
animation.toValue = [NSValue valueWithCGRect:CGRectMake(300, 300, 1000, 0)];
//设置动画时长
animation.duration = 2;
[sender pop_addAnimation:animation forKey:@"animation"];
阅读(...) 评论()