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