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をドラッグによる移動に対応させたいと思います。