スポンサーリンク

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)の両方の要望を同時に叶えて,簡潔にプログラミングしよう。

例えば,下記のようなフローの連続したアニメーションを考える。

  1. View1が,下にスーッと移動。 その後,ちょっと停止。
  2. 次に,View1とView2が,いっぺんに右にスーッと移動。 その後,ちょっと停止。
  3. 次に,View1とView2が,同時にスーッと上に移動しながら,フェードアウト。 しばらくそのまま。
  4. 全てのアニメーションが終わったら,自動的に何らかの処理(ここでは画面遷移)を実行する。

このフローを,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」を提供する。

アニメーションを逐次実行するためのランナークラス

このライブラリを使えば,利用側のコードでは,スレッドの並列性を全く意識しなくてすむ所がポイント。

続きを読む