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()でスクロール処理を繰り返し実行している
という内容です.