2010年11月1日月曜日

MapView でアニメーション (Overlay編)

MapView を使って、Google マップを表示させている上に、
画像をアニメーションさせるサンプル・プログラムの2つ目です。

2つ目は、日本Androidの会のメーリング・リストでやりとりされていた
以下のスレッドを参考にさせて頂きました。
参考サイト: OverlayにImageViewを表示する方法について

参考サイトでは、OverlayItem を使用しているのですが、
表示させたい画像(アニメーション)は1つしかないので、Overlay を使用するようにしました。


<設計方針>

Overlay を使います。

Overlay の draw() で画像を表示させるわけですが、画像を60枚用意しておき、
draw() が実行される都度、画像を切り替えるようにしておきます。
(最後の画像になったら、最初の画像に戻ります)

MapView に対して定期的に invalidate() を実行することで、Overlay の draw() が定期的に
実行されるようになり、パラパラ漫画(アニメーション)の完成です。

<問題点>

問題点でないですが、MapView に対して invalidate() を実行しているため、
複数の画像(静止画)があった場合、それらも再描画対象となるかなぁというのが気にかかります。


以下にポイントとなるソースを貼り付けます。

まずはレイアウトです。
FrameLayout に対し、MapView のみを表示させています。
(子要素が MapView しかないので、FrameLayout である必要はないんですけど。)

main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<com.google.android.maps.MapView
 android:id="@+id/mapview"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:enabled="true"
 android:clickable="true"
 android:apiKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
 />
</FrameLayout>


次は Activity (MapActivity継承) クラスです。

onCreate() では、person_01.png ~ person_60.png の 60 枚の画像を読み込んでいます。
その後、アニメーションを行う Overlay を MapView に紐付けています。

MapTestActivity.java
package jp.kochi.test;

import java.util.ArrayList;
import java.util.List;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;

public class MapTestActivity extends MapActivity {
 ImageView mImageView;
 AnimationOverlay mAnimOverlay;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  Log.v("MapTest", "onCreate()");
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  MapView mapView = (MapView) findViewById(R.id.mapview);

  mapView.setBuiltInZoomControls(true);

  ArrayList<Bitmap> list = new ArrayList<Bitmap>();
  Resources r = getResources();

  int id;
  Bitmap bmp;
  String str;
  for (int i = 1; i <= 60; i++) {
   str = "person_" + String.format("%1$02d", i);
   id = r.getIdentifier(str, "drawable", getPackageName());
   Log.v("MapTest", "str = " + str + ", id = " + id);
   bmp = BitmapFactory.decodeResource(r, id);
   list.add(bmp);
  }

  Log.v("MapTest", "list.size() = " + list.size());

  // アニメーション表示
  // お遍路さん2番札所
  double latitude  = 34.156028;
  double longitude = 134.49025;
  GeoPoint gpoint = new GeoPoint((int)(latitude * 1E6), (int)(longitude * 1E6));
  List listOverlays = mapView.getOverlays();
  mAnimOverlay = new AnimationOverlay(gpoint, list, mapView);
  listOverlays.add(mAnimOverlay);

 }
 
 @Override
 protected void onResume() {
  Log.v("MapTest", "onResume()");
  super.onResume();
  mAnimOverlay.resume();
 }
 
 @Override
 protected void onPause() {
  Log.v("MapTest", "onPause()");
  super.onPause();
  mAnimOverlay.pause();
 }

 @Override
 protected boolean isRouteDisplayed() {
  // TODO Auto-generated method stub
  return false;
 }
}

onResume()、onPause() では、Overlay のアニメーション (定期的に invalidate() 実行) を
再開/停止させる処理を追加しています。


最後は AnimationOverlay (Overlay継承) クラスです。

AnimationOverlay.java
package jp.kochi.test;

import java.util.ArrayList;
import java.util.List;
import android.R.bool;
import android.R.drawable;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.Log;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;

public class AnimationOverlay extends Overlay implements Runnable {
 private Handler mHandler;
 private ArrayList<Bitmap> mPngList;
 private int current_frame;
 private MapView mMapView;
 private GeoPoint mGeoPoint;
 final private int sInterval = 100;
 private boolean mStop = false;

 public AnimationOverlay(GeoPoint point, ArrayList<Bitmap> list,
   MapView mview) {
  Log.v("MapTest", "AnimationOverlay()");

  mPngList = list;
  mMapView = mview;
  mGeoPoint = point;

  current_frame = 0;

  mHandler = new Handler();
  mHandler.postDelayed(this, sInterval);
 }

 @Override
 public void draw(Canvas canvas, MapView mapView, boolean shadow) {
  super.draw(canvas, mapView, shadow);

  if (shadow == false) {
   Log.v("MapTest", "draw() : shadow = false");
   Point pxPoint = new Point();

   Projection projection = mMapView.getProjection();
   projection.toPixels(mGeoPoint, pxPoint);

   canvas.drawBitmap(mPngList.get(current_frame), pxPoint.x,
     pxPoint.y, null);
  } else {
   Log.v("MapTest", "draw() : shadow = true");
  }
 }

 @Override
 public void run() {
  Log.v("MapTest", "run()");
  if (current_frame == mPngList.size() - 1)
   current_frame = 0;
  else
   current_frame++;

  mMapView.invalidate();

  if (mStop == false) mHandler.postDelayed(this, sInterval);
  
 }
 
 public void resume() {
  mHandler.postDelayed(this, sInterval);
  mStop = false;
 }
 
 public void pause() {
  mStop = true;
 }
 
}


以下は実行画面です。
これもまた、分かりにくいですが、画面中央付近の画像が変化(アニメーション)しています。