Core Animationの話 Part.1

46
Core Animationの話 Part.1 初めによくある問題 2012.05.19 ふじしげ ゆういち @nakiwo

description

Core Animation でアニメーション終了後位置が戻る問題の話。 第44回 Cocoa勉強会関西の発表資料。

Transcript of Core Animationの話 Part.1

Core Animationの話 Part.1初めによくある問題

2012.05.19ふじしげ ゆういち@nakiwo

• ふじしげ ゆういち• @nakiwo• 株式会社フィードテイラーhttp://feedtailor.jp/

Book+

そら案内

SYNCNEL

• ふじしげ ゆういち• @nakiwo• http://www.nakiwo.com/

洞窟物語

めがね(Mac AppStore)

今日のテーマ• Core Animation

•最初にはまりがちな問題『アニメーション終了後表示が戻る』

•結論は簡単。過程が複雑

• iOS

今日のサンプル

https://github.com/nakiwo/CoreAnimationSample1

Core Animation•矩形の高速表示+アニメーションのための仕組み (Open GL のラッパー)

• OS X v10.5で導入

• iOSでは最初からある

• CALayer/CAAnimation

何が嬉しい?

• Core Animationでしかできない事がある

• UIView/NSViewのアニメーション機構で済むならそれが一番楽

• 2.5Dアニメーション (平面を3D配置)

•繰り返しアニメーション

•キーフレームアニメーション

• CAReplicatorLayer, CAEmitterLayer, CAGradientLayer, AVPlayerLayer, AVCaptureVideoPreviewLayer etc..

• NSViewとCALayerはあまりうまく統合されていない

• layer-backed mode

• layer-hosting mode

NSViewとの統合

NSView- (void)setWantsLayer:(BOOL)flag

• UIViewはCALayerのラッパー

• UIViewとCALayerは完全に統合されている(片方のプロパティを変えると相互に反映される ※若干名前違い有り)

UIViewとの統合

UIView@property(nonatomic, readonly, retain) CALayer *layer

#import <QuartzCore/QuartzCore.h>

UIView *view;...

CALayer *layer = [CALayer layer];...

[view.layer addSublayer:layer];

本題・よくある問題• CABasicAnimationをLayerに追加するとアニメーションした後元の表示に戻る

CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];anim.fromValue = [NSValue valueWithCGPoint:_startPoint];anim.toValue = [NSValue valueWithCGPoint:_endPoint];anim.duration = 0.5;anim.timingFunction =[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];

[_testLayer addAnimation:anim forKey:@"hoge"];

デモ

※ Keynoteを中断する時は[H]を押す

よく見かける解決策

•微妙に気持ち悪い

CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];anim.fromValue = [NSValue valueWithCGPoint:_startPoint];anim.toValue = [NSValue valueWithCGPoint:_endPoint];anim.duration = 0.5;anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; // アニメーションが終了しても自動削除しない// 終了後はtoValueをキープanim.removedOnCompletion = NO;anim.fillMode = kCAFillModeForwards; [_testLayer addAnimation:anim forKey:@"hoge"];

何が問題?

CALayer

• Core Animationレンダリングシステムが表示する内容を表すデータモデル

• NSView/UIViewと違い、イベントハンドリングの仕組みを持っていない(表示専任)

animatable• CALayerのプロパティのうち、アニメーション可能な物はドキュメントに

“animatable”の記載がある

position

Specifies the receiver’s position in the superlayer’s coordinate system. Animatable.

@property CGPoint position

暗黙的アニメーション• implicit animation

• animatableなプロパティを変更すると勝手にアニメーション発動

(hidden、addSubLayer, removeFromSuperLayer 等でも発動)

_testLayer.position = CGPointMake(x, y);

•暗黙的アニメーションは現在のトランザクション設定に基づいて発動する

•トランザクションを明示しない場合、デフォルトのトランザクションが適用される

[CATransaction begin];[CATransaction setAnimationDuration:2];

_testLayer.position = _endPoint;

[CATransaction commit];

CAAnimation• 明示的アニメーション(explicit animation)

• アニメーション=時間と共に変化するプロパティ

• CALayerにkey/valueで関連づける(明示的アニメーション)

• CALayerのプロパティを一時的に上書きする

• 細かい設定が可能

CABasicAnimation

CAPropertyAnimation

CAAnimation<CAMediaTiming, CAAction>

CAKeyframeAnimation

fromValue, toValue, byValue

keyPath

values, keyTimes

CABasicAnimation

duration=1.0keypath=“position”fromValue=(10,10)toValue= (20,20)

CALayer

CABasicAnimation

duration=1.0keypath=“opacity”

fromValue=0.0toValue=1.0

- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key;

- (void)removeAnimationForKey:(NSString *)key;- (void)removeAllAnimations;

- (NSArray *)animationKeys;- (CAAnimation *)animationForKey:(NSString *)key;

key=”hoge”

key=”fuga”

LayerとAnimationはkey-valueで関連

CABasicAnimation

duration=1.0keypath=“position”fromValue=(10,10)toValue= (20,20)

CALayer

CABasicAnimation

duration=1.0keypath=“opacity”

fromValue=0.0toValue=1.0

key=”hoge”

key=”fuga”

LayerとAnimationはkey-valueで関連

layer->animationのkeyと、animationのkeypath は無関係

CALayeropacity=0.0

レイヤーツリー

CALayeropacity=0.0

opaticity=0.0 opaticity=0.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

レイヤーツリー

CALayeropacity=0.0

CABasicAnimation

duration=1.0keypath=“opacity”

fromValue=0.0toValue=1.0

opaticity=0.0 opaticity=0.0->1.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

opaticity=0.0->1.0

CAAction

•暗黙アニメーション発動時に実行されるアニメーション

CAAnimation : NSObject <CAAction>

CALayer- (id<CAAction>)actionForKey:(NSString *)event;

暗黙アニメーションとアクション

• Layerのプロパティ”X”を更新する

• [layer actionForKey:@”X”]で取り出されたAction(=Animation)が、Layerに追加される

• Layerのプロパティ値が実際に更新される

_testLayer.X = n;

CAAnimation<CAAction>CALayer

key=”X”

CALayeropacity=0.0

CALayeropacity=0.0

opaticity=0.0 opaticity=0.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

layer.opaticity=0の状態でlayer.opaticity=1を設定

CALayeropacity=1.0

CABasicAnimation

duration=1.0keypath=“opacity”

fromValue=0.0toValue=1.0

opaticity=1.0 opaticity=0.0->1.0 opaticity=0.0->1.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

layer.opaticity=0の状態でlayer.opaticity=1を設定

key=”opacity”

CALayeropacity=1.0

CALayeropacity=1.0

opaticity=1.0 opaticity=1.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

layer.opaticity=0の状態でlayer.opaticity=1を設定

本題・よくある問題• CABasicAnimationをLayerに追加するとアニメーションした後元の表示に戻る

CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];anim.fromValue = [NSValue valueWithCGPoint:_startPoint];anim.toValue = [NSValue valueWithCGPoint:_endPoint];anim.duration = 0.5;anim.timingFunction =[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];

[_testLayer addAnimation:anim forKey:@"hoge"];

CALayeropacity=0.0

CALayeropacity=0.0

opaticity=0.0 opaticity=0.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

CALayeropacity=0.0

CABasicAnimation

duration=1.0keypath=“opacity”

fromValue=0.0toValue=1.0

opaticity=0.0 opaticity=0.0->1.0 opaticity=0.0->1.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

CALayeropacity=0.0

CALayeropacity=0.0

opaticity=0.0 opaticity=0.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

よく見る解決策•微妙に気持ち悪さが残るCABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];anim.fromValue = [NSValue valueWithCGPoint:_startPoint];anim.toValue = [NSValue valueWithCGPoint:_endPoint];anim.duration = 0.5;anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; // アニメーションが終了しても自動削除しない// 終了後はtoValueをキープanim.removedOnCompletion = NO;anim.fillMode = kCAFillModeForwards; [_testLayer addAnimation:anim forKey:@"hoge"];

CALayeropacity=0.0

CALayeropacity=0.0

opaticity=0.0 opaticity=0.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

CALayeropacity=0.0

CABasicAnimation

duration=1.0keypath=“opacity”

fromValue=0.0toValue=1.0

opaticity=0.0 opaticity=0.0->1.0 opaticity=0.0->1.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

CALayeropacity=0.0

CABasicAnimation

duration=1.0keypath=“opacity”

fromValue=0.0toValue=1.0

opaticity=0.0 opaticity=1.0

Layer Tree Presentation Tree Render Tree (private)

layer.presentationLayer

opaticity=1.0

解決策

•先にLayerの値を更新しておく

[CATransaction begin]; // 暗黙トランザクションのdurationを長めに設定(確認用)[CATransaction setAnimationDuration:4];

// animations追加CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];anim.fromValue = [NSValue valueWithCGPoint:_startPoint];anim.toValue = [NSValue valueWithCGPoint:_endPoint];anim.duration = 0.3;anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];

// モデルの値も更新しておく// しかしこの方法だとimplicitアニメーションも同時に走ってしまう!// 同じkeyPathに対するアニメーションが2つ以上ある場合、どちらが勝つかは不定?// 登録された順番により結果が異なる様子_testLayer.position = _endPoint; // ここで暗黙アニメーション発動[self showAnimations]; // positionアニメーションが登録されている事がわかる[_testLayer addAnimation:anim forKey:@"hoge"]; // 適当な名前のキーで追加[self showAnimations]; // hoge/positionの2アニメーションが登録されている事がわかる[CATransaction commit];

[CATransaction begin]; // 暗黙トランザクションのdurationを長めに設定(確認用)[CATransaction setAnimationDuration:4];

// animations追加CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];anim.fromValue = [NSValue valueWithCGPoint:_startPoint];anim.toValue = [NSValue valueWithCGPoint:_endPoint];anim.duration = 0.3;anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];

// モデルの値も更新しておく// しかしこの方法だとimplicitアニメーションも同時に走ってしまう!// 同じkeyPathに対するアニメーションが2つ以上ある場合、どちらが勝つかは不定?// 登録された順番により結果が異なる様子_testLayer.position = _endPoint; // ここで暗黙アニメーション発動[self showAnimations]; // positionアニメーションが登録されている事がわかる[_testLayer addAnimation:anim forKey:@"hoge"]; // 適当な名前のキーで追加[self showAnimations]; // hoge/positionの2アニメーションが登録されている事がわかる[CATransaction commit];

[CATransaction begin];// 暗黙トランザクションのdurationを長めに設定(explicit3との対比確認のため)[CATransaction setAnimationDuration:4];NSLog(@"current transaction animationDuration : %f", [CATransaction animationDuration]);// animations追加CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];anim.fromValue = [NSValue valueWithCGPoint:_startPoint];anim.toValue = [NSValue valueWithCGPoint:_endPoint];anim.duration = 1;anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];// おすすめ解決策その1// モデルの値も更新しておく。_testLayer.position = _endPoint; // デフォルトアクションが発動[self showAnimations]; // positionのみ(デフォルトアクション)// アニメーションを登録する際、元のプロパティ名をキーとして使用する// 同じキーのアニメーションは1つしか登録出来ない。同じ名前があると上書きされる。// implicitアニメーションのアニメーションと同じキーで上書き[_testLayer addAnimation:anim forKey:@"position"];[self showAnimations]; // positionのみ(アプリが指定したインスタンス)[CATransaction commit];

// animations追加CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"position"];anim.fromValue = [NSValue valueWithCGPoint:_startPoint];anim.toValue = [NSValue valueWithCGPoint:_endPoint];anim.duration = 0.5;anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];

// おすすめ解決策その2// モデルの値も更新しておく// デフォルトアクション(=implicitアニメーション)が走らないように設定[CATransaction begin];[CATransaction setDisableActions:YES];_testLayer.position = _endPoint;// Layerアクションが無効になっているのでアニメーションは発動しない

[CATransaction commit];[self showAnimations]; // アニメーション無し[_testLayer addAnimation:anim forKey:@"hoge"]; // 適当な名前のキーで追加[self showAnimations]; // hogeしか無い

UIViewのLayer• UIView.layerは暗黙アニメーション(デフォルトアクション)が無効になっている

//[CATransaction begin]; // 不要//[CATransaction setDisableActions:YES]; // 不要view.layer.position = _endPoint;//[CATransaction commit]; // 不要[view.layer addAnimation:anim forKey:@"hoge"];

参考資料

• Core Animation Programming Guide