タッチイベントの実装方法・応用編|Android開発

      2018/10/26

前記事」では基本的なタッチイベントの実装方法について記述しました。
本記事では、応用編として実際にタッチイベントを処理するサンプルを記述します。

サンプル

以下のサンプルはImageViewに表示した画像を実際の2倍(MAX_SCALEで指定)まで拡大表示させるプログラムです。

準備

public class ImageActivity extends Activity {
    private ImageView mImageView;

    private GestureDetector mGestureDetector;
    private ScaleGestureDetector mScaleGestureDetector;
    private Matrix mMatrix;
    private float mFitScale = 1.f;
    private float mCurrentScale = 1.f;
    private static final float MAX_SCALE = 2.f;
    private Point mDisplaySize;
    private Point mImageSize;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_xxxx);

        mDisplaySize = new Point();
        getWindowManager().getDefaultDisplay().getRealSize(mDisplaySize);

        mImageView = (ImageView) findViewById(R.id.xxxx_img);

        /* Touch event */
        mMatrix = new Matrix();
        mImageView.setOnTouchListener(mTouchEventListener);
        mGestureDetector = new GestureDetector(this, mGestureListener);
        mScaleGestureDetector = new ScaleGestureDetector(this, mScaleGestureListener);
        mImageView.setScaleType(ImageView.ScaleType.MATRIX);
    }

    public void SetBitmap(Bitmap bmp) {
        mImageView.setImageBitmap(bmp);
        mImageSize = new Point(bmp.getWidth(), bmp.getHeight());
        float fitScaleH = mDisplaySize.x / (float)mImageSize.x;
        float fitScaleV = mDisplaySize.y / (float)mImageSize.y;
        mFitScale = Math.min(fitScaleH, fitScaleV);

        if (mFitScale < 1.f) {
            mMatrix.setScale(mFitScale, mFitScale);
            mImageView.setImageMatrix(mMatrix);
            // correct
        }
    }

    // これらの詳細は以降の項目に記載します.
    private GestureDetector.SimpleOnGestureListener mGestureListener;
    private View.OnTouchListener mTouchEventListener;
    private View.OnTouchListener mTouchEventListener;
}

xxxx_img(ImageView)はレイアウトでサイズにwrap_contentを指定します。

setScaleType(ImageView.ScaleType.MATRIX)をコールしないと、Matrixによる変形が効きません。

ダブルタップリスナ

private GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        mMatrix.reset();
        if (mCurrentScale <= mFitScale) {
            mCurrentScale = MAX_SCALE;
            mMatrix.postScale(mCurrentScale, mCurrentScale, e.getX(), e.getY());
        } else {
            mCurrentScale = mFitScale;
            if (mFitScale < 1.f) {
                mMatrix.setScale(mFitScale, mFitScale);
                // correct
            }
        }

        mImageView.setImageMatrix(mMatrix);

        return true;
    }
};

タッチイベントリスナ

private View.OnTouchListener mTouchEventListener = new View.OnTouchListener() {
    private PointF mLastPoint = new PointF();
    private int mLastPointerCount;

    @Override
    public boolean onTouch(View v, MotionEvent e) {
        mGestureDetector.onTouchEvent(e);
        mScaleGestureDetector.onTouchEvent(e);

        boolean updateImage = false;
        float x;
        float y;

        if (e.getPointerCount() == 1) {
            if (mCurrentScale <= mFitScale) {
                mLastPointerCount = 1;
                return true;
            }

            if (mLastPointerCount == 1) {
                if (e.getAction() == MotionEvent.ACTION_MOVE) {
                    x = e.getX();
                    y = e.getY();

                    mMatrix.postTranslate(x - mLastPoint.x, y - mLastPoint.y);
                    // correct

                    mLastPoint.set(x, y);
                    updateImage = true;
                } else {
                    mLastPoint.set(e.getX(), e.getY());
                }
            } else {
                mLastPoint.set(e.getX(), e.getY());
            }
        }

        mLastPointerCount = e.getPointerCount();

        if (updateImage) {
            mImageView.setImageMatrix(mMatrix);
        }

        return true;
    }
};

ピンチイベントリスナ

private ScaleGestureDetector.SimpleOnScaleGestureListener mScaleGestureListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {

    private PointF mCenterPosition = new PointF();

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        mCenterPosition.set(detector.getFocusX(), detector.getFocusY());
        return true;
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float factor = detector.getScaleFactor();
        boolean useFitScale = false;

        if (mCurrentScale * factor >= MAX_SCALE) {
            factor = MAX_SCALE / mCurrentScale;
        }
        if (mCurrentScale * factor <= mFitScale) {
            factor = mFitScale / mCurrentScale;
            useFitScale = mFitScale < 1.f;
        }

        if (useFitScale) {
            mMatrix.setScale(mFitScale, mFitScale);
        } else {
            mMatrix.postScale(factor, factor, mCenterPosition.x, mCenterPosition.y);
        }
        // correct

        mCurrentScale = mCurrentScale * factor;

        mImageView.setImageMatrix(mMatrix);

        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
    }
};

移動先の補正

上記サンプルはタッチ処理のみに焦点を当てています。実際は画像の位置などを調整する必要があると思います。
その場合は、上記サンプルコードの// correct付近でMatrixの補正を行います。

補正処理のサンプル

適用後のMatrixの値を見て、補正が必要な場合は補正後の値を直接セットしています。

void correctMatrix(Matrix matrix, Point imageSize, Point displaySize) {
    float values[] = new float[9];
    matrix.getValues(values);

    float x = values[Matrix.MTRANS_X];
    float y = values[Matrix.MTRANS_Y];
    if (0.f < values[Matrix.MTRANS_X]) {
        x = 0.f;
    }
    if (0.f < values[Matrix.MTRANS_Y]) {
        y = 0.f;
    }
    int imgWidth = Math.round(imageSize.x * values[Matrix.MSCALE_X]);
    int imgHeight = Math.round(imageSize.y * values[Matrix.MSCALE_Y]);
    if (imgWidth < displaySize.x) {
        values[Matrix.MTRANS_X] = (displaySize.x - imgWidth) >> 1;
    } else {
        values[Matrix.MTRANS_X] = Math.max(x, displaySize.x - imgWidth);
    }
    if (imgHeight < displaySize.y) {
        values[Matrix.MTRANS_Y] = (displaySize.y - imgHeight) >> 1;
    } else {
        values[Matrix.MTRANS_Y] = Math.max(y, displaySize.y - imgHeight);
    }

    matrix.setValues(values);
}

 - Android