Android:ImageViewをカスタマイズする【前編 ピンチイン・ピンチアウト】

この記事は2015年11月24日に書かれたものです。内容が古い可能性がありますのでご注意ください。


Androidには残念なことにピンチイン・ピンチアウトによる縮小・拡大にデフォルトで対応したViewがありません。

そこでAndroidアプリ内で表示している画像を、ピンチインにより縮小・ピンチアウトによる拡大できるように、
また加えてドラッグによる画像のスクロール移動できるようにカスタマイズしたImageViewをのせたいと思います。

記事は二回にわけ前編の本記事ではピンチイン・ピンチアウトに対応させたいと思います。

ポイントは以下の三点です。

  • 画面のモーションイベントをScaleGestureDetectorオブジェクトに渡す
  • ScaleGestureDetector.OnScaleGestureListenerインターフェースを実装しジェスチャーの処理を決める
  • ImageView#getImageMatrix()で取得できるMatrixオブジェクトを操作し、再描画する

CustomImageView

package jp.techpjin.samplecustomimageview;

import android.content.Context;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

public class CustomImageView extends ImageView {
    private Matrix matrix = new Matrix();
    private ScaleGestureDetector scaleGestureDetector;
    private final float SCALE_MAX = 3.0f;
    private final float SCALE_MIN = 0.5f;
    private final float PINCH_SENSITIVITY = 5.0f;

    public CustomImageView(Context context) {
        super(context);
        init(context);
    }

    public CustomImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public CustomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        setImageResource(R.drawable.tech_pjin_icon);
        setScaleType(ScaleType.MATRIX);
        scaleGestureDetector = new ScaleGestureDetector(context, simpleOnScaleGestureListener);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        setImageMatrix(matrix);
        scaleGestureDetector.onTouchEvent(event);
        return scaleGestureDetector.onTouchEvent(event);
    }

    private ScaleGestureDetector.SimpleOnScaleGestureListener simpleOnScaleGestureListener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
        float focusX;
        float focusY;

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            float scaleFactor = 1.0f;
            float previousScale = getMatrixValue(Matrix.MSCALE_Y);

            if (detector.getScaleFactor() >= 1.0f) {
                scaleFactor = 1 + (detector.getScaleFactor() - 1) / (previousScale * PINCH_SENSITIVITY);
            } else {
                scaleFactor = 1 - (1 - detector.getScaleFactor()) / (previousScale * PINCH_SENSITIVITY);
            }

            float scale = scaleFactor * previousScale;

            if (scale < SCALE_MIN) {
                return false;
            }

            if (scale > SCALE_MAX) {
                return false;
            }

            matrix.postScale(scaleFactor, scaleFactor, focusX,focusY);

            invalidate();

            return super.onScale(detector);

        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            focusX = detector.getFocusX();
            focusY = detector.getFocusY();
            return super.onScaleBegin(detector);
        }

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

    };

    private float getMatrixValue(int index) {
        if (matrix == null) {
            matrix = getImageMatrix();
        }

        float[] values = new float[9];
        matrix.getValues(values);

        float value = values[index];
        return value;
    }

}

CustomImageViewのコンストラクタでScaleGestureDetectorのインスタンスを取得しておきます。
ScaleGestureDetectorのコンストラクタの第二引数にはScaleGestureDetector.OnScaleGestureListenerインターフェースのインスタンスを与えます。

onTouchEvent

@Override
public boolean onTouchEvent(MotionEvent event) {
    setImageMatrix(matrix);
    scaleGestureDetector.onTouchEvent(event);
    return scaleGestureDetector.onTouchEvent(event);
}

onTouchEventが呼ばれた際にScaleGestureDetector#onTouchEventを呼ぶことで処理をScaleGestureDetectorオブジェクトに委譲しています。

Matrix

Matrixオブジェクトの値はMatrix#getValuesを呼ぶことでfloat配列に格納することができます。
配列の値はMatrixクラスの定数をインデックスに与えて取り出します。
主に使うのは次の四つです。

  • MSCALE_X
  • MSCALE_Y
  • MTRANS_X
  • MTRANS_Y

MSCALE_X,MSCALE_Yはそれぞれx方向y方向の画像の縮小・拡大率です。
本サンプルではxyの比率は維持するため実質的に両者はイコールです。
MTRANS_X,MTRANS_Yはそれぞれ画像の左上頂点(ImageViewの頂点ではない)の座標です。

ScaleGestureDetector.SimpleOnScaleGestureListener

ScaleGestureDetector.SimpleOnScaleGestureListenerインターフェースは以下の三つのメソッドを実装しなければなりません。

  • ジェスチャーの開始時(2本指でタッチしたとき)に呼ばれるonScaleBegin
  • ジェスチャー中に呼ばれるonScale
  • ジェスチャー終了時に呼ばれるonScaleEnd

onScaleBeginではタッチした2点の中心点をScaleGestureDetector#getFocusX,Yで取得し保持

onScaleではMatrix#postScaleを呼びMatrixオブジェクトを操作した後
View#invalidateを呼ぶことでImageViewの再描画を行っています。

*本サンプルではSCALE_MAXにより(元画像からの)拡大率の最大値を、SCALE_MINにより(元画像からの)縮小率の最小値を設定しています。
*ジェスチャーの距離変化比をそのまま用いると反応が良すぎるため、定数PINCH_SENSITIVITYで割ることで動きをゆるやかにしています。

CustomImageViewを作れたので後はlayoutファイルに書いてあげれば使えます。
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    >

    <jp.techpjin.samplecustomimageview.CustomImageView
        android:layout_height="match_parent"
        android:layout_width="match_parent"/>

</LinearLayout>

 

ScaleGestureDetectorを用いてピンチイン・ピンチアウトに対応したカスタムImageViewを作成しました。
次回はImageViewをドラッグによる移動に対応させたいと思います。

  • このエントリーをはてなブックマークに追加

PAGE TOP