ラベル Core Animation の投稿を表示しています。 すべての投稿を表示
ラベル Core Animation の投稿を表示しています。 すべての投稿を表示

CAGradientLayer を使ったグラデーション付きボタンの試作

2011年10月22日土曜日 | Published in | 4 コメント

このエントリーをはてなブックマークに追加

CAGradientLayer を使った描画がしたくて調べている。試しにボタンを作ってみた。


仕組み


UIControl をベースのクラスに使い、その上にレイヤーを重ねて作ってある。
基本の色は UIControl.backgroundColor で決める。

その上に CAGradientLayer をかぶせてグラデーションをかける。グラデーションは白色をアルファ値を変えて表現する。
self.gradientLayer = [CAGradientLayer layer];
    self.gradientLayer.frame = self.bounds;
    self.gradientLayer.locations = [NSArray arrayWithObjects:
                                    [NSNumber numberWithFloat:0.0],
                                    [NSNumber numberWithFloat:0.5],
                                    [NSNumber numberWithFloat:0.5],
                                    [NSNumber numberWithFloat:1.0],
                                    nil]
     self.gradientLayer.colors =
        [NSArray arrayWithObjects:
             (id)[UIColor colorWithWhite:1.0 alpha:0.7].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.4].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.3].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.0].CGColor,
             nil];
    [self.layer addSublayer:self.gradientLayer];
こうするとベースの色(UIControl.backgroundColor)は単純に基本の色を指定するだけで良い。色を加工したり、あらかじめ色のグラーでションを用意する必要が無いので使い勝手がいい(と思う)。

その上にボタンの文字を CATextLayer に描く。
self.textLayer = [CATextLayer layer];
    self.textLayer.string = self.text;
    self.textLayer.font = CGFontCreateWithFontName((CFStringRef)[self _font].fontName);
    self.textLayer.fontSize = [self _font].pointSize;
    self.textLayer.truncationMode = kCATruncationEnd;
    self.textLayer.alignmentMode = kCAAlignmentCenter;
    self.textLayer.shadowColor = [UIColor blackColor].CGColor;
    self.textLayer.shadowRadius = 0.5;
    self.textLayer.shadowOffset =CGSizeMake(-0.5, -0.5);
    self.textLayer.shadowOpacity = 0.5;
    self.textLayer.foregroundColor = [UIColor whiteColor];
    [self.layer addSublayer:self.textLayer];

その上のレイヤーはボタンに立体感を出すためのハイライトライン。
これを描くために CALayer のサブクラス HighlightEdgeLayer を作り自前で線を描画している。
#define CORNER_RADIUS   5.0

@implementation HighlightEdgeLayer
- (void)drawInContext:(CGContextRef)context
{
    CGMutablePathRef path = CGPathCreateMutable();
    CGPoint p = CGPointMake(CORNER_RADIUS, CORNER_RADIUS);

    CGPathAddArc(path, NULL,
                 p.x, p.y,
                 CORNER_RADIUS,
                 2.0*M_PI/2.0,
                 3.0*M_PI/2.0,
                 false);

    p.x = self.bounds.size.width - CORNER_RADIUS;
    CGPathAddArc(path, NULL,
                 p.x, p.y,
                 CORNER_RADIUS,
                 3.0*M_PI/2.0,
                 4.0*M_PI/2.0,
                 false);

    CGContextAddPath(context, path);
    CGPathRelease(path);
    CGContextSetStrokeColorWithColor(context, [UIColor colorWithWhite:1.0 alpha:0.5].CGColor);
    CGContextSetLineWidth(context, 1.0);
    CGContextDrawPath(context, kCGPathStroke);

}
@end
一番上のレイヤーは UIControl に最初からついている CALayer。これで黒い境界線を引き、角を丸くしている。
self.layer.cornerRadius = CORNER_RADIUS;
    self.layer.masksToBounds = YES;
    self.layer.borderColor = [UIColor colorWithWhite:0.0 alpha:0.75].CGColor;
    self.layer.borderWidth = 1.0;

ボタンが押された時には -[UIControl setHighlighted:] が呼ばれるのでここでグラデーションの色を変えてやる。
- (void)setHighlighted:(BOOL)highlighted
{
    [super setHighlighted:highlighted];
    [self _setState:highlighted ? GradientButtonStateHighlighted : GradientButtonStateNormal];
}
サンプルでは3つの状態を定義しておいて、それぞれでグラデーションの色と文字色を変えている。
typedef enum {
    GradientButtonStateNormal = 0,
    GradientButtonStateHighlighted,
    GradientButtonStateDisabled
} GradientButtonState;

- (void)_setState:(GradientButtonState)state
{
    switch (state) {
        case GradientButtonStateNormal:
            self.gradientLayer.colors =
            [NSArray arrayWithObjects:
             (id)[UIColor colorWithWhite:1.0 alpha:0.7].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.4].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.3].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.0].CGColor,
             nil];
            self.textLayer.foregroundColor = self.textColor.CGColor;
            break;
            
        case GradientButtonStateHighlighted:
            self.gradientLayer.colors =
            [NSArray arrayWithObjects:
             (id)[UIColor colorWithWhite:1.0 alpha:0.5].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.2].CGColor,
             (id)[UIColor colorWithWhite:0.0 alpha:0.05].CGColor,
             (id)[UIColor colorWithWhite:0.0 alpha:0.1].CGColor,
             nil];
            self.textLayer.foregroundColor = self.textColor.CGColor;
            break;
            
        case GradientButtonStateDisabled:
            self.gradientLayer.colors =
            [NSArray arrayWithObjects:
             (id)[UIColor colorWithWhite:1.0 alpha:0.7].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.4].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.3].CGColor,
             (id)[UIColor colorWithWhite:1.0 alpha:0.0].CGColor,
             nil];
            self.textLayer.foregroundColor = [UIColor lightGrayColor].CGColor;
            break;
    }
}
押す前

押した後
ディゼーブル


サンプル


何色かのボタンを並べたサンプルを作ってみた。最後のボタンは Disable状態。
ベースの色は階調が見やすいように濃いめの色にした方がいい。これはグラデーションの表現を白で表現している為。標準で UIColor に用意されている greenColor や yellowColor は明るすぎて階調が出づらい。

Xib はこんな感じ。UIView を配置して大きさを調整した後、クラスに GradientButton を指定する。


利用上の注意点としては Xib 上で IBAction を指定すると Value Changed のアクションがデフォルトで選択される。これではボタン押下時のイベントが取れないので Touch up inside を使う。


ソースコード


GitHub からどうぞ。
GradientButton at 2011-10-22 from xcatsan/iOS-Sample-Code - GitHub


参考情報


テン*シー*シー - 【iPhoneアプリ開発ドリル】Aqua風ボタンを作る

CAGradientLayer の使い方はこのサイトがとても参考になった。UIControl をボタンとして実装するアイディアはここから拝借した。情報をどうも!

iPhoneアプリ開発、その(17) 文字だって回転|テン*シー*シー

CATextLayer の使い方もここ。

iphone - How can i convert UIFont to CGFontRef?I causes warning and not working - Stack Overflow

UIFont から CGFontRef を取る方法。
CGFontRef cgFont = CGFontCreateWithFontName((CFStringRef)uiFont.fontName);

サンプルコードのボタンのグラデーション(ハイライト)は iOS のポップアップメニューを参考にした。


なので通常のボタンと見た目がちょっと違う。


関連情報


CATextLayer Class Reference
CAGradientLayer Class Reference


SimpleCap - Selection History Expose [7] 始まりと終わり〜フェードアウト/フェードイン

2010年11月6日土曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: SimpleCap - Selection History Expose [6] 座標系変換(flippedからnon-flippedへ)

[参考] Cocoaの日々: Core Animation - 画面手前からフェードイン / 手前へフェードアウト [MacOSX版]

Core Animation を使い、SHE (Selection History Expose) の出現時と退場時のアニメーションを実装した。静止画で表すとこんな感じ。

まず範囲選択の矩形を表示させる。そして SHEボタン(左から2番目)を押すと..

画面手前から縮小しながらフェードインして履歴矩形が現れる。



履歴矩形をひとつ選択すると今度は反対に拡大しながら手前へフェードアウトする。

動画も作ってみた。


- - - -
検証中も問題になったフェートアウト後に一瞬原寸大表示になる現象が出た。このままだとフェードアウト後にチラつく感じが出てよろしくない。どうしたものか。

Core Animation - 画面手前からフェードイン / 手前へフェードアウト [MacOSX版]

2010年11月5日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回まで] Cocoaの日々: Core Animation - 画面手前からフェードイン / 手前へフェードアウト [2]手直し

SimpleCapに組み込むにあたって気になる部分が出てきたので、やっぱりサンプルを Mac OS X 向けに作り直した。


違い


iPhone版と MacOSX版はどちらも基本的に同等のコードで動作するのだが以下の2点が違った。

UIView と NSView の違い

UIView は必ず CALayer を持っていて変更ができない(layerプロパティは readonly)。また setWantsLayer: は不要(そもそもメソッドが無い)。一方、NSView の場合は layer は自前で用意して設定するのが基本で差し替えも可能。またレイヤーを(明示的に)使う場合は setWantsLayer:YES が必要。

zPositionの適用

iOS の場合、9つのレイヤーを載せたベースのレイヤーに対して zPositionの変更を行うアニメーションを行うことができた。
- (void)animateFadeInOut:(BOOL)flag
{
  :
 CABasicAnimation *animation;
 animation=[CABasicAnimation animationWithKeyPath:@"zPosition"];
  :
 [baseLayer addAnimation:animation forKey:@"zPosition"];
}

Mac OS X の場合、これは駄目で9つのレイヤー一つ一つにアニメーションを設定擦る必要があった。
- (void)animateFadeInOut:(BOOL)flag
{
  :
 for (CALayer* layer in self.view.layer.sublayers) {
  CABasicAnimation *animation;
  animation=[CABasicAnimation animationWithKeyPath:@"zPosition"];
  :
  [layer addAnimation:animation forKey:@"zPosition"];
 }
}
やり方がまずいのかもしれない。opacity は問題なかった。



サンプル実行

実行すると手間から画像が徐々に出てくる。


最後は原寸大で終了

ボタンを押すと今度は逆に拡大しながら手前へフェードアウトする。後はその繰り返し。


ソースコード


GitHubからどうぞ。

CoreAnimation3DSample4MacOSX at 2010-11-05 from xcatsan's MacOSX-Sample-Code - GitHub


- - - -
Mac OS X と iPhone の違いは、他には Core Image の有無がある。将来デバイスの能力が上がってくると搭載される可能性はある。

Core Animation - 画面手前からフェードイン / 手前へフェードアウト [2]手直し

2010年11月4日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: Core Animation - 画面手前からフェードイン / 手前へフェードアウト

前回のコードでは手前にフェードアウトした直後に原寸大が一瞬表示され、その後消えた。

表示を消す処理は、アニメーション終了時にベースのレイヤーを hidden=YES としていた。
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
 if (!fadeIn) {
  CALayer* baseLayer = [self.view.layer.sublayers objectAtIndex:1];
  baseLayer.hidden = YES;
 }
}

今回新たに baseView::UIView を用意し、そこへベースレイヤーを載せ、そして表示を消す時にこのビューを hidden=YES とするようにしてみた。
@interface CoreAnimation3DSample2ViewController : UIViewController {

 BOOL fadeIn;
 UIView* baseView;
}

- (IBAction)fadeOut:(id)sender;
@property (nonatomic, retain) IBOutlet  UIView* baseView;

@end

- (void)viewDidLoad {
  [super viewDidLoad];
  :
 CALayer* baseLayer = [CALayer layer];
 [self.baseView.layer addSublayer:baseLayer];
  :
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
 if (!fadeIn) {
  self.baseView.hidden = YES;
 }
}

するとフェードアウト後の一瞬表示はなくなり真っ黒となった。


シミュレータではうまくいったように見える。
そこで実機で試してみる。

結果は NG

うーむ。
今回は未解決。


ソースコード
CoreAnimation3DSample2 at 2010-11-04 from xcatsan's iOS-Sample-Code - GitHub

Core Animation - 画面手前からフェードイン / 手前へフェードアウト

2010年11月3日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: Core Animation - 画像が遠くから近づいてきてフェードアウトする [2]手直し

前回のコードを改良して、画面手前からフェードインするようなアニメーションを実装してみる。その逆に画面手前へフェードアウトさせる。そう、Dashboardのような動き。


サンプル実行


まずは動作から。こんな感じ。

 画面手前から現れたように拡大で始まる

徐々に縮小して目の前に現れる

最後は実寸大(100x75)表示

ボタンを押すと逆に実寸大の状態から徐々に拡大が始まり、画面の手前へフェードアウトする。


実装


前回の処理でアニメーションを行っていた箇所をメソッドとして切り出す。
#define DURATION 0.5
- (void)animateFadeInOut:(BOOL)flag
{
 CGFloat zPositionFrom = flag ? -1000 : 0;
 CGFloat zPositionTo = flag ? 0 : -1000;
 CGFloat opacityFrom = flag ? 0.25 : 1.0;
 CGFloat opacityTo = flag ? 1.0 : 0.25;

 CALayer* baseLayer = [self.view.layer.sublayers objectAtIndex:1];
 
 CABasicAnimation *animation;
 animation=[CABasicAnimation animationWithKeyPath:@"zPosition"];
 animation.fromValue=[NSNumber numberWithFloat:zPositionFrom];
 animation.toValue=[NSNumber numberWithFloat:zPositionTo];
 animation.duration=DURATION;
 animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
 animation.repeatCount = 1;
 animation.delegate = self;
 
 [baseLayer addAnimation:animation forKey:@"zPosition"];
 
 animation=[CABasicAnimation animationWithKeyPath:@"opacity"];
 animation.fromValue=[NSNumber numberWithFloat:opacityFrom];
 animation.toValue=[NSNumber numberWithFloat:opacityTo];
 animation.duration=DURATION;
 animation.repeatCount = 1;
 animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
 [baseLayer addAnimation:animation forKey:@"opacity"];  
 
}
フェードイン/フェードアウトによって値を逆転させている。フェードインの場合は zPosition -1000=>0, opacity 1.0=>0.25。zPositionが負値なので画面の手前から入ってくるような視覚効果となる。最後の 0 は原寸大の位置。フェードアウトの場合はちょうどその逆となる。


CAAnimation - デリゲート


CAAnimation にデリゲートを指定すると、アニメーションの開始と終了のタイミングで下記のメソッドが呼び出される。
- (void)animationDidStart:(CAAnimation *)theAnimation;
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag

今回はアニメーション終了時にフェートアウトの場合はベースレイヤーを隠している(hidden=YES)。ただこの場合一瞬原寸大で表示されてしまう。アニメーション終了後はアニメーション開始直前の状態に戻っていることがわかる。この一瞬の表示を隠すにはどうしたらよいものか。removedOnCompletion = NO としても駄目だった。

なお delegateプロパティは retain属性が付いている。
CAAnimation Class Reference より転載。
Important: The delegate object is retained by the receiver. This is a rare exception to the memory management rules described in Memory Management Programming Guide.
An instance of CAAnimation should not be set as a delegate of itself. Doing so (outside of a garbage-collected environment) will cause retain cycles.


CAAnimation - タイミング関数


アニメーションのタイミング(速度の緩急)は標準で数種類用意されている。

CAMediaTimingFunction Class Reference より転載
Predefined Timing Functions
These constants are used to specify one of the predefined timing functions used by functionWithName:.

NSString * const kCAMediaTimingFunctionLinear;
NSString * const kCAMediaTimingFunctionEaseIn;
NSString * const kCAMediaTimingFunctionEaseOut;
NSString * const kCAMediaTimingFunctionEaseInEaseOut;
NSString * const kCAMediaTimingFunctionDefault;
Linearは速度一定。EaseIn とは最初遅めで徐々に早くなる。EaseOut はその逆で最初早めで徐々に遅くなる。EaseInEaseOut は最初が遅めで徐々に加速、その後減速して最後は遅くなる。Defaultは Bézier timing function でパラメータが (0.25,0.1), (0.25,0.1) のケース。なお Default は Mac OS X は 10.6 から利用可能。

これらは +[CAMediaTimingFunction functionWithName:] でインスタンスを取得することができる。これを CAAnimation.timingFunctionへセットしてやればよい。
animation.timingFunction =
  [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];

Durationを短くする(0.25〜0.5秒)予定だが、その場合の視覚的効果はあまり差は感じられない。

[参考情報] CAMediaTimingFunction Class Reference


ソースコード


GitHub からどうぞ。

CoreAnimation3DSample2 at 2010-11-03 from xcatsan's iOS-Sample-Code - GitHub

Core Animation - 画像が遠くから近づいてきてフェードアウトする [2]手直し

2010年11月2日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: Core Animation - 画像が遠くから近づいてきてフェードアウトする

前回のコードを少し見直す。


ベースレイヤーの導入


前回は9つのレイヤーすべてにアニメーション設定をしていた。
for (int i=0; i < 9; i++) {
  UIImage* image = [UIImage imageNamed:[NSString stringWithFormat:@"image%02ds.jpg", i+1]];
  CALayer* layer = [CALayer layer];
   :
  CABasicAnimation *theAnimation;
  theAnimation=[CABasicAnimation animationWithKeyPath:@"zPosition"];
  theAnimation.fromValue=[NSNumber numberWithFloat:-4000];
  theAnimation.toValue=[NSNumber numberWithFloat:1000];
  theAnimation.duration=10;
  theAnimation.repeatCount = 1e100;
  [layer addAnimation:theAnimation forKey:@"zPosition"];
  
  theAnimation=[CABasicAnimation animationWithKeyPath:@"opacity"];
  theAnimation.fromValue=[NSNumber numberWithFloat:1.0];
  theAnimation.toValue=[NSNumber numberWithFloat:0.0];
  theAnimation.duration=10;
  theAnimation.repeatCount = 1e100;
  [layer addAnimation:theAnimation forKey:@"opacity"];
 }
全部同じ動きをするのだからどうも無駄な感じがする。9つのレイヤーを載せるベースとなるレイヤーを用意し、これをアニメーションしてもよさそうだ。こんな感じ。
CALayer* baseLayer = [CALayer layer];
 [self.view.layer addSublayer:baseLayer];


 for (int i=0; i < 9; i++) {
  UIImage* image = [UIImage imageNamed:[NSString stringWithFormat:@"image%02ds.jpg", i+1]];
  CALayer* layer = [CALayer layer];
  [baseLayer addSublayer:layer];
   :
 }
 CABasicAnimation *theAnimation;
 theAnimation=[CABasicAnimation animationWithKeyPath:@"zPosition"];
 theAnimation.fromValue=[NSNumber numberWithFloat:-4000];
 theAnimation.toValue=[NSNumber numberWithFloat:1000];
 theAnimation.duration=10;
 theAnimation.repeatCount = 1e100;
 [baseLayer addAnimation:theAnimation forKey:@"zPosition"];
 
 theAnimation=[CABasicAnimation animationWithKeyPath:@"opacity"];
 theAnimation.fromValue=[NSNumber numberWithFloat:1.0];
 theAnimation.toValue=[NSNumber numberWithFloat:0.0];
 theAnimation.duration=10;
 theAnimation.repeatCount = 1e100;
 [baseLayer addAnimation:theAnimation forKey:@"opacity"];
動作結果は OK。

ソースコード

GitHubからどうぞ。 CoreAnimation3DSample at 2010-11-02b from xcatsan's iOS-Sample-Code - GitHub

Core Animation - 画像が遠くから近づいてきてフェードアウトする

2010年11月1日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

SimpleCap の SHE (Selection History Expose) を作っている過程で表題のアニメーション効果が欲しくなった。今回はその検証。


サンプル動作


まずはサンプル動作結果から。

SimpleCap 自体は Mac OS X 向けだが、せっかくなので iOS 用に作ってみた。Core Animation は両OS共に共通なので iOSで動けば Mac OS X でも動くはず(※実際には iOSの方はサブセット)。

実行すると9つの画像が表示され、これが徐々に手前に近づきながらフェードアウトする。





サンプルではこの動作を繰り返す。


実装解説


Xcodeのテンプレートから View-based application を選び viewDidLoad: を実装する。
- (void)viewDidLoad {
    [super viewDidLoad];

 self.view.layer.backgroundColor = [[UIColor blackColor] CGColor];

 // パース設定 
 CATransform3D transform = CATransform3DMakeRotation(0, 0, 0, 0); 
 float zDistance = 2000; 
 transform.m34 = 1.0 / -zDistance;
 self.view.layer.sublayerTransform = transform;
 
 // 9枚の CALayer を準備する
 for (int i=0; i < 9; i++) {
  // バンドルした画像を CALayer へ貼り付ける
  UIImage* image = [UIImage imageNamed:[NSString stringWithFormat:@"image%02ds.jpg", i+1]];
  CALayer* layer = [CALayer layer];
  layer.contents = (id)[image CGImage];
  layer.frame = CGRectMake(x[i], y[i], IMAGE_WIDTH, IMAGE_HEIGHT);
  CGPoint p = layer.frame.origin;
  p.x += 320/2;
  p.y += (480-2)/2;
  layer.position = p;
  [self.view.layer addSublayer:layer];

  // Z軸方向のアニメーション設定
  CABasicAnimation *theAnimation;
  theAnimation=[CABasicAnimation animationWithKeyPath:@"zPosition"];
  theAnimation.fromValue=[NSNumber numberWithFloat:-4000];
  theAnimation.toValue=[NSNumber numberWithFloat:1000];
  theAnimation.duration=10;
  theAnimation.repeatCount = 1e100;
  [layer addAnimation:theAnimation forKey:@"zPosition"];
  
  // 不透明度のアニメーション設定
  theAnimation=[CABasicAnimation animationWithKeyPath:@"opacity"];
  theAnimation.fromValue=[NSNumber numberWithFloat:1.0];
  theAnimation.toValue=[NSNumber numberWithFloat:0.0];
  theAnimation.duration=10;
  theAnimation.repeatCount = 1e100;
  [layer addAnimation:theAnimation forKey:@"opacity"];  
 }
}
これだけ。ポイントはパース設定。これが無いと zPosition を変更してもアニメーションが起こらない。 m34 パラメータは Z軸の初期位置を決める。 m34 = -1.0/200 の場合
m34 = -1.0/2000 の場合
m34 = -1.0/20000 の場合


ソースコード


GitHubからどうぞ。 CoreAnimation3DSample at 2010-11-01b from xcatsan's iOS-Sample-Code - GitHub


参考情報

Watching Apple: Core Animation: 3D Perspective Core Animation を使った擬似3Dアニメーションのサンプル。非常に役に立った。

Core Animation Programming Guide: Introduction to Core Animation Programming Guide Core Animation リファレンス

Core Animationプログラミングガイド: Core Animationプログラミングガイドの紹介 日本語版リファレンス

Core Animationプログラミングガイド: サンプル:Core Animation Menuアプリケーション Core Animation を使った Front Row 風インターフェイスの実装サンプル。参考になる。

人気の投稿(過去 30日間)