2012年4月19日木曜日

AndroidのTranslateAnimationでリアルに(Property Animationのように)アニメーションするには?

昨日はAndroid上のTranslateAnimationのクセについて学び、無事ターゲットをリアルに(Property Animationのように)アニメーションしながら動かすことができた。

琴線探査: AndroidのTranslateAnimationのクセはひどいね

昨日のコードのmoveTarget()メソッドは相対座標系に対する移動だった。いわばmoveRelative()とでも呼ぶべきものだった。そこで今度は親コンテナの座標系に対する移動をやってみた。

「target」と表示されている赤い四角が、画面上でタップした場所に移動するというサンプル。





Eclipseプロジェクトファイル:TransformAnimationMoveTo.zip(192KB)

res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/parent"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/target"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="#660000"
        android:gravity="center"
        android:text="target"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>


TransformAnimationMoveToActivity.java
package jp.example;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;


public class TransformAnimationMoveToActivity extends Activity {


  // 移動アニメーションさせるターゲット
  protected View target;

  // ターゲットの親コンテナ
  protected View parent;


  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    parent = findViewById(R.id.parent); // 親コンテナ取得
    target = findViewById(R.id.target); // ターゲット取得
  } // END onCreate()


  @Override
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_UP:

        // 親コンテナの画面に対する描画位置オフセットを計算
        Display disp = getWindowManager().getDefaultDisplay();
        int offsetX = disp.getWidth() - parent.getWidth();
        int offsetY = disp.getHeight() - parent.getHeight();

        // 移動先を計算(オフセットとターゲットの中心点を考慮)
        int toX = (int) (event.getX() - offsetX - target.getWidth() / 2);
        int toY = (int) (event.getY() - offsetY - target.getHeight() / 2);

        // タッチアップでターゲット移動
        moveTo(toX, toY);

        Log.v("onTouchEvent", "ACTION_UP x=" + target.getLeft() + " y=" + target.getTop() + " offsetX=" + offsetX + " offsetY=" + offsetY + " toX=" + toX + " toY=" + toY);

        break;
    } // END switch
    return super.onTouchEvent(event);
  } // END onTouchEvent()


  /**
   * ターゲットを指定の場所に移動する
   * 
   * TranslateAnimationで移動アニメーションをすると、 見かけ上は移動したように見えるが実際には移動していないらしい。
   * そこで、アニメーション完了時にlayout()を使って物理的にも移動させる。
   * 
   * @param x
   *          X軸に対する移動先
   * @param y
   *          Y軸に対する移動先
   */
  protected void moveTo(int x, int y) {
    if (target.getAnimation() != null) {
      return; // アニメーション中なら何もしない
    }
    final int left = target.getLeft();
    final int top = target.getTop();
    final int toX = x; // AnimationListenerから参照できるように
    final int toY = y; // AnimationListenerから参照できるように
    TranslateAnimation anim = new TranslateAnimation(left, toX, top, toY);
    anim.setAnimationListener(new Animation.AnimationListener() {

      @Override
      public void onAnimationStart(Animation animation) {
      }

      @Override
      public void onAnimationRepeat(Animation animation) {
      }

      @Override
      public void onAnimationEnd(Animation animation) {
        target.layout(toX, toY, toX + target.getWidth(), toY + target.getHeight()); // 物理的にも移動
        target.setAnimation(null); // これをしないとアニメーション完了後にチラつく
        parent.invalidate(); // 後ろに残る残像?ゴミ?をクリアする(実行環境によるみたい)
      }

    });
    anim.setDuration(500); // セットしないとアニメーションしない
    target.layout(0, 0, target.getWidth(), target.getHeight()); // 初期位置に戻す。これをしないと2度目以降のアニメーションがおかしくなる(チラつく)
    target.startAnimation(anim);
  } // END moveTo()


} // END class TransformAnimationMoveToActivity

第1のポイントは、moveTo()で引数として取得したx、yをそれぞれfinal変数のtoX、toYに格納しているところ。こうしないとAnimationListenerから参照できない。

第2のポイントは、new TranslateAnimation(left, toX, top, toY)。相対座標系での移動の場合と違って特に何を計算するでもなく単にtoX、toYを与えている。

ポイントというほどのことではないが、onTouchEvent()でActivityに対するMotionEventを取得しているので、上の方のバーやらなんやらで実際の表示領域(ターゲットの親コンテナ)の座標系と合わないため、画面サイズと親コンテナサイズの差分を取ってオフセット計算した。