1 package com.cooliris.media;
4 * Copyright (C) 2009 The Android Open Source Project
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.Matrix;
22 import android.graphics.RectF;
23 import android.graphics.drawable.Drawable;
24 import android.os.Handler;
25 import android.util.AttributeSet;
26 import android.view.KeyEvent;
27 import android.widget.ImageView;
29 abstract class ImageViewTouchBase extends ImageView {
31 @SuppressWarnings("unused")
32 private static final String TAG = "ImageViewTouchBase";
34 // This is the base transformation which is used to show the image
35 // initially. The current computation for this shows the image in
36 // it's entirety, letterboxing as needed. One could choose to
37 // show the image as cropped instead.
39 // This matrix is recomputed when we go from the thumbnail image to
40 // the full size image.
41 protected Matrix mBaseMatrix = new Matrix();
43 // This is the supplementary transformation which reflects what
44 // the user has done in terms of zooming and panning.
46 // This matrix remains the same when we go from the thumbnail image
47 // to the full size image.
48 protected Matrix mSuppMatrix = new Matrix();
50 // This is the final matrix which is computed as the concatentation
51 // of the base matrix and the supplementary matrix.
52 private final Matrix mDisplayMatrix = new Matrix();
54 // Temporary buffer used for getting the values out of a matrix.
55 private final float[] mMatrixValues = new float[9];
57 // The current bitmap being displayed.
58 final protected RotateBitmap mBitmapDisplayed = new RotateBitmap(null);
60 int mThisWidth = -1, mThisHeight = -1;
64 // ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished
65 // its use of that Bitmap.
66 public interface Recycler {
67 public void recycle(Bitmap b);
70 public void setRecycler(Recycler r) {
74 private Recycler mRecycler;
77 protected void onLayout(boolean changed, int left, int top,
78 int right, int bottom) {
79 super.onLayout(changed, left, top, right, bottom);
80 mThisWidth = right - left;
81 mThisHeight = bottom - top;
82 Runnable r = mOnLayoutRunnable;
84 mOnLayoutRunnable = null;
87 if (mBitmapDisplayed.getBitmap() != null) {
88 getProperBaseMatrix(mBitmapDisplayed, mBaseMatrix);
89 setImageMatrix(getImageViewMatrix());
94 public boolean onKeyDown(int keyCode, KeyEvent event) {
95 if (keyCode == KeyEvent.KEYCODE_BACK && getScale() > 1.0f) {
96 // If we're zoomed in, pressing Back jumps out to show the entire
97 // image, otherwise Back returns the user to the gallery.
101 return super.onKeyDown(keyCode, event);
104 protected Handler mHandler = new Handler();
106 protected int mLastXTouchPos;
107 protected int mLastYTouchPos;
110 public void setImageBitmap(Bitmap bitmap) {
111 setImageBitmap(bitmap, 0);
114 private void setImageBitmap(Bitmap bitmap, int rotation) {
115 super.setImageBitmap(bitmap);
116 Drawable d = getDrawable();
121 Bitmap old = mBitmapDisplayed.getBitmap();
122 mBitmapDisplayed.setBitmap(bitmap);
123 mBitmapDisplayed.setRotation(rotation);
125 if (old != null && old != bitmap && mRecycler != null) {
126 mRecycler.recycle(old);
130 public void clear() {
131 setImageBitmapResetBase(null, true);
134 private Runnable mOnLayoutRunnable = null;
136 // This function changes bitmap, reset base matrix according to the size
137 // of the bitmap, and optionally reset the supplementary matrix.
138 public void setImageBitmapResetBase(final Bitmap bitmap,
139 final boolean resetSupp) {
140 setImageRotateBitmapResetBase(new RotateBitmap(bitmap), resetSupp);
143 public void setImageRotateBitmapResetBase(final RotateBitmap bitmap,
144 final boolean resetSupp) {
145 final int viewWidth = getWidth();
147 if (viewWidth <= 0) {
148 mOnLayoutRunnable = new Runnable() {
150 setImageRotateBitmapResetBase(bitmap, resetSupp);
156 if (bitmap.getBitmap() != null) {
157 getProperBaseMatrix(bitmap, mBaseMatrix);
158 setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
161 setImageBitmap(null);
167 setImageMatrix(getImageViewMatrix());
168 mMaxZoom = maxZoom();
171 // Center as much as possible in one or both axis. Centering is
172 // defined as follows: if the image is scaled down below the
173 // view's dimensions then center it (literally). If the image
174 // is scaled larger than the view and is translated out of view
175 // then translate it back into view (i.e. eliminate black bars).
176 protected void center(boolean horizontal, boolean vertical) {
177 if (mBitmapDisplayed.getBitmap() == null) {
181 Matrix m = getImageViewMatrix();
183 RectF rect = new RectF(0, 0,
184 mBitmapDisplayed.getBitmap().getWidth(),
185 mBitmapDisplayed.getBitmap().getHeight());
189 float height = rect.height();
190 float width = rect.width();
192 float deltaX = 0, deltaY = 0;
195 int viewHeight = getHeight();
196 if (height < viewHeight) {
197 deltaY = (viewHeight - height) / 2 - rect.top;
198 } else if (rect.top > 0) {
200 } else if (rect.bottom < viewHeight) {
201 deltaY = getHeight() - rect.bottom;
206 int viewWidth = getWidth();
207 if (width < viewWidth) {
208 deltaX = (viewWidth - width) / 2 - rect.left;
209 } else if (rect.left > 0) {
211 } else if (rect.right < viewWidth) {
212 deltaX = viewWidth - rect.right;
216 postTranslate(deltaX, deltaY);
217 setImageMatrix(getImageViewMatrix());
220 public ImageViewTouchBase(Context context) {
225 public ImageViewTouchBase(Context context, AttributeSet attrs) {
226 super(context, attrs);
230 private void init() {
231 setScaleType(ImageView.ScaleType.MATRIX);
234 protected float getValue(Matrix matrix, int whichValue) {
235 matrix.getValues(mMatrixValues);
236 return mMatrixValues[whichValue];
239 // Get the scale factor out of the matrix.
240 protected float getScale(Matrix matrix) {
241 return getValue(matrix, Matrix.MSCALE_X);
244 protected float getScale() {
245 return getScale(mSuppMatrix);
248 // Setup the base matrix so that the image is centered and scaled properly.
249 private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix) {
250 float viewWidth = getWidth();
251 float viewHeight = getHeight();
253 float w = bitmap.getWidth();
254 float h = bitmap.getHeight();
257 // We limit up-scaling to 2x otherwise the result may look bad if it's
259 float widthScale = Math.min(viewWidth / w, 2.0f);
260 float heightScale = Math.min(viewHeight / h, 2.0f);
261 float scale = Math.min(widthScale, heightScale);
263 matrix.postConcat(bitmap.getRotateMatrix());
264 matrix.postScale(scale, scale);
266 matrix.postTranslate(
267 (viewWidth - w * scale) / 2F,
268 (viewHeight - h * scale) / 2F);
271 // Combine the base matrix and the supp matrix to make the final matrix.
272 protected Matrix getImageViewMatrix() {
273 // The final matrix is computed as the concatentation of the base matrix
274 // and the supplementary matrix.
275 mDisplayMatrix.set(mBaseMatrix);
276 mDisplayMatrix.postConcat(mSuppMatrix);
277 return mDisplayMatrix;
280 static final float SCALE_RATE = 1.25F;
282 // Sets the maximum zoom, which is a scale relative to the base matrix. It
283 // is calculated to show the image at 400% zoom regardless of screen or
284 // image orientation. If in the future we decode the full 3 megapixel image,
285 // rather than the current 1024x768, this should be changed down to 200%.
286 protected float maxZoom() {
287 if (mBitmapDisplayed.getBitmap() == null) {
291 float fw = (float) mBitmapDisplayed.getWidth() / (float) mThisWidth;
292 float fh = (float) mBitmapDisplayed.getHeight() / (float) mThisHeight;
293 float max = Math.max(fw, fh) * 4;
297 protected void zoomTo(float scale, float centerX, float centerY) {
298 if (scale > mMaxZoom) {
302 float oldScale = getScale();
303 float deltaScale = scale / oldScale;
305 mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
306 setImageMatrix(getImageViewMatrix());
310 protected void zoomTo(final float scale, final float centerX,
311 final float centerY, final float durationMs) {
312 final float incrementPerMs = (scale - getScale()) / durationMs;
313 final float oldScale = getScale();
314 final long startTime = System.currentTimeMillis();
316 mHandler.post(new Runnable() {
318 long now = System.currentTimeMillis();
319 float currentMs = Math.min(durationMs, now - startTime);
320 float target = oldScale + (incrementPerMs * currentMs);
321 zoomTo(target, centerX, centerY);
323 if (currentMs < durationMs) {
330 protected void zoomTo(float scale) {
331 float cx = getWidth() / 2F;
332 float cy = getHeight() / 2F;
334 zoomTo(scale, cx, cy);
337 protected void zoomIn() {
341 protected void zoomOut() {
345 protected void zoomIn(float rate) {
346 if (getScale() >= mMaxZoom) {
347 return; // Don't let the user zoom into the molecular level.
349 if (mBitmapDisplayed.getBitmap() == null) {
353 float cx = getWidth() / 2F;
354 float cy = getHeight() / 2F;
356 mSuppMatrix.postScale(rate, rate, cx, cy);
357 setImageMatrix(getImageViewMatrix());
360 protected void zoomOut(float rate) {
361 if (mBitmapDisplayed.getBitmap() == null) {
365 float cx = getWidth() / 2F;
366 float cy = getHeight() / 2F;
368 // Zoom out to at most 1x.
369 Matrix tmp = new Matrix(mSuppMatrix);
370 tmp.postScale(1F / rate, 1F / rate, cx, cy);
372 if (getScale(tmp) < 1F) {
373 mSuppMatrix.setScale(1F, 1F, cx, cy);
375 mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
377 setImageMatrix(getImageViewMatrix());
381 protected void postTranslate(float dx, float dy) {
382 mSuppMatrix.postTranslate(dx, dy);
385 protected void panBy(float dx, float dy) {
386 postTranslate(dx, dy);
387 setImageMatrix(getImageViewMatrix());