画面を、1秒ごととか1分ごとに書き換えることはできるようになったので、次は、なんかアニメーションしたいですね。
時計作るにしても、時刻が変わるときに、ちょっとした動きを入れたいですからね!でもどうやってやるんでしょう?
サンプルから、それっぽいプログラムを見てみましょう。watchapps/feature_property_animation が良さそうです。アニメーションだけを行う、シンプルなサンプルっぽいです。ソース取り込んで、そのまま動かしてみましょう。
こういう感じに、ボタンを押すごとに、アニメーションが発生します。
そういえば、ボタン操作のやり方も、まだ知りませんでした。せっかくなので一緒に見ときましょう。
ソースファイルの定義から見ていくと、さっそくアニメーションっぽいものがありました。
static PropertyAnimation *s_prop_animation;
PropertyAnimation の公式ドキュメントはこちら。
Writing Pebble Apps : Display & Animations
Pebble C : User Interface : Animation
いろいろ書かれていますが、ひとまずはサンプルを追っかけます。
main() /init() /deinit() のあたりは、いままで作ってきたプログラムと同じですね。
main_window_load() で、textLayerを作成して、大きさ(0,0,60,60)に指定、”Started!”とテキストを設定してます。
GRect to_rect = GRect(84, 92, 60, 60);
で、何やら場所を指定しています。プログラム起動後、最初に表示されるテキスト位置っぽい。
s_prop_animation = property_animation_create_layer_frame(text_layer_get_layer(s_text_layer), NULL, &to_rect);
で、最初に定義した s_prop_animation を作ってます。むむむ、なんか難易度高くなった。property_animation_create_layer_frame はナニモノか。
PropertyAnimation * property_animation_create_layer_frame(struct Layer * layer, GRect * from_frame, GRect * to_frame)
layerにアニメーションさせるLayer、from_frame に最初の位置、to_frameに移動先の位置を入れるようだ。初期位置は特に無いからNULLなのか。
animation_schedule((Animation*) s_prop_animation);
main_window_load 終了。アニメーションをスケジュールに登録しているということだな。ここまでで、起動して、右端に”started!”が表示されるところまでの動作ですね。
この後の動きは、ボタンを押したときに発生します。ボタン押したときの動作定義はここ。
static void click_config_provider(void *context) { window_single_click_subscribe(BUTTON_ID_UP, click_handler); window_single_click_subscribe(BUTTON_ID_SELECT, click_handler); window_single_click_subscribe(BUTTON_ID_DOWN, click_handler); }
上、真ん中(セレクト)、下のボタンが押されたときに、click_handlerが呼び出されるように指定しています。全部、呼び先は同じか。
これを init() で登録してました。
window_set_click_config_provider(s_main_window, click_config_provider);
そしてclick_handlerでは…
static void click_handler(ClickRecognizerRef recognizer, void *context) { Layer *layer = text_layer_get_layer(s_text_layer); GRect to_rect = (s_toggle) ? GRect(4, 4, 120, 60) : GRect(84, 92, 60, 60); s_toggle = !s_toggle; destroy_property_animation(&s_prop_animation); s_prop_animation = property_animation_create_layer_frame(layer, NULL, &to_rect); animation_set_duration((Animation*) s_prop_animation, 400); switch (click_recognizer_get_button_id(recognizer)) { case BUTTON_ID_UP: animation_set_curve((Animation*) s_prop_animation, AnimationCurveEaseOut); break; case BUTTON_ID_DOWN: animation_set_curve((Animation*) s_prop_animation, AnimationCurveEaseIn); break; default: case BUTTON_ID_SELECT: animation_set_curve((Animation*) s_prop_animation, AnimationCurveEaseInOut); break; } /* // Example animation parameters: // Duration defaults to 250 ms animation_set_duration(&s_prop_animation->animation, 1000); // Curve defaults to ease-in-out animation_set_curve(&s_prop_animation->animation, AnimationCurveEaseOut); // Delay defaults to 0 ms animation_set_delay(&s_prop_animation->animation, 1000); */ animation_set_handlers((Animation*) s_prop_animation, (AnimationHandlers) { .started = (AnimationStartedHandler) animation_started, .stopped = (AnimationStoppedHandler) animation_stopped, }, NULL /* callback data */); animation_schedule((Animation*) s_prop_animation); }
うおっと、わりかし長かった。ここがこのプログラムのキモですね。
まず、layerにs_text_layerを設定した後、
GRect to_rect = (s_toggle) ? GRect(4, 4, 120, 60) : GRect(84, 92, 60, 60); s_toggle = !s_toggle;
関数が呼ばれるごと、つまりボタンが押されるごとに、位置を入れ替えてます。
次に…
destroy_property_animation(&s_prop_animation);
なんじゃこれ?なんでいきなりdestroy。ボタン連打とかでアニメーションが途中の時に呼ばれた対応と思われる。おそらくそうだ。ということにして、あとまわしにします。
s_prop_animation = property_animation_create_layer_frame(layer, NULL, &to_rect);
これ、さっきもあったやつですね。あ、そうか、from_frame を NULLにすると、layerの現在位置からのアニメーションになるのか。
animation_set_duration((Animation*) s_prop_animation, 400);
アニメーションをどれぐらいの時間をかけて行うかの指定ですね。400 ms を指定しています。
そして、どのボタンが押されたかによって、動作を変えていますね。AnimationCurveの説明によれば、
AnimationCurveEaseOut:だんだんゆっくりになる
AnimationCurveEaseIn:だんだん早くなる
AnimationCurveEaseInOut:だんだん早くなって終わる前にゆっくりに戻る
のようです。だた、エミュレータだとそこまで違いが判別できないような気もする。
後ろのコメントによれば、
・Durationを指定しなければデフォルトで250 ms
・Curveを指定しなければデフォルトでAnimationCurveEaseOut
・ここでは指定しませんでしたが、ディレイ指定しなければデフォルトで 0ms
になるとのこと。
その後、コールバック設定しています。
animation_set_handlers((Animation*) s_prop_animation, (AnimationHandlers) { .started = (AnimationStartedHandler) animation_started, .stopped = (AnimationStoppedHandler) animation_stopped, }, NULL /* callback data */);
アニメーション開始時、終了時に呼ばれる関数の設定ですね。
開始時には animation_started で
static void animation_started(Animation *animation, void *data) { text_layer_set_text(s_text_layer, "Started."); }
テキストを “Started.” に設定
終了時には、 animation_stoppedで
static void animation_stopped(Animation *animation, bool finished, void *data) { text_layer_set_text(s_text_layer, finished ? "Hi, I'm a TextLayer!" : "Just Stopped."); }
アニメーションの状態によって、終わってれば”Hi, I’m a TextLayer!”に設定し、終わってなければ(どういうときだろう?)”Just Stopped.”に設定してます。
あとはスケジュール登録
animation_schedule((Animation*) s_prop_animation);
でアニメーション開始ですね。
さきほどの destroy_property_animation の中も見ときましょう。
・prop_animation が NULL ならそのまま終了。
・スケジュールに登録されていたら、スケジュールから外す。
・そして property_animation_destroy で s_prop_animation の内容破棄、
・その後、s_prop_animation に NULL設定。
というかんじ。
これ main_window_unload からも呼ばれてるんですね。中身があるかどうか、スケジュール登録されてるかどうかで、処理違ってきますからね。
アニメーションによるレイヤー移動の基本的なやり方はわかりました。ついでにボタンクリック取得もOKだ。