MatrixのpostScaleで画像を拡大縮小させる

ImageViewなり、自分で作ったCustom Viewなりで表示させる画像を、動かしたり拡大縮小させたりするのに使えるMatrixをいじって学んだことのメモです。

特にpostScaleを使った拡大縮小がイメージ通りに動かなくてハマってしまいました。

ちなみにMatrixクラスを使ってBitmapを加工する – Techoboosterを参考に始めました。

使い方

ImageViewに設定するには、setImageMatrixメソッドでMatrixを渡してやるといいです。

Matrix matrix = new Matrix();
ImageView.setImageMatrix(Matrix);

CustomViewで使う場合は、オーバーライドしたonDraw()で描画するときにMatrixを渡せばいいです。

   @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(mImage, mMatrix, mPaint);
    }

こんな感じ。mImageはBitmapオブジェクトで、mMatrixとmPaintはそれぞれnew Matrix(),new Paint()したものを渡してます。

今回は下のCustom ViewでMatrixを操作していて分かったことを書きます。

タッチ操作で動かす

画面上を指でなぞると、その動きに応じて画像も移動するようにする場合はこうすればOK。

mMatrix.postTranslate(float X移動量,float Y移動量);

移動量を取得するにはTouchEventを自分で判定するなり、GestureDetectorを使うなりして取得します。

GestureDetectorの使い方はDetecting Common Gestures – Android Developers参照。また別途記事書こうと思います。

移動に関しては特に難しくはありませんでした。ただし、移動制限を設けようとするとこれはこれでまた大変そうです。

こちらの記事が移動制限を実装するのに非常に役立ちそうな予感です。実装できたらまた記事書きたいと思います(そればっか)。

拡大縮小

ピンチイン・アウトで画像を拡大縮小させる場合がクセモノでした。

mMatrix.postScale(float X拡大率, float Y拡大率, float 拡大の起点X, float 拡大の起点Y);

ハマったポイントはここで渡す拡大率の扱いです。

postScaleに渡す拡大率は、Matrixを指定した拡大率に変形させるのではありません。現在のMatrixを渡した拡大率で拡大縮小させます。Matrixの拡大率が0.1のときにpostScale(0.1f,0.1f)するとMatrixの拡大率は0.01になります。

画像が過剰に縮小・拡大されないように渡す拡大率の値を制限したとしても、制限した値をそのまま渡してしまったら制限が効きません。

指定した拡大率に画像を変形させたい場合は変化量を計算して渡すようにします。

ScaleGestureDetectorを使って拡大縮小させていて、頭の中がこんがらがっていました(現在進行形ですけど)。ScaleGestureDetectorを使うと、onScaleメソッド内でdetector.getScaleFactor()を使うことでピンチイン・アウトによる拡大率を取得することができます。

この拡大率は、ピンチ操作が始まった段階では1.0から始まります。そのためこの値をそのままMatrixのpostScaleに渡すと、拡大縮小の開始時に一旦元の縮尺に戻ってしまいます。そのこととごっちゃになっていて間違ったこと書いてました。

float deltaScale = targetScale / nowScale;
mMatrix.postScale(deltaScale, deltaScale);

postScaleではなくsetScaleを使う方法もあるのかもしれませんが、動き始めに画像が元のサイズに戻ってしまうため、この方法がスマートな気がします。

ちなみにピンチイン・アウトを検出するにはScaleGestureDetectorが使えます。Dragging and Scaling – Android Developers

拡大縮小の起点を指定する場合、detector.getFocusX()を使うとイメージに近い動きになりました。ただし画像の範囲外でやると当然ながら動きがおかしくなるので、画像が画面の範囲外に移動できないように制限を実装しないとダメそうです。

現在の拡大率を取得するには?

Matrixに設定された現在の拡大率を取得するのも一工夫必要です。getScaleXというような、Marixの現在の拡大率を直接取得するメソッドはありません。

Matrixは9つの値を保持していますが、その値を参照するためにはfloatの配列を渡してコピーしてもらうしか方法がありません。

float[] values = new float[9];
mMatrix.getValues(values);
nowScale = values[Matrix.MSCALE_X];

ちなみにAPIリファレンスにある並びで並んでいるわけではないので、定数を使ってアクセスしましょう。

ちなみに0から順にMSCALE_X,MSKEW_X,MTRANS_X,MSCALE_Y,MSKEW_Y,MTRANS_Y,MPERSP_0,MPERSP_1,MPERSP_2の順に並んでます。

なんで変な並びになってるのかと思ったら、英語のWikipedia見たらその意味が分かるかもしれません。Transformation matrix – Wikipedia

Android特有の概念ではなく、Matrixを使った変形の概念があるんですね。数式だらけでサッパリ分かりませんが。

日本語で解説してあるサイトもありますが、いずれにしても奥が深そうで難解です・・・。

Amazonのほしいものリストを公開しています。仕事で欲しいもの、単なる趣味としてほしいもの、リフレッシュのために欲しいものなどを登録しています。 寄贈いただけると泣いて喜びます。大したお礼はできませんが、よりよい情報発信へのモチベーションに繋がりますので、ご検討いただければ幸いです。