【Android】GestuerDetectorとScrollerを組み合わせた例

投稿者: | 2012年7月25日

View上において、ジェスチャー操作を扱うためのクラスGestureDetectorと
スクロール処理を扱うためのクラスScrollerを組み合わせた使用例を示す。
アプリの概要は以下の通りである。

・画面をタッチして指を移動したとき、それに追従する図形を描画する。
・そこからさらに、はじくような操作をしたときに図形を慣性スクロールさせて描画する。

最終的なイメージは以下のようになる。

このサンプルのレイアウトxmlは以下のようになっている。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.scrollerlesson.FlingLessonView
        android:id="@+id/myView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />

</RelativeLayout>

このように、FlingLessonViewという独自のViewを画面全体に表示している。
それではこの独自のViewのソースを見てみよう。

FlingLessonView.java

package com.example.scrollerlesson;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Scroller;

public class FlingLessonView extends View {

	private static final int ANIMATION_INTERVAL = 10;
	private final Handler handler = new Handler();
	private Scroller scroller;
	private GestureDetector gestureDetector;
	
	private float currX;
	private float currY;
	
	/**
	 * @param context
	 */
	public FlingLessonView(Context context) {
		super(context);
		init(context);
	}
	
	/**
	 * @param context
	 * @param attrs
	 */
	public FlingLessonView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}
	
	/**
	 * ビューの初期化
	 * @param context
	 */
	private void init(Context context){
		
		setLongClickable(true);
		scroller = new Scroller(context);
		gestureDetector = new GestureDetector(context,new OnGestureListener() {
			
			@Override
			public boolean onSingleTapUp(MotionEvent e) {
				// TODO Auto-generated method stub
				return false;
			}
			
			@Override
			public void onShowPress(MotionEvent e) {
				// TODO Auto-generated method stub
			}
			
			@Override
			public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
					float distanceY) {

				if(!scroller.isFinished())
					scroller.abortAnimation();
				
				currX = e2.getX();
				currY = e2.getY();
				
				invalidate();
				
				return true;
			}
			
			@Override
			public void onLongPress(MotionEvent e) {
				// TODO Auto-generated method stub
			}
			
			@Override
			public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
					float velocityY) {
				
				scroller.fling(
					(int)e2.getX(),
					(int)e2.getY(),
					(int)velocityX,
					(int)velocityY,
					0, getWidth(), 0, getHeight());

				handler.post(new Runnable() {
					
					@Override
					public void run() {
						scroller.computeScrollOffset();
						currX = scroller.getCurrX();
						currY = scroller.getCurrY();
						
						invalidate();
						
						if(!scroller.isFinished()){
							handler.postDelayed(this, ANIMATION_INTERVAL);
						}
					}
				});
				
				return true;
			}
			
			@Override
			public boolean onDown(MotionEvent e) {
				// TODO Auto-generated method stub
				return false;
			}
		});
	}
	
	private final Paint paint = new Paint();
	{
		paint.setStyle(Style.FILL);
		paint.setColor(Color.GREEN);
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawColor(Color.BLACK);
		canvas.drawCircle(currX, currY, 60.f, paint);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return 
				gestureDetector.onTouchEvent(event) ||
				super.onTouchEvent(event);
	}
}

47行目からのinit()メソッドがこのViewの初期化処理となる。
まずはsetLongClickable()をtrueで呼んでいる。
これをしておかないとOnGestureListenerのonScroll()、onFling()が呼ばれないようである。

51行目でGestureDetectorのインスタンスを生成している。
そのコンストラクタ引数のひとつであるOnGestureListenerの宣言が少し長くなってしまったが、
今回の例ではonScroll()とonFling()の2つだけを使用している。

65行目のonScroll()メソッドは、View上でタッチし、そのまま指を移動したときに呼び出される。
現在の位置は2番目の引数のMotionEventから取得できるので、
Viewのインスタンス変数に保持しておき、invalidate()を呼び出してonDraw()のタイミングで
新しい座標で図形が描画されるようにしておく。

85行目のonFling()メソッドは、View上で指をはじくような動作をすると呼び出される。
ここでScrollerのfling()メソッドに渡ってきた引数をそのまま渡している。
fling()メソッドでは移動後の最小座標と最大座標を指定できるので、画面内で収まる範囲とした。
この後、定期的にScrollerのgetCurrX(),getCurrY()メソッドで得られる座標を監視すると
Scrollerが内部で計算した値を取得できる。
今回の例ではスクロールが起きている間は10msごとにその座標を監視し、画面を更新するようにしてみた。

129行目からのonDraw()メソッドでは、単に現時点での座標に青い円を描画しているだけである。

135行目のonTouch()メソッドではGestureDetectorにタッチイベントを委譲している。

このようにして、フリック操作と慣性スクロールを連動させることができた。
続きに、エミュレーターで録画した動画を掲載する。
※実機では、もう少しスムーズに描画される。

[slmplayer uri=”2012/07/20120724_device.wmv”]

【Android】GestuerDetectorとScrollerを組み合わせた例」への2件のフィードバック

  1. ピンバック: 【Android】ScaleGestureDetectorを使う | ザワプロ!

  2. 89行目と90行目 間違ってる。。。
    誤)
    (int)e2.getX(),
    (int)e2.getY(),

    正)
    currX,
    currY,

    でしょ。
    何か動きがおかしかったので、調べてしまった・・・・・・・・

    返信

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です