Ruby Motionチュートリアルやってみた(6)


Ruby Motionチュートリアルをやってます。前回の記事はこちら。

Ruby Motionチュートリアルはこちら。

▼開発環境
Ruby 2.5.3
Xcode 10.1
Ruby Motion 5.16

さてさて、今回の章ではアニメーションを扱うそうですよ!

僕はアニメが大好きなので嬉しいです^_^(そのアニメじゃないって?知ってますよ。)

さて、実際に勉強して行きましょう!画面の周りにボックスを移動させてみます。

windowにビューを追加うることから始め、設定したポイントの周辺でアニメーションするようにして行きます。

チュートリアルは、AppDelegateにすでに書いてあるやつを消して次のコードを書いてくださいって書いてます。。。せっかく書いたのに;;

def application(application, didFinishLaunchingWithOptions:launchOptions)
 @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
 @window.makeKeyAndVisible
 # the points we're going to animate to
 @points = [[0, 0], [50, 0], [0, 50], [50, 50]]
 @current_index = 0
 # usual method of adding views to our window
 @view = UIView.alloc.initWithFrame [@points[@current_index], [100, 100]]
 @view.backgroundColor = UIColor.blueColor
 @window.addSubview(@view)
 animate_to_next_point
 true
end
def animate_to_next_point
 @current_index += 1
 # keep current_index in the range [0,3]
 @current_index = @current_index % @points.count
 UIView.animateWithDuration(2,
   animations: lambda {
      @view.frame = [@points[@current_index], [100, 100]]
   },
   completion:lambda {|finished|
     self.animate_to_next_point
   }
 )
end

とりあえず、このコードを読み解いて行きますか。

最初のdidFinishLaunchingWithOptionsは大丈夫ですね。問題は次のところかな?

UIView.animateWithDuration(2,
   animations: lambda {
      @view.frame = [@points[@current_index], [100, 100]]
   },
   completion:lambda {|finished|
     self.animate_to_next_point
   }
 )

さてさて、この構文でlambdaが出てきてますね。

lambdaの説明は、チュートリアルを引用します。

二つ目は lambda です。この用語に慣れていない人向けに説明すると、lambda は変数が割り当てられたり引数が渡されたりする関数です。たとえば、my_lambda = lambda { p "Hello!" } とすることができます。ここで my_lambda.call を実行すると、lambda ブロック内のコードが実行されます。lambda は引数を持つこともできます。
my_lambda = lambda { |name| p "Hello #{name}!" }
my_lambda.call("Clay")
=> Hello Clay

メソッドでも同じことできるじゃん!って思ったかもしれませんが、僕の理解だともしメソッドを使うと返り値にオブジェクトが生成されますが、lambdaは生成されないという違いがあります。

要するに処理が軽くなるメリットがあるのかな?

もし、違ったらコメントくれると助かります。

では、コードに戻りましょう。

ポイントは、「UIView.animateWithDuration:animations:completion:」ですね。

これは、アニメーションを制御するためのメソッドだそうです。

第一引数(duration)にはアニメーションを何秒間継続するかを設定します。チュートリアルによると、0.3秒にするのがオススメらしいです。

第二引数はanimationsを指定し、任意のビューに対してlambdaで行った変更がスムーズにアニメーションに変更されるそうです。

え?マジで!?すごすぎて気持ち悪いwww

あ、なんかAndroidの時との比較が書いてある。なんでも、Androidでは望むアニメーションのために各プロパティの独自のAnimationオブジェクトを作成する必要があるそうです。???。よくわからんぞん?まぁ、保留にしとくか。Android持ってないからデバッグできないし。

第三引数には、completionsを指定し、completionsはアニメーションが終了した後に呼び出されるものらしいです。このlambdaでは、必ず引数を指定する必要があって、アニメーションが本当に終了したかを通知するbooleanの値になるそうです。他のアニメーションを明示的に行う場合にのみ、それによってアニメーションが取り消されるため、アニメーションは失敗となることがあるそうです。

さぁ、いよいよ実行してアニメーションを確認してみましょう!

はい、エラーになりましたー。これ、一番最初のところでも同じエラー出ましたね。。。

バージョンがアップされたからか、rootがないとエラーになるらしいです。。。

うん!じゃあ、前の章で作ったアルファベットのテーブルのやつの上にアニメーションを表示させてやる!コードを変更します。

def application(application, didFinishLaunchingWithOptions:launchOptions)
   @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
   @window.makeKeyAndVisible
   # the points we're going to animate to
   @points = [[0, 0], [50, 0], [0, 50], [50, 50]]
   @current_index = 0
   # usual method of adding views to our window
   @view = UIView.alloc.initWithFrame [@points[@current_index], [100, 100]]
   @view.backgroundColor = UIColor.blueColor
   @window.addSubview(@view)
   animate_to_next_point
   controller = TapController.alloc.initWithNibName(nil, bundle: nil)
   nav_controller = UINavigationController.alloc.initWithRootViewController(controller)
   alphabet_controller = AlphabetController.alloc.initWithNibName(nil, bundle: nil)
   tab_controller = UITabBarController.alloc.initWithNibName(nil, bundle: nil)
   tab_controller.viewControllers = [alphabet_controller,nav_controller]
   @window.rootViewController = tab_controller
   @window.addSubview(@view)
   true
 end

これで、再度実行します!

※なんか、Gifがアップロード失敗するので、URL貼っときます。

おおーー!!!!すげーwwwちゃんとアニメーションできてるwww

んで、次が、⑴delayを持たせる、⑵別のアニメーションカーブを持たせる、ということをやっていくらしいです。

iOSのデフォルトのアニメーションカーブが「UIViewAnimationOptionCurveEaseInOut」で定義されているらしいです。デフォルトなかなか悪くないね。

さて、これまで使ってきた「animateWithDuration:animations:completion:」を変更して行きますね。

「animateWithDuration:delay:options:animations:completion:」はアニメーションを開始する前にdelayを追加して、optionsで設定するそうです。delayは秒単位を取って、animations:ブロックの開始までのオフセットになると。

んで、optionsはUIViewAnimationOptionというプレフィックスの値でintegerのビットマスクをとるそうです。

ん?ビットマスクってなんぞ?ちょっとググってくる〜。

ふむふむ。ビット演算するときに、特定のデータを取るために行う作業って感じかな。例えば、

01010110 and 0000001100000010

って感じか。んで、これをやる理由はなんだ?とりあえず、チュートリアルを引用。

リニアなアニメーションカーブと自動的にアニメーションを繰り返すというような、同時に複数のアニメーションオプションを組み合わせたい場合に備えビットマスクになっています(options: (UIViewAnimationOptionCurveLinear | UIViewAnimationOptionRepeat))。

おおー!ビットマスクすげー。これなら直感的に複数のアニメーションを組み合わせれるね!!

実際にやってみましょう!animate_to_next_pointを変更して行きます。

def animate_to_next_point
 @current_index += 1
 @current_index = @current_index % @points.count
 UIView.animateWithDuration(2,
   delay: 1,
   options: UIViewAnimationOptionCurveLinear,
   animations: lambda {
      @view.frame = [@points[@current_index], [100, 100]]
   }, completion:lambda {|finished|
     self.animate_to_next_point
   })
end

よっしゃ!それじゃ、早速実行〜♪

まとめ

You Know This Section
iOS の基本的なアニメーションを扱ってきました。これまでどんなことをやってきましたか?

・animateWithDuration:animations: と類似したものを使い、ビューのプロパティを animations: の lambda で変更することでアニメーションが動きました。
・アニメーションカーブによってアニメーションの動作が決まります。UIViewAnimationOptionCurveLinear と UIViewAnimationOptionCurveEaseInOut を例として扱いました。

サポートしていただけると、泣いて喜びます! 嬉しくて仕事をめちゃめちゃ頑張れます。