package com.android_mvc.framework.ui.anim; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.android_mvc.framework.common.FWUtil; import com.android_mvc.framework.ui.anim.desc.AnimationDescription; import android.app.Activity; import android.view.View; import android.widget.RelativeLayout; /** * 複数のAnimationを順番に実行するためのランナー。 * @author id:language_and_engineering * */ public class SequentialAnimationsRunner { // @see http://d.hatena.ne.jp/language_and_engineering/20120416/AndroidAnimationSetSequentialDSL // アニメーションを走らせる画面 private Activity target_activity = null; // アニメーション詳細設定たち private List<AnimationDescription> descriptions = new ArrayList<AnimationDescription>(); // 全終了後のリスナ private AnimationsFinishListener animationsFinishListener = null; // アニメーション適用対象Viewたち private ArrayList<View> current_target_views = new ArrayList<View>(); // 現在取り扱い中の詳細設定のインデックス private int current_description_cursor = 0; // 1スレッドを使いまわすサービス private ExecutorService exService = null; // 全アニメーションを実行途中であるかどうか(簡易ロック用) private boolean executing_flag; private RelativeLayout root_layout; // ------- 初期化処理 -------- /** * 初期化 */ public SequentialAnimationsRunner(Activity activity) { this.target_activity = activity; } /** * アニメーション詳細設定たちを追加。 * 可変長引数で何個でも可能。 */ public SequentialAnimationsRunner add( AnimationDescription...descs ) { for( AnimationDescription desc : descs ) { descriptions.add( desc ); //FWUtil.d(descriptions.size() + "個目のdescriptionがaddされました。"); } return this; } /** * アニメーションの舞台となるルートレイアウトを指定する。 */ public SequentialAnimationsRunner rootLayoutIs(RelativeLayout layout) { this.root_layout = layout; return this; } /** * アニメーション終了時の挙動を設定。 */ public SequentialAnimationsRunner onFinish(AnimationsFinishListener animationsFinishListener) { this.animationsFinishListener = animationsFinishListener; //FWUtil.d("runnerにanimationsFinishListenerがセットされました。"); return this; } /** * アニメのタイトルを記述 */ public SequentialAnimationsRunner title(String s) { // 何もせず return this; } // ------- 全Descriptionのスキャン処理 -------- /** * 全アニメーションを開始する。 */ public void start() { // 開始済み? if( executionAlreadyStarted() ) { // ランナーのインスタンス単位で排他する。 FWUtil.d("このインスタンスのアニメーションは既に開始済みです。"); } else { executing_flag = true; // 全部実行開始 execAllDescriptions(); } } /** * 登録された全詳細を実行開始 */ private void execAllDescriptions() { // NOTE: 1個以上の追加は前提とする // アニメーションはパフォーマンスを気にすべき処理なので // シングルスレッドを使いまわして毎回のスレッド生成のオーバーヘッドを省く exService = Executors.newSingleThreadExecutor(); // @see http://www.techscore.com/tech/Java/JavaSE/Thread/7-2/ // https://gist.github.com/1764033 // カーソルを先頭にセット current_description_cursor = 0; //FWUtil.d("最初の詳細を実行開始します。"); // 開始 execCurrentDescription(); } /** * 現在のカーソルが指し示すアニメーション詳細設定を実行する。 */ private void execCurrentDescription() { // 現在のDescriptionを取得 AnimationDescription desc = descriptions.get(current_description_cursor); // 具体的なAnimationと前後のフック executeDescribedAnimation(desc); // 現在のターゲットとなるViewを覚えさせる。 // ユーザ記述側のメソッド内でセットされた可能性もあるので,それよりも後に行なう。 updateTargetsIfSpecified(desc); } // ------- 個別のアニメーション実行処理 -------- /** * 1つのアニメーションまたはAnimationSetを実行 */ private void executeDescribedAnimation( final AnimationDescription desc ) { // 処理させる準備 desc.current_target_views = current_target_views; desc.target_activity = target_activity; desc.root_layout = root_layout; // スレッド生成のコストを省きつつ,別スレッドでアニメを開始。 // NOTE: 別スレッドに分ける理由は待機処理などが入るから。 exService.execute(new Runnable(){ @Override public void run() { // 別スレッドでアニメーションを実行 desc.carryAnimationFlowOnOtherThread(); // 元スレッドに終了を通知 onCurrentDescriptionFinished(); } }); } // ------- 複数アニメーションの制御 -------- /** * アニメーションの実行が開始しているかどうか */ private boolean executionAlreadyStarted() { return executing_flag; } /** * 1つ分の詳細を扱い終わった際に呼ばれる。 */ protected void onCurrentDescriptionFinished() { //FWUtil.d("1ステップのアニメーション実行完了時点として,ランナーに終了を通知します。"); // これで全部終わりか判定 if( allDescriptionsFinished() ) { //FWUtil.d("全詳細の扱いが終了しました。"); // 全部実行終了 target_activity.runOnUiThread(new Runnable(){ @Override public void run() { // 事後処理 //FWUtil.d("全詳細の事後処理を開始します。"); afterAllExecuted(); } }); executing_flag = false; } else { // 次の詳細へ execNextDescription(); } } /** * 全詳細を扱い終えたかどうか判定 */ private boolean allDescriptionsFinished() { return ( current_description_cursor == ( descriptions.size() - 1 ) ); } /** * 次の詳細を実行する */ private void execNextDescription() { // カーソルをインクリメント current_description_cursor ++; //FWUtil.d("次の詳細へ進みます。現在のカーソルは" + current_description_cursor); // 開始 execCurrentDescription(); } /** * もし必要なら,詳細の指示通りに,アニメーション適用対象を変更する。 */ private void updateTargetsIfSpecified(AnimationDescription desc) { // ターゲットが変更されたか if( desc.new_target_views != null ) { // まず全部クリア current_target_views.clear(); // 1個ずつ登録しなおす for( View target_view : desc.new_target_views ) { if( target_view == null ){ FWUtil.e("targetとしてnullが追加されています。"); } current_target_views.add( target_view ); } FWUtil.d("ターゲットが変更されました。個数は" + current_target_views.size() ); } } /** * 全アニメーション終了後の処理をUIスレッド上で実行 */ private void afterAllExecuted() { // 登録されていれば if( animationsFinishListener != null ) { animationsFinishListener.exec(); } } }