Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

SimpleOnScaleGestureListener と SimpleOnGestureListener

朝の散歩です。柴犬は何か見つけたようです。視線の先にあるものは?

カモの集団です。ひょこひょこと歩く姿がユーモラスで見てて飽きません。こういう時がこれからも持てますようにと願うばかりです。

概要

前回の件を機会に、ジェスチャ関係を調べてみました。

Android のHPの次のURLに説明があります。

ScaleGestureDetector.SimpleOnScaleGestureListener 関係

https://developer.android.com/reference/android/view/ScaleGestureDetector.SimpleOnScaleGestureListener

GestureDetector.SimpleOnGestureListener 関係

https://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener

表にまとめてみましたので記録します。

WEBのみでは断片的で覚えにくいので最初に購入した Kotlin の本です。

ScaleGestureDetector.
SimpleOnScaleGestureListener

3つのコールバック関数があり、引数はいづれも ScaleGestureDetector 型です。この型で各種の値はメソッドを使って取得します。

ScaleGestureDetector.SimpleOnScaleGestureListener
メソッド
内容
戻り値
onScale(ScaleGestureDetector detector)
進行中のジェスチャのスケーリングイベントに応答します。
boolean
onScaleBegin(ScaleGestureDetector detector)
スケーリングジェスチャの開始に応答します。
boolean
onScaleEnd(ScaleGestureDetector detector)
拡大縮小ジェスチャの終了に応答します。
void
 
計 3要素
 

ScaleGestureDetector

リスナーをトリガーするには、onTouchEvent(MotionEvent event) の実行は必ず必要となります。

ScaleGestureDetector
メソッド
内容
戻り値
getCurrentSpan()
焦点を通って進行中のジェスチャーを形成する各ポインタ間の平均距離を返します。
float
getCurrentSpanX()
フォーカルポイントを通って進行中のジェスチャーを形成する各ポインタ間の平均X距離を返します。
float
getCurrentSpanY()
フォーカスポイントを通って進行中のジェスチャーを形成する各ポインタ間の平均Y距離を返します。
float
getEventTime()
処理中の現在のイベントのイベント時刻を返します。
long
getFocusX()
現在のジェスチャーのフォーカスポイントのX座標を取得します。
float
getFocusY()
現在のジェスチャーのフォーカスポイントのY座標を取得します。
float
getPreviousSpan()
進行中のジェスチャーを形成する各ポインタ間のフォーカスポイントを通る前回の平均距離を返します。
float
getPreviousSpanX()
焦点を通って進行中のジェスチャーを形成する各ポインタ間の前回の平均X距離を返します。
float
getPreviousSpanY()
焦点を通って進行中のジェスチャーを形成する各ポインタ間の前回の平均Y距離を返します。
float
getScaleFactor()
前のスケールイベントから現在のイベントへのスケーリングファクターを返します。
float
getTimeDelta()
前回受付けたスケーリング・イベントと現在のスケーリング・イベントの時間差をミリ秒単位で返します。
long
isInProgress()
スケールジェスチャが進行中の場合trueを返します。
boolean
isQuickScaleEnabled()
クイックスケールジェスチャ(ダブルタップの後にスワイプするジェスチャ)でスケーリングを実行するかどうかを返します。
boolean
isStylusScaleEnabled()
ユーザーがスタイラスを使用してボタンを押すスタイラス スケール ジェスチャでスケーリングを実行するかどうかを返します。
boolean
onTouchEvent(MotionEvent event)
MotionEventsを受け取り適切な時、OnScaleGestureListenerにイベントをディスパッチします。
boolean
setQuickScaleEnabled(boolean scales)
ユーザがダブルタップに続いてスワイプを実行した時、関連付けられたOnScaleGestureListenerがonScaleコールバックを受け取るべきかどうかを設定します。
void
setStylusScaleEnabled(boolean scales)
ユーザーがスタイラスを使用してボタンを押した時、関連するOnScaleGestureListenerがonScaleコールバックを受け取るべきかどうかを設定します。
void
 
計 17要素
 

GestureDetector.
SimpleOnGestureListener

GestureDetector.SimpleOnGestureListener
メソッド
内容
戻り値
onContextClick(MotionEvent e)
コンテキストクリック発生時に通知されます。
boolean
onDoubleTap(MotionEvent e)
ダブルタップが発生すると通知されます。
boolean
onDoubleTapEvent(MotionEvent e)
ダブルタップジェスチャ内のイベント(ダウン、移動、アップイベントを含む)が発生したときに通知されます。
boolean
onDown(MotionEvent e)
タップのトリガーとなった Down MotionEventが発生したときに通知されます。
boolean
onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
最初の Down MotionEventと一致する Up MotionEventで Fling イベントが発生すると通知されます。
boolean
onLongPress(MotionEvent e)
長押しが発生したときに、最初の Down MotionEventで通知されます。
void
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
スクロールが発生したときに、最初の Down MotionEventと現在の Move MotionEventで通知されます。
boolean
onShowPress(MotionEvent e)
ユーザーが onDown MotionEventを実行して Move または Up をまだ実行していない状態。
void
onSingleTapConfirmed(MotionEvent e)
シングルタップが発生した時に通知されます。
boolean
onSingleTapUp(MotionEvent e)
Up MotionEventをトリガーにタップが発生すると通知されます。
boolean
 
計 10要素
 

GestureDetector

リスナーをトリガーするには、onTouchEvent(MotionEvent ev) の実行は必ず必要となります。

GestureDetector
メソッド
内容
戻り値
isLongpressEnabled()
 
boolean
onGenericMotionEvent(MotionEvent ev)
与えられた一般的なモーションイベントを分析し、該当する場合は、供給されたOnGestureListener上の適切なコールバックをトリガします。
boolean
onTouchEvent(MotionEvent ev)
与えられたモーションイベントを分析し、該当する場合、提供されたOnGestureListenerの適切なコールバックをトリガーします。
boolean
setContextClickListener(GestureDetector.OnContextClickListener onContextClickListener)
コンテキストクリックのために呼び出されるリスナーを設定します。
void
setIsLongpressEnabled(boolean isLongpressEnabled)
長押しが有効かどうかを設定します。長押しが有効な場合、長押しイベントが発生します。
void
setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener)
ダブルタップと関連するジェスチャーに対して呼び出されるリスナーを設定します。
void
 
計 6要素
 

リスナーをクラスで

リスナー SimpleOnScaleGestureListener をクラスにしてみました。

    private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            Log.d("scale","onscalebegin");
            //ピンチ操作開始
            //開始座標を取得
            _focusx = detector.getFocusX();
            _focusy = detector.getFocusY();
            return super.onScaleBegin(detector);
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            //ピンチ操作終了
            super.onScaleEnd(detector);
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            //ピンチ操作中
            //縮尺率を取得
            _lastscalefactor = detector.getScaleFactor();
            //縦横の拡大縮小を設定するマトリックスの作成
            drawmatrix.postScale(_lastscalefactor,
                                 _lastscalefactor,
                                 _focusx,
                                 _focusy);
            //これを実行することにより onDraw が発火
            invalidate();
            super.onScale(detector);
            return true;
        }
    }

同様に、リスナー SimpleOnGestureListener をクラスにしてみました。

    private class GestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll (MotionEvent e1,
                                 MotionEvent e2,
                                 float distanceX,
                                 float distanceY) {
            Log.d("scroll","onscroll");
            // スクロールしたとき
            if (e1.getPointerId(0) ==
                e2.getPointerId(0)) {
                // 画像を移動
                drawmatrix.postTranslate(-distanceX, -distanceY);
                //これを実行することにより onDraw が発火
                invalidate();
            }
            return super.onScroll(e1, e2, distanceX, distanceY);
        }

        @Override
        public boolean onDoubleTap (MotionEvent e) {
            Log.d("doubletap","onDoubleTap");
            drawmatrix.reset();
            //drawmatrix.postScale(firstrate, firstrate, firstx, firsty);
            drawmatrix.postScale(firstrate, firstrate);
            drawmatrix.postTranslate(firstx, firsty);
            return super.onDoubleTap(e);
        }
    }

この2つクラスをコードの中に入れ、init() 周りも見直してみました。見通しが良くなった感じがすると自画自賛しています。

copy

package org.sibainu.relax.room.scalegesturedetector;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;

import androidx.annotation.Nullable;

public class GestureView extends View {

    private Context context;
    //表示するビットマップ画像
    private Bitmap _bm;
    //ピンチ操作開始のX座標
    private float _focusx;
    //ピンチ操作開始のY座標
    private float _focusy;
    //ピンチ操作時の画像の縮尺
    private float _lastscalefactor = 1.0f;
    //デフォルトの画像の縮尺
    private float firstrate = 1.0f;
    //描画の左上のx座標
    private float firstx =0.0f;
    //描画の左上のy座標
    private float firsty =0.0f;
    //View自身の横幅
    private int viewwidth;
    //View自身の高さ
    private int viewheight;
    //画像の描画を設定するオブジェクト
    private Matrix drawmatrix = new Matrix();
    //Paintオブジェクト
    private Paint paint = new Paint();
    //リスナー関係のオブジェクト
    private ScaleGestureDetector scalegesturedetector;
    private ScaleGestureListener scalegesturelistener;
    private GestureDetector gesturedetector;
    private GestureListener gesturelistener;

    // コンストラクタ
    public GestureView (Context context) {
        super(context);
        this.context = context;
        init();
    }

    public GestureView (Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
        init();
    }

    public GestureView (Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        init();
    }

    private void init () {
        //ピンチ操作時の動き感知するリスナーの作成
        scalegesturelistener = new ScaleGestureListener();
        //スクロールの動き感知するリスナーの作成
        gesturelistener = new GestureListener();
        //リスナーを組み込む
        scalegesturedetector = new ScaleGestureDetector(context, scalegesturelistener);
        gesturedetector = new GestureDetector(context, gesturelistener);
    }

    @Override
    public boolean onTouchEvent (MotionEvent event) {
        super.onTouchEvent(event);
        // すべてのGestureはここを通る
        Log.d("touchevent",String.valueOf(gesturedetector.onTouchEvent(event)));
        return scalegesturedetector.onTouchEvent(event) || 
               gesturedetector.onTouchEvent(event) ||
               super.onTouchEvent(event);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //再描画時の累積した Matrix drawmatrix を適応
        canvas.save();
        canvas.drawBitmap(_bm, drawmatrix, paint);
        canvas.restore();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        // BitMap の全体表示をデフォルトとする drawmatirix を作成します
        viewwidth = getWidth();   // Viewの横幅を取得
        viewheight = getHeight(); // Viewの高さを取得

        int bmwidth = _bm.getWidth();
        float widthrate = viewwidth * 1.0f / bmwidth;
        if (firstrate > widthrate) {
            firstrate = widthrate;
        }

        int bmheight = _bm.getHeight();
        float heightrate = viewheight * 1.0f / bmheight;
        if (firstrate > heightrate) {
            firstrate = heightrate;
        }
 
        if (firstrate != 1.0f) {
            //キャンバスの中心に描画します
            firstx = (viewwidth - bmwidth * firstrate) / 2;
            firsty = (viewheight - bmheight * firstrate) / 2;
            drawmatrix.reset();
            //相似の拡大縮小 sx:firstrate  sy:firstrate  px:firstx  py:firsty
            //drawmatrix.postScale(firstrate, firstrate, firstx, firsty);
            drawmatrix.postScale(firstrate, firstrate);
            drawmatrix.postTranslate(firstx, firsty);
        }
    }

    public void setImageBitmap(Bitmap bm) {
        _bm = bm;
        Log.d("setImageBitmap","setImageBitmap");
        invalidate();
    }

    private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            Log.d("scale","onscalebegin");
            //ピンチ操作開始
            //開始座標を取得
            _focusx = detector.getFocusX();
            _focusy = detector.getFocusY();
            return super.onScaleBegin(detector);
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            //ピンチ操作終了
            super.onScaleEnd(detector);
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            //ピンチ操作中
            //縮尺率を取得
            _lastscalefactor = detector.getScaleFactor();
            //縦横の拡大縮小を設定するマトリックスの作成
            drawmatrix.postScale(_lastscalefactor,
                                 _lastscalefactor,
                                 _focusx,
                                 _focusy);
            //これを実行することにより onDraw が発火
            invalidate();
            super.onScale(detector);
            return true;
        }
    }

    private class GestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll (MotionEvent e1,
                                 MotionEvent e2,
                                 float distanceX,
                                 float distanceY) {
            Log.d("scroll","onscroll");
            // スクロールしたとき
            if (e1.getPointerId(0) ==
                e2.getPointerId(0)) {
                // 画像を移動
                drawmatrix.postTranslate(-distanceX, -distanceY);
                //これを実行することにより onDraw が発火
                invalidate();
            }
            return super.onScroll(e1, e2, distanceX, distanceY);
        }

        @Override
        public boolean onDoubleTap (MotionEvent e) {
            Log.d("doubletap","onDoubleTap");
            drawmatrix.reset();
            //drawmatrix.postScale(firstrate, firstrate, firstx, firsty);
            drawmatrix.postScale(firstrate, firstrate);
            drawmatrix.postTranslate(firstx, firsty);
            invalidate();
            return super.onDoubleTap(e);
        }
    }

この件はここまでとします。