スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

RubyCocoaでアニメーション:レイヤーの階層構造を作る

 昨日うまくいかなかった移動しながらの画像の押し出し効果だが、今日提供されたOSのアップデータをかけてもそのままであった(^^; しかたがないので、なんとかうまく表示できないか、考えてみることにする。
 動きをよく見ると、どうも移動途中のレイヤーの座標がうまくとれていなくて、押し出される側の画像を、レイヤーが最初にいた位置に表示してしまっているようである。
 そこで、画像を表示してるレイヤーの親レイヤーを作って、移動のアニメーションはそちらで行なうようにする。そうすると、画像を切り替えている方のレイヤーは全く動いていないことになるので、表示する位置がずれるということはなくなるはずだ。図で表わすと、こんな感じになる。
sublayer.png

 では、コードを書いて行こう。まず、awakeFromNibで表示するレイヤーを背景レイヤーに入れている箇所
backgroundLayer.addSublayer_(layer)

を消して、以下のように書き換える。
transitionLayer =OSX::CALayer.layer()
transitionLayer.frame = layer.frame
transitionLayer.addSublayer_(layer)
transitionLayer.masksToBounds=true
backgroundLayer.addSublayer_(transitionLayer)

このコードでは、新しくtransitionLayerを作り、その中に、いままでのレイヤーをaddSublayerで加えている。そして背景レイヤーには新しく作ったtransitionLayerの方を入れているのだ。

 続いて、画像切り替えのcangeContentsの方にも手を入れよう。contentsの中身を入れ替えるレイヤーは一つ階層が深くなっているのだから、最初にレイヤーを取得するコードを
layer = @view.layer.sublayers.objectAtIndex_(0)

から
transitionLayer = @view.layer.sublayers.objectAtIndex_(0)
layer=transitionLayer.sublayers.objectAtIndex_(0)

に変更する。

 これで、アニメーションしながら押し出しの効果を実行しても、おかしな表示にはならないはずだ。

 しかし、移動のアニメーションはちゃんと動作するものの、フィルターのアニメーションが動かなくなっている。アニメーションがtransitionLayerの方についているが、フィルターはもとのlayerの方についているため、フィルターのアニメーションが利かなくなっているのだ。
 そこで、awakeFromNibでfilterをつけるレイヤーをtransitionLayerの方に変えてやる。
layer.setFilters_(filterArray)


transitionLayer.setFilters_(filterArray)

こうかえるわけだ。

これで、思った通りの動きをするようなっているはずだ。

スポンサーサイト

RubyCocoaでアニメーション:表示している画像を入れ替える

 今度は、表示している画像を入れ替えてみよう。そのためには、レイヤーのcontents属性を変えてやればいい。新しくchangeボタンを追加して、このボタンがクリックされたら別の画像に変化するようにしてみる。
まずは、AppController.rbにインターフェースとなるib_actionを宣言する。

ib_action :changeContents

 ここでAppController.rbを保存し、MainMenu.nibをダブルクリックしてインターフェースビルダーで、画面にchangeボタンを配置し、AppController.rbとの間にコネクションを貼る。ここまで終わったら、再びXCodeに戻って、changeContentsに以下のコードを記述する。

def changeContents
bitmapImage = OSX::NSBitmapImageRep.imageRepWithContentsOfFile_(
'/Users/takayukiu/Documents/P1000342.jpg')
image = bitmapImage.CGImage()
layer = @view.layer.sublayers.objectAtIndex_(0)
layer.contents=image
end

 見れば分かる通り、単純に別のファイルから新しいimageを作ってレイヤーのcontents属性に代入しているだけである。これでchangeボタンを押せば画像が新しいものに入れ替わる。フィルターの効果もそのまま残っているのが分かるはずだ。
 で、フィルターがあるから若干分かりづらいのだが、切り替わるときに短い時間だがフェードの効果がかかっている。実はこのcontents切り替わり時の効果もプログラムで変更することが出来る。これにはアクションという機能を利用する。詳細な説明は例によってアップルのドキュメントを参照して欲しい。
Core Animationプログラミングガイド;アクション
 いまいち分かりにくいかもしれないが、簡単に言ってしまうと、何かレイヤーの属性を変化させるような操作を行ったときに、自動的に発動するプログラムを登録しておくことが出来るしくみ、と言えばいいのだろうか。今回は、この機能を使って自分で定義した切り替え効果を実行する。

 まずは、使用する切り替え効果を作成する。これはトランジションアニメーションと呼ばれている。詳しい説明はリンク先を読んでもらうとして、さっそく使ってみよう。先ほど作成したchangeContentsで、レイヤーのcontentsにimageを代入している文の直前に、以下のコードを追加する。
transition = OSX::CATransition.alloc.init
transition.duration = 1.5
transition.type =OSX::KCATransitionPush
transition.subtype = OSX::KCATransitionFromRight

 これは、1.5秒かけてふるい画像を新しい画像が右から押し出して行く、という効果になる。次にこれをレイヤーのcontentsが変化したときのアクションとして登録する。
layerActions = OSX::NSMutableDictionary.alloc.init
layerActions.setObject_forKey_(transition,'contents')
layer.actions=layerActions

 これでビルドして実行し、changeボタンをクリックすると、画像が押し出されながら切り替わるようになるはずだ。

……ところが、これでめでたしめでたしとはならないのが厄介なところ。止まっているときにはこれでOKなのだが、アニメーションしている最中にchangeボタンをクリックするとおかしな動きをしてしまう。どうもOS側のバグのようなのだが……。

RubyCocoaでアニメーション:レイヤーにフィルタを適用する

 CoreAnimationには、アニメーション以外にも強力な機能がある。前回の最後にリンクを貼ったドキュメントにも記載があったのだが、レイヤーに対してはCore Imageのフィルタが適用できるのだ。
 と、いきなり言われてもピンと来ないかもしれない。とりあえず実際に試して見よう。画像をセピア調に換えるフィルターを使ってみることにする。そのためにはAppController.rbのawakeFromNibに、以下のコードを追加する。 
filter = OSX::CIFilter.filterWithName_('CISepiaTone')
filter.setDefaults
filter.setName_('sepiaFilter')
layer.setFilters_(OSX::NSArray.arrayWithObject_(filter))

 これでビルドして実行すると、画像がセピア調になる。このようにCIFilterを作成して、レイヤーのfilters属性に設定してやるだけで、画像にフィルターがかかるのだ。このフィルター、OSで用意されているものだけでもかなり多い。さらに、自分で新しいフィルターを作ることもできるし、サードパーティが作成したフィルターがインストールされているなら、それを使うこともできる。
OSにあらかじめ用意されているフィルターの一覧は、下のページから見ることができる。
Core Image プログラミングガイド:Core Image フィルタ

 ところで、この属性がfiltersとなっていることに気がついただろうか? 引数として配列を渡すようになっていることからも分かるかもしれないが、複数のフィルターを設定することができる。セピア調にしながら、ブルームフィルターをかける、なんてことも可能なのだ。さらに適用したフィルターのパラメータにアニメーションを設定してやると、フィルターの効果を時間によって変化させることができる。

 実際に試してみよう。先ほどのコードで、レイヤーにフィルターを設定していた、
layer.setFilters_(OSX::NSArray.arrayWithObject_(filter))

の部分を消して、以下のように書き換える。
# ブルームフィルターを作成する
filter2 = OSX::CIFilter.filterWithName_('CIBloom')
filter2.setDefaults
filter2.setValue_forKey_(OSX::NSNumber.numberWithFloat_(0.0), 'inputIntensity')
filter2.setValue_forKey_(OSX::NSNumber.numberWithFloat_(5.0), 'inputRadius')
filter2.setName_('bloomFilter')
# セピア調フィルタとブルームフィルターを配列にして、レイヤーに設定
filterArray = OSX::NSMutableArray.arrayWithObject_(filter)
filterArray.addObject_(filter2)
layer.setFilters_(filterArray)

これで、二つのフィルターがレイヤーに適用されるようになる。(ただし、この時点ではブルームフィルターのinputIntensityが0なので、セピア調フィルターの効果しか現われていない)

 続いて、フィルターのアニメーションをさせるために、doAnimationに以下のコードを書き加える
pluseAnimation = OSX::CABasicAnimation.animation
pluseAnimation.keyPath='filters.bloomFilter.inputIntensity'
pluseAnimation.fromValue = OSX::NSNumber.numberWithFloat_(0.0)
pluseAnimation.toValue = OSX::NSNumber.numberWithFloat_(1.5)
pluseAnimation.duration = 1.0
pluseAnimation.repeatCount = 1e100
pluseAnimation.autoreverses = true
layer.addAnimation_forKey_(pluseAnimation,'bloom')

 ここでは二行目のkeyPathに注目して欲しい。
'filters.bloomFilter.inputIntensity'とある。これはawakeFromNibでフィルターを作成したときに、
filter2.setName_('bloomFilter')

と書いて付けた名前を使って、どのフィルターにアクセスするかを指定している。当然、セピア調フィルターのパラメータを変化させようと思ったらこの部分は'filters.sepiaFilter.xxxx~'となるわけだ。

 これでビルドして実行すると、画像がセピア調になったまま、輝きを増したり減じたりという表示になるはずだ。もちろん、移動のアニメーションはそのままである。

RubyCocoaでアニメーション:自分でアニメーションを設定する

前回やった方法で、一応アニメーションさせることはできたが、実際に自分のプログラムで使おうと思ったら、スピードを変えたり、何回も繰り返したりとか細かい制御をしたいことが多くなるだろう。
そんな場合は、自分でアニメーションオブジェクトを作ってアニメーションさせたいレイヤーに加えてやればいい。

そのために、前回作成したプログラムのdoAnimationを次のように変更する。
def doAnimation(sender)
layer = @view.layer.sublayers.objectAtIndex_(0)
# アニメーションを作成する
theAnimation = OSX::CABasicAnimation.animation
theAnimation.keyPath='position'
theAnimation.duration=5.0
theAnimation.toValue = OSX::NSValue.valueWithPoint_(
OSX::NSPointFromCGPoint(OSX::CGPointMake(400,300)))
# 作成したアニメーションをレイヤーに追加する
layer.addAnimation_forKey_(theAnimation,'move')

end


アニメーションさせるレイヤーを変数layerに代入するところまでは前回と同じ。その後で、theAnimationという名前でアニメーションオブジェクトを作成する。そしてkeyPathにアニメーションさせたいlayerの属性(この場合は、レイヤーの位置=position)を入れる。durationには再生時間を、toValueには最後の値(この場合は目的地の座標)を入れる。
ここで注意しないといけないことは、アニメーションオブジェクトのtoValueに入れる値はNSValueというオブジェクトにする必要があるということだ。前回のコードと同じ感じで、
theAnimation.toValue =OSX::CGPoitnMake(400,300)

と書いてしまうとエラーになってしまう。

ここまでできたら、またビルドして動かして見よう。ゆっくり時間をかけて画像が左下から右上に動くはずだ。さらに、theAnimationにいろいろと値を設定することで、アニメーションの動きを変更することができる。例えば同じ動きを繰り返す場合は、
theAnimation.repeatCount=3

とrepaatCountに回数を代入する。また、動いた後に元の位置にアニメーションしながら戻ってくるようにするには、
theAnimation.autorevers=true

と指定する。また、timingFunctionを指定すると、アニメーションの加速/減速を指定することもできる。以下は、加速と減速を指定した場合である。この設定を追加すると、動き始めはゆっくりで次第に加速し、停止するときに再び減速するアニメーションが表示できる。
theAnimation.timingFunction =
OSX::CAMediaTimingFunction.functionWithName_(
OSX::KCAMediaTimingFunctionEaseInEaseOut)

 このとき、ひとつ注意しておくことがある。
OSX::KCAMediaTimingFunctionEaseInEaseOutというのは、CoreAnimationが用意している定数なのだが、Objective-CではkCAMediaTimingFunctionEaseInEaseOutと先頭は小文字で定義されている。だが、Rubyの言語仕様では、定数は先頭一文字が大文字にしないとならない。そして、XCodeでコード補完機能を使っていると、小文字で書かれてしまい、そのまま実行するとそこでエラーになってしまう。私はこれでずいぶん悩んだ(^^;

 今回と前回は位置の移動をアニメーションしてみたが、CoreAnimationでできるのはこれだけじゃない。例えば透明度を変化させたい場合は、

theAnimation.keyPath='opacity'
theAnimation.duration=5.0
theAnimation.toValue = OSX::NSNumber.numberWithFloat(0.5)

とkeyPathに'opacity'を入れてやり、toValueに変化させたい値を入れればよい。

 アニメーションできる属性にどんなものがあり、それぞれどんな意味をもっているかはアップルの次のドキュメントを参照するといい。

Core Animationプログラミングガイド: アニメーション化可能プロパティ
Core Animationプログラミングガイド: レイヤスタイルプロパティ

RubyCocoaでアニメーション:最初のアニメーションプログラム

長いので、本文は続きを読むで

RubyCocoaでアニメーション

 今年に入ってからちょっとずつCoreAnimationをいじっていたのだが、PyObjCでCoreAnimation入門というのを見て、Rubyからも使えそうだということがわかり、ちょうどRubyCocoaにも手を出そうかと考えていたところなので、試して見た。

 とりあえず、RubyCocoaに関してはチュートリアルを見て、だいたいの雰囲気を掴むことに成功。そして、CoreAnimationについては、木下さんの解説記事を見ながら、同じような感じでやってみたら、割と簡単に動いた。ここまでは、ある程度MacやRubyでコード書いたことがあれば、到達できるんじゃないかな。
 というわけで、ドキュメント読みながら、いろいろ試行錯誤中。明日あたりから、エントリーに上げて行きます。もうちっと早めにやるはずだったんだがな(^^;

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。