Pop上手體驗(i)
Facebook一直爲開發者提供自己的開源代碼庫非常令人感激。最新的一個是
Pop,在Github上不到24小時就已經獲得3500個星了(目前是將近6000個)。
(文中涉及動態圖,可能會加載的慢,請耐心查看!)
Facebook官方闡述:
Pop是一個適用於iOS和OS X平臺的可擴展動畫引擎。除了基本的靜態動畫,Pop還支持spring和decay動畫,有助於打造一個逼真的,基於物理的交互。你可以通過Pop的API把Pop快速集成到現有的Objective-C代碼庫中,並在任何對象上實現動畫的任何屬性。這是一個成熟的並且經過良好測試的框架,承載了Paper中所有的動畫和交互。 |
我使用Pop創建一個
非常簡單的例子。我只是想看看它是如何很好地實現用戶輸入框帶有的陰影效果,它確實做得很棒。我還想快速的創建我所知道的東西。在使用POPSpringAnimation這個例子中,我覺得這個代碼跟
我寫過的其他代碼很相似。
至於研究這個庫,我的策略是查閱這些文件中的.h文件(這庫也有Objective-C++版本):
POPBasicAnimation
POPDecayAnimation
POPPropertyAnimation
POPSpringAnimation
POPCustomAnimation
POPAnimation
POPAnimatableProperty
Pop的一些東西確實很酷,即當你添加一個動畫時,展示層和模型層是同步的。Sam Page(
@sampage)中使用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)
基於手勢速度旋轉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很容易做出來。
- - (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() + delayInSeconds;.我簡單地添加了delay,當然不會湊效。感謝Kimon(@kimon) 的警告。
3. 當你看到類似kPOPLayerScaleXY屬性時,它將會有兩個值。在這個例子中是CGSize。現在可能是有意義的,不過我傳遞了一個NSNumber(單一值),期待設置成X和Y值。
Pop上手體驗(iv)
還真有用:
- 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 = constantProperty;
- constantAnimation.fromValue = @(_layoutConstraint.constant);
- constantAnimation.toValue = @(200);
- [_layoutConstraint pop_addAnimation:constantAnimation forKey:@"constantAnimation"];
這是一個小便籤,我沒有注意到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]];
- };
- }];
這個過程有點工作量,但不用害怕。我的
第一個pull request(希望不是最後一個)已經通過審覈,並併入了主要的Pop
repo。這意味着現在我讓這兩個屬性應用在了CAShapeLayer上,沒有添加任何邏輯。
簡單幾步即可爲Pop添加屬性,如果有人想要貢獻的話,可以:
1.把NSString和你的屬性名稱添加到POPAnimatableProperty.h中,遵守它的命名慣例,看起來可能像kPOP。如果它不止有一個值,那麼它可能會像kPOPXY。然後在POPAnimatableProperty.m上添加實際值,它可能會是NSString * const kPop = @""。如果你不確定如何命名,可以看看其他屬性。
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 color;
- }
這個輔助方法位於POPCGUtils上,雖然POPLayerExtras上有很多。作爲一個“良好公民”,你可以創建其他方法,所以用戶可把它們用於其他相似的屬性行爲。
1. 爲test suit添加你的屬性!由於那些屬性闇昧不明,所以我僅把它添加到了POPAnimatablePropertyTests.m的testProvidedExistence,以確保它的實現是確實存在的。
2. 如果你做了與衆不同的事情,並且沒有覆蓋默認的test suit,那麼你需要更多的測試。