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