AndroidでGoogle Maps APIを使った地図を慣性スクロールさせる方法 (android sdk 1.5)

慣性スクロールというのは,指で画面をスクロールしたときに,勢いを付けておくと指を離してもしばらくビューンとスクロールし続けるという動作,という意味で使っています.少なくともこの記事では.

MainAct.java

import com.google.android.maps.MapActivity;
import android.os.Bundle;

public class MainAct extends MapActivity {
	private GoogleMapView gmapView;
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		gmapView = new GoogleMapView(this, null);
		setContentView(gmapView);
	}

	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}
}

GoogleMapView.java

import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;

public class GoogleMapView extends MapView {
	private static final String API_KEY = "[自分のAPIキー]";

	private static final boolean USE_KANSEI_SCROLL = true;
	private static final int KANSEI_MIN_DIFF = 3;
	private static final int KANSEI_STEP = 2;
	private static final int KANSEI_LOOP_INTERVAL = 20;

	private Handler handle;
	private boolean curDown;
	private boolean doingKanseiScroll;

	private int lastdownX;
	private int lastdownY;
	private int[] latestTouchedDx = new int[]{0,0};
	private int[] latestTouchedDy = new int[]{0,0};

	public GoogleMapView(Context context, AttributeSet attrs) {
		super(context, API_KEY);
		handle = new Handler();
	}

	@Override
	protected void onAttachedToWindow() {
		setBuiltInZoomControls(true);
		setSatellite(false);

		MapController mc = getController();
		mc.setZoom(17);
		mc.setCenter(new GeoPoint((int)(35.6655 * 1E6), (int)(139.7596 * 1E6)));

		setClickable(true);
		setFocusable(true);
		setFocusableInTouchMode(true);
		requestFocus();
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		int action = event.getAction(); 	 
		int curX = (int) event.getX();
		int curY = (int) event.getY();

		if (action == MotionEvent.ACTION_DOWN) {
			lastdownX = curX;
			lastdownY = curY;
			curDown = true;
			doingKanseiScroll = false;
			
		} else if (action == MotionEvent.ACTION_UP) {
			curDown = false;

			if (USE_KANSEI_SCROLL) {
				int biggerDx = (latestTouchedDx[0] > 0)?
					Math.max(latestTouchedDx[0], latestTouchedDx[1]):
					Math.min(latestTouchedDx[0], latestTouchedDx[1]);
				int biggerDy = (latestTouchedDy[0] > 0)?
					Math.max(latestTouchedDy[0], latestTouchedDy[1]):
					Math.min(latestTouchedDy[0], latestTouchedDy[1]);

				if (biggerDx < -KANSEI_MIN_DIFF || biggerDx > KANSEI_MIN_DIFF
						 || biggerDy < -KANSEI_MIN_DIFF || biggerDy > KANSEI_MIN_DIFF) {
	
					doingKanseiScroll = true;

					redrawForKanseiScroll(biggerDx, biggerDy);
				}
			}

		} else if (curDown && action == MotionEvent.ACTION_MOVE) {
			int dx = lastdownX - curX;
			int dy = lastdownY - curY;
			lastdownX = curX;
			lastdownY = curY;
			
			scroll(dx, dy);

			if (USE_KANSEI_SCROLL) {
				latestTouchedDx[1] = latestTouchedDx[0];
				latestTouchedDx[0] = dx;
				latestTouchedDy[1] = latestTouchedDy[0];
				latestTouchedDy[0] = dy;
			}
		}
		return true;
	}

	private void redrawForKanseiScroll(int currentKanseiDx, int currentKanseiDy) {
		if (doingKanseiScroll) {
			if (currentKanseiDx > KANSEI_STEP) {
				currentKanseiDx -= KANSEI_STEP;
			} else if (currentKanseiDx < -KANSEI_STEP) {
				currentKanseiDx += KANSEI_STEP;
			} else {
				currentKanseiDx = 0;
			}
			if (currentKanseiDy > KANSEI_STEP) {
				currentKanseiDy -= KANSEI_STEP;
			} else if (currentKanseiDy < -KANSEI_STEP) {
				currentKanseiDy += KANSEI_STEP;
			} else {
				currentKanseiDy = 0;
			}
			scroll(currentKanseiDx, currentKanseiDy);

			if (currentKanseiDx == 0 && currentKanseiDy == 0) {
				doingKanseiScroll = false;
			} else {
				final int dx = currentKanseiDx;
				final int dy = currentKanseiDy;
				handle.postDelayed(new Runnable() {
					public void run() {
						redrawForKanseiScroll(dx, dy);
					}
				}, KANSEI_LOOP_INTERVAL);
			}
		}
	}

	private void scroll(int dx, int dy) {
		getController().scrollBy(dx, dy);
	}
}
ゼロから試行錯誤で編み出した実装なので,はっきり言ってものすごく泥臭いのですが,動作は快適です.地図ロイドではこの方式を使っています.

やっていることは,指を離した(ACTION_UP)ら慣性スクロールを開始しているというだけなんですが,

  • スクロール速度は,過去2回のACTION_MOVEイベントによる移動量のうち大きい方を採用している
  • その初速を使って,徐々に減衰させながらHandler#postDelayed()でスクロール処理を繰り返し実行している

という内容です.


© 2024 KMIソフトウェア