Androidで,複数のAnimationを「順番に」実行するためのライブラリ (XMLを使わずに「連続した動きの変化」を指定し,逐次実行するDSL)
重要なお知らせ:
この記事で公開した情報は,AndroidのMVCフレームワーク「Android-MVC」の機能の一部として取り込まれました。
より正確な設計情報や,動作可能な全ソースコードを閲覧したい場合,「Android-MVC」の公式ページより技術情報を参照してください。
AndroidのMVCフレームワーク - 「Android-MVC」
http://code.google.com/p/android-mvc-...
Androidアプリの画面上で,ダイナミックな視覚効果を表現するためには,
SDKに組み込み済みの アニメーション API を利用する。
もし,複数のAnimationを組み合わせて利用する場合,
「複数」という語には,2通りの意味が存在する。
- (1)AnimationSetとして合成されて,同一のタイミングで実行される。
- (2)時間的な順序で連続して,「順番に」個別に実行される。
前者(1)のケースでは,複数のアニメーションを合成し,1つのアニメーションとして扱うことが可能だ。
したがって,合成結果である「AnimationSet」オブジェクトは,普通の1つの「Animation」オブジェクトとして取り扱う事ができる。
※AnimationSetがAnimationを継承しているため。
では,後者(2)のケースはどうか?
「移動する」「回転する」「アルファ値を遷移させる」「伸縮する」
などの複数のアニメーションを,順番に実行したい。
しかも,動作対象となるViewを,途中で自由に切り替えたい。
また,間にポーズ(一時停止)などを自由に挟みたい。
なおかつ,シンプルなコードにしたい。
こういう場合,どうするか。
「連続した複数のアニメーション」を,シンプルにコーディングしよう
上記の(1)と(2)の両方の要望を同時に叶えて,簡潔にプログラミングしよう。
例えば,下記のようなフローの連続したアニメーションを考える。
- View1が,下にスーッと移動。 その後,ちょっと停止。
- 次に,View1とView2が,いっぺんに右にスーッと移動。 その後,ちょっと停止。
- 次に,View1とView2が,同時にスーッと上に移動しながら,フェードアウト。 しばらくそのまま。
- 全てのアニメーションが終わったら,自動的に何らかの処理(ここでは画面遷移)を実行する。
このフローを,Activity上で下記のようにコーディングできたら便利ではないか?
Activity:
package com.example; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.TranslateAnimation; import android.widget.TextView; public class AnimTestActivity extends Activity implements OnClickListener { SequentialAnimationsRunner anim_runner; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); findViewById(R.id.main_btn).setOnClickListener(this); // アニメーションを定義 TextView tv1 = (TextView)findViewById(R.id.tv1); TextView tv2 = (TextView)findViewById(R.id.tv2); final Activity activity = this; anim_runner = new SequentialAnimationsRunner(this) .add( // アニメーションのターゲットを設定 new AnimationDescription().targetViews( tv1 ) , new AnimationDescription(){ @Override protected Animation describe() { // 下方向に移動 Animation anim = new TranslateAnimation( Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, 150f ); return anim; } @Override protected void modifyAfterAnimation(View v) { // 下にずらす ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)v.getLayoutParams(); lp.setMargins(lp.leftMargin, lp.topMargin + 150, lp.rightMargin, lp.bottomMargin); v.setLayoutParams(lp); } }.waitBefore(1000).duration( 2000 ).waitAfter( 1000 ) , // アニメーションのターゲットを変更 new AnimationDescription().targetViews( tv1, tv2 ) , new AnimationDescription(){ @Override protected Animation describe() { // 右方向に移動 Animation anim = new TranslateAnimation( Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, 150f, Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, 0f ); return anim; } @Override protected void modifyAfterAnimation(View v) { // 右にずらす ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)v.getLayoutParams(); lp.setMargins(lp.leftMargin + 150, lp.topMargin, lp.rightMargin, lp.bottomMargin); v.setLayoutParams(lp); } }.duration( 1000 ).waitAfter( 500 ) , new AnimationDescription(){ @Override protected Animation describe() { // 上方向に移動 Animation anim1 = new TranslateAnimation( Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, 0f, Animation.ABSOLUTE, -150f ); // フェードアウト Animation anim2 = new AlphaAnimation(1f, 0f); // これらのアニメーションを合成して同時進行させる AnimationSet anim_set = new AnimationSet(true); anim_set.addAnimation(anim1); anim_set.addAnimation(anim2); return anim_set; } @Override protected void modifyAfterAnimation(View v) { // 消える v.setVisibility(View.GONE); } }.duration( 2000 ).waitAfter( 2000 ) ) .onFinish( new AnimationsFinishListener(){ @Override protected void exec() { Log.d("AnimTest", "全アニメーションが終了したため,画面遷移します。"); // 終わった後の処理。画面遷移 activity.startActivity( new Intent( activity, AnimTestActivity.class ) ); } }) ; // この場ですぐにstart() で開始することもできるが, // 実行中のロック保持のため,いったんインスタンスを保持する。 } @Override public void onClick(View v) { Log.d("AnimTest", "開始ボタンが押されました。"); // アニメ開始 anim_runner.start(); } }
単純に,時系列にアニメーション制御の仕様が記述されているので,
ソースコードから,アニメーションのフローを容易に理解できる。
一つのアニメーションの終了をListenしてリスナ内で次のアニメを呼び出して・・・という,非同期プログラミングでよく見られるコールバックの入れ子地獄も味わわなくて済む。
そして,このように時系列に記述したコード内容にしたがって,
各アニメが「逐次的に」(シーケンシャルに)ちゃんと動作する。
画面構成は下記の通りで,アニメーションを発動させるためのボタンが1個置いてある。
なお,ボタンが押されてから,全アニメーションが終了するまでの間,重複してボタンを押す事はできないように工夫してある。
レイアウトXML:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <Button android:id="@+id/main_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="開始" android:layout_marginTop="0px" /> <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="内容1" android:layout_marginTop="100px" /> <TextView android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="内容2" android:layout_marginTop="200px" /> </RelativeLayout>
- 移動アニメーション後に相対位置で各Viewの座標を設定したりするので,LinearLayoutではなくRelativeLayoutを使う。
このようなコードを動作させるための,ライブラリ・クラスを実装する。
このライブラリは,「アニメーションを逐次実行するためのDSL」を提供する。
アニメーションを逐次実行するためのランナークラス
このライブラリを使えば,利用側のコードでは,スレッドの並列性を全く意識しなくてすむ所がポイント。