いや〜回転させただけで、移動範囲のチェックが一気に難易度アップでしたね。本当はまだ不十分な部分もあるんですが、そこは目を瞑ることにして(^_^;)。今回はフィードバック用にイメージの色を一定時間反転させる処理と、ImageView様に表示されているイメージを見たままの状態でビットマップとして取得する方法についてです。
まずはイメージの色の反転から。
ImageView様は、元となるイメージにジオメトリ(大きさ・位置・回転角度)を変換するMatrixと色を変換するColorFilterを適用してからイメージを表示してくださいます。前回まではジオメトリ変換のMatrixを扱ってきましたが、今回は色の反転ということでColorFilterを扱う必要があります。
ImageView様のColorFilter関連のメソッドは5つあります。
- ImageView#clearColorFilter
- ImageView#getColorFilter
- ImageView#setColorFilter(ColorFilter)
- ImageView#setColorFilter(int)
- ImageView#setColorFilter(int, PorterDuff.Mode)
今回は2つ目のImageView#getColorFilterと3つ目のImageView#setColorFilterを使います。
ただし、2つ目のImageView#getColorFilterはAPI16以降でしか使えないのでAndroidのバージョンのチェックが必要なのと、API16未満の場合の対策をしておかないといけません。
また、色を変換するためのColorFilterは下の3種類のクラスが用意されていて、色を反転させるにはColorMatrixColorFilterを使います。
まずはソースから。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
/** * ColorMatrix data for reversing image color */ private static final float[] REVERSE = { -1.0f, 0.0f, 0.0f, 0.0f, 255.0f, 0.0f, -1.0f, 0.0f, 0.0f, 255.0f, 0.0f, 0.0f, -1.0f, 0.0f, 255.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; @SuppressLint("NewApi") private final void callOnStartRotationListener() { boolean result = false; if (mOnStartRotationListener != null) try { result = mOnStartRotationListener.onStartRotation(this); } catch (Exception e) { if (DEBUG) Log.w(TAG, e); } if (!result) { // apply default visual feedback = image color reversing if (Build.VERSION.SDK_INT >= 16) { // this method is available only if API level >= 16 mSavedColorFilter = getColorFilter(); } if (mColorReverseFilter == null) { mColorReverseFilter = new ColorMatrixColorFilter(new ColorMatrix(REVERSE)); } super.setColorFilter(mColorReverseFilter); // post runnable to reset the color reversing if (mWaitReverseReset == null) { mWaitReverseReset = new WaitReverseReset(); } postDelayed(mWaitReverseReset, REVERSING_TIMEOUT); } } private ColorFilter mSavedColorFilter; @Override public void setColorFilter(ColorFilter cf) { // save the ColorFilter to restore after default visual feedback on start rotating mSavedColorFilter = cf; super.setColorFilter(cf); } private final class WaitReverseReset implements Runnable { @Override public void run() { resetColorFilter(); } } private final void resetColorFilter() { super.setColorFilter(mSavedColorFilter); } |
ところで、回転開始時のデフォルトのフィードバックは色を反転させることに決めてしまいましたが、気に入らない人もいるでしょうし、追加で音を鳴らしたいとかブルブルさせたいなんて人もいるかも知れません。
そこで最初に、コールバックリスナーが割り当てられているかをチェックして割り当てられていれば呼び出します。
もし、コールバックリスナーの返り値がtrueなら、それ以上の処理はしません。コールバックリスナーが割り当てられていないか、もしくは返り値がfalseの場合のみデフォルトの色反転を実行します。
23行目がAPIレベルによって処理を分岐させる部分です。定番ですね。
ただしこのままではAPI16未満の場合にはImageView様の元々のColorFilterに復帰させる事が出来ません。ImageView#clearColorFilterを呼ぶことにしてもいいのですが、今回はImageView#setColorFilter(ColorFilter)をオーバーライドして、セットされたColorFilterインスタンスをフィールド(mSavedColorFilter)に保存しておくことにしました。
なお、ImageView様は他にも2種類のsetColorFilterメソッドがありますが、どちらも内部でImageView#setColorFilter(ColorFilter)を呼び出しているので、ImageView#setColorFilter(ColorFilter)だけをオーバーライドすれば問題ありません(そもそも他の2つはfinalメソッドなのでまっとうな方法ではオーバーライド出来ません)。
27〜29行で色を反転させるためのColorMatrixColorFilterインスタンスを生成して、30行目でImageView様に適用をお願いしています。しかしこのままでは色が反転したっきりになってしまうので、またまた登場、View#postDelayedで一定時間後に色の復帰処理#resetColorFilterを呼んでもらうようにお願いしています。
色の復帰処理では、保存しておいたColorFilterをsuper.setColorFilterでImageView様に適用するだけです。
あっ、そうそう、30行と44行と55行の#setColorFilterは必ずsuper.setColorFilterとして呼ばないと行けません。そうしないと40〜45行でオーバーライドしている方の#setColorFilterメソッドが呼び出され、せっかく保存してあるColorFilterが破壊されてしまいます。
最後は、ImageView様に表示されているイメージを見たままの状態でビットマップとして取得する方法です。
簡単なんで一気に行きます(^o^)/
見たまま…つまりMatrix付きでBitmapを生成すれば良いってことで、こんな感じになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/** * ImageViewに表示されているイメージを見たままの状態(拡大縮小・移動・回転を適用)で * Bitmapとして取得する */ public Bitmap getCurrentImage() { final Bitmap offscreen = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(offscreen); canvas.setMatrix(super.getImageMatrix()); super.getDrawable().draw(canvas); return offscreen; } /** * ImageViewに表示されているイメージの一部分を選択してBitmapとして取得する * @param frame: 選択する矩形を指定する(ImageViewのローカル座標として指定すること) */ public Bitmap getCurrentImage(Rect frame) { Bitmap image = getCurrentImage(); if ((frame != null) && !frame.isEmpty()) { Bitmap tmp = Bitmap.createBitmap(image, frame.left, frame.top, frame.width(), frame.height(), null, false); image.recycle(); image = tmp; } return image; } |
元々は下のコードのようにしてました。でもこれだとImageViewにBitmap以外を割り当てているとダメなんですよね。でもBitmap#createBitmap系にもBitmapFactoryにもDrawableを受け取ってBitmapを生成するメソッドは無いし、CanvasにもDrawableを描画するメソッドは無いし、DrawableのメソッドにはMatrix付きで描画するメソッドは無い(´・ω・`)ガッカリ…。
ちょっとだけムムっと思いながらCanvasのレファレンスを見ていると、なんとCanvas#concat(Matrix)とか、Canvas#setMatrix(Matrix)と言う素晴らしい文字が目に飛び込んできました。Canvas様に先にMatrixを適用しといてからDrawable様に描画をお願いすればいいってだけでしたね。
1 2 3 4 5 6 7 8 |
public Bitmap getCurrentImage() { final Bitmap offscreen = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(offscreen); Bitmap image = ((BitmapDrawable)super.getDrawable()).getBitmap(); canvas.drawBitmap(image, super.getImageMatrix(), null); image.recycle(); return offscreen; } |
ちなみに、はみ出した所は勝手にクリップしてくれますが、イメージが足りない部分は透明になるみたいです。もし足りないところに色を付けるのであれば、9行目のDrawable#draw(Canvas)を呼ぶ前にCanvas#drawARGBとか、Canvas#drawColorなどのメソッドで背景を塗っておいてくださいね。
タッチで拡大縮小・移動できて更に回転まで出来るようにしたImageViewについては今回でおしまいです。
他にも色々細々とメソッドがありますが、詳しくはGitHubでソースを公開しているのでそっちを見てください。
お疲れ様でした。