ImageViewTouchBase.java revision be2f475fe49538e38753ad391fb3176f9b0d1c69
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.camera; 18 19import android.content.Context; 20import android.graphics.Bitmap; 21import android.graphics.Matrix; 22import android.graphics.RectF; 23import android.graphics.drawable.Drawable; 24import android.os.Handler; 25import android.util.AttributeSet; 26import android.view.KeyEvent; 27import android.widget.ImageView; 28 29abstract class ImageViewTouchBase extends ImageView { 30 31 @SuppressWarnings("unused") 32 private static final String TAG = "ImageViewTouchBase"; 33 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. 38 // 39 // This matrix is recomputed when we go from the thumbnail image to 40 // the full size image. 41 protected Matrix mBaseMatrix = new Matrix(); 42 43 // This is the supplementary transformation which reflects what 44 // the user has done in terms of zooming and panning. 45 // 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(); 49 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(); 53 54 // Temporary buffer used for getting the values out of a matrix. 55 private final float[] mMatrixValues = new float[9]; 56 57 // The current bitmap being displayed. 58 protected final RotateBitmap mBitmapDisplayed = new RotateBitmap(null); 59 60 int mThisWidth = -1, mThisHeight = -1; 61 62 float mMaxZoom; 63 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); 68 } 69 70 public void setRecycler(Recycler r) { 71 mRecycler = r; 72 } 73 74 private Recycler mRecycler; 75 76 @Override 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; 83 if (r != null) { 84 mOnLayoutRunnable = null; 85 r.run(); 86 } 87 if (mBitmapDisplayed.getBitmap() != null) { 88 getProperBaseMatrix(mBitmapDisplayed, mBaseMatrix); 89 setImageMatrix(getImageViewMatrix()); 90 } 91 } 92 93 @Override 94 public boolean onKeyDown(int keyCode, KeyEvent event) { 95 if (keyCode == KeyEvent.KEYCODE_BACK 96 && event.getRepeatCount() == 0) { 97 event.startTracking(); 98 return true; 99 } 100 return super.onKeyDown(keyCode, event); 101 } 102 103 @Override 104 public boolean onKeyUp(int keyCode, KeyEvent event) { 105 if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() 106 && !event.isCanceled()) { 107 if (getScale() > 1.0f) { 108 // If we're zoomed in, pressing Back jumps out to show the 109 // entire image, otherwise Back returns the user to the gallery. 110 zoomTo(1.0f); 111 return true; 112 } 113 } 114 return super.onKeyUp(keyCode, event); 115 } 116 117 protected Handler mHandler = new Handler(); 118 119 protected int mLastXTouchPos; 120 protected int mLastYTouchPos; 121 122 @Override 123 public void setImageBitmap(Bitmap bitmap) { 124 setImageBitmap(bitmap, 0); 125 } 126 127 private void setImageBitmap(Bitmap bitmap, int rotation) { 128 super.setImageBitmap(bitmap); 129 Drawable d = getDrawable(); 130 if (d != null) { 131 d.setDither(true); 132 } 133 134 Bitmap old = mBitmapDisplayed.getBitmap(); 135 mBitmapDisplayed.setBitmap(bitmap); 136 mBitmapDisplayed.setRotation(rotation); 137 138 if (old != null && old != bitmap && mRecycler != null) { 139 mRecycler.recycle(old); 140 } 141 } 142 143 public void clear() { 144 setImageBitmapResetBase(null, true); 145 } 146 147 private Runnable mOnLayoutRunnable = null; 148 149 // This function changes bitmap, reset base matrix according to the size 150 // of the bitmap, and optionally reset the supplementary matrix. 151 public void setImageBitmapResetBase(final Bitmap bitmap, 152 final boolean resetSupp) { 153 setImageRotateBitmapResetBase(new RotateBitmap(bitmap), resetSupp); 154 } 155 156 public void setImageRotateBitmapResetBase(final RotateBitmap bitmap, 157 final boolean resetSupp) { 158 final int viewWidth = getWidth(); 159 160 if (viewWidth <= 0) { 161 mOnLayoutRunnable = new Runnable() { 162 public void run() { 163 setImageRotateBitmapResetBase(bitmap, resetSupp); 164 } 165 }; 166 return; 167 } 168 169 if (bitmap.getBitmap() != null) { 170 getProperBaseMatrix(bitmap, mBaseMatrix); 171 setImageBitmap(bitmap.getBitmap(), bitmap.getRotation()); 172 } else { 173 mBaseMatrix.reset(); 174 setImageBitmap(null); 175 } 176 177 if (resetSupp) { 178 mSuppMatrix.reset(); 179 } 180 setImageMatrix(getImageViewMatrix()); 181 mMaxZoom = maxZoom(); 182 } 183 184 // Center as much as possible in one or both axis. Centering is 185 // defined as follows: if the image is scaled down below the 186 // view's dimensions then center it (literally). If the image 187 // is scaled larger than the view and is translated out of view 188 // then translate it back into view (i.e. eliminate black bars). 189 protected void center(boolean horizontal, boolean vertical) { 190 if (mBitmapDisplayed.getBitmap() == null) { 191 return; 192 } 193 194 Matrix m = getImageViewMatrix(); 195 196 RectF rect = new RectF(0, 0, 197 mBitmapDisplayed.getBitmap().getWidth(), 198 mBitmapDisplayed.getBitmap().getHeight()); 199 200 m.mapRect(rect); 201 202 float height = rect.height(); 203 float width = rect.width(); 204 205 float deltaX = 0, deltaY = 0; 206 207 if (vertical) { 208 int viewHeight = getHeight(); 209 if (height < viewHeight) { 210 deltaY = (viewHeight - height) / 2 - rect.top; 211 } else if (rect.top > 0) { 212 deltaY = -rect.top; 213 } else if (rect.bottom < viewHeight) { 214 deltaY = getHeight() - rect.bottom; 215 } 216 } 217 218 if (horizontal) { 219 int viewWidth = getWidth(); 220 if (width < viewWidth) { 221 deltaX = (viewWidth - width) / 2 - rect.left; 222 } else if (rect.left > 0) { 223 deltaX = -rect.left; 224 } else if (rect.right < viewWidth) { 225 deltaX = viewWidth - rect.right; 226 } 227 } 228 229 postTranslate(deltaX, deltaY); 230 setImageMatrix(getImageViewMatrix()); 231 } 232 233 public ImageViewTouchBase(Context context) { 234 super(context); 235 init(); 236 } 237 238 public ImageViewTouchBase(Context context, AttributeSet attrs) { 239 super(context, attrs); 240 init(); 241 } 242 243 private void init() { 244 setScaleType(ImageView.ScaleType.MATRIX); 245 } 246 247 protected float getValue(Matrix matrix, int whichValue) { 248 matrix.getValues(mMatrixValues); 249 return mMatrixValues[whichValue]; 250 } 251 252 // Get the scale factor out of the matrix. 253 protected float getScale(Matrix matrix) { 254 return getValue(matrix, Matrix.MSCALE_X); 255 } 256 257 protected float getScale() { 258 return getScale(mSuppMatrix); 259 } 260 261 // Setup the base matrix so that the image is centered and scaled properly. 262 private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix) { 263 float viewWidth = getWidth(); 264 float viewHeight = getHeight(); 265 266 float w = bitmap.getWidth(); 267 float h = bitmap.getHeight(); 268 matrix.reset(); 269 270 // We limit up-scaling to 3x otherwise the result may look bad if it's 271 // a small icon. 272 float widthScale = Math.min(viewWidth / w, 3.0f); 273 float heightScale = Math.min(viewHeight / h, 3.0f); 274 float scale = Math.min(widthScale, heightScale); 275 276 matrix.postConcat(bitmap.getRotateMatrix()); 277 matrix.postScale(scale, scale); 278 279 matrix.postTranslate( 280 (viewWidth - w * scale) / 2F, 281 (viewHeight - h * scale) / 2F); 282 } 283 284 // Combine the base matrix and the supp matrix to make the final matrix. 285 protected Matrix getImageViewMatrix() { 286 // The final matrix is computed as the concatentation of the base matrix 287 // and the supplementary matrix. 288 mDisplayMatrix.set(mBaseMatrix); 289 mDisplayMatrix.postConcat(mSuppMatrix); 290 return mDisplayMatrix; 291 } 292 293 static final float SCALE_RATE = 1.25F; 294 295 // Sets the maximum zoom, which is a scale relative to the base matrix. It 296 // is calculated to show the image at 400% zoom regardless of screen or 297 // image orientation. If in the future we decode the full 3 megapixel image, 298 // rather than the current 1024x768, this should be changed down to 200%. 299 protected float maxZoom() { 300 if (mBitmapDisplayed.getBitmap() == null) { 301 return 1F; 302 } 303 304 float fw = (float) mBitmapDisplayed.getWidth() / (float) mThisWidth; 305 float fh = (float) mBitmapDisplayed.getHeight() / (float) mThisHeight; 306 float max = Math.max(fw, fh) * 4; 307 return max; 308 } 309 310 protected void zoomTo(float scale, float centerX, float centerY) { 311 if (scale > mMaxZoom) { 312 scale = mMaxZoom; 313 } 314 315 float oldScale = getScale(); 316 float deltaScale = scale / oldScale; 317 318 mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY); 319 setImageMatrix(getImageViewMatrix()); 320 center(true, true); 321 } 322 323 protected void zoomTo(final float scale, final float centerX, 324 final float centerY, final float durationMs) { 325 final float incrementPerMs = (scale - getScale()) / durationMs; 326 final float oldScale = getScale(); 327 final long startTime = System.currentTimeMillis(); 328 329 mHandler.post(new Runnable() { 330 public void run() { 331 long now = System.currentTimeMillis(); 332 float currentMs = Math.min(durationMs, now - startTime); 333 float target = oldScale + (incrementPerMs * currentMs); 334 zoomTo(target, centerX, centerY); 335 336 if (currentMs < durationMs) { 337 mHandler.post(this); 338 } 339 } 340 }); 341 } 342 343 protected void zoomTo(float scale) { 344 float cx = getWidth() / 2F; 345 float cy = getHeight() / 2F; 346 347 zoomTo(scale, cx, cy); 348 } 349 350 protected void zoomToPoint(float scale, float pointX, float pointY) { 351 float cx = getWidth() / 2F; 352 float cy = getHeight() / 2F; 353 354 panBy(cx - pointX, cy - pointY); 355 zoomTo(scale, cx, cy); 356 } 357 358 protected void zoomIn() { 359 zoomIn(SCALE_RATE); 360 } 361 362 protected void zoomOut() { 363 zoomOut(SCALE_RATE); 364 } 365 366 protected void zoomIn(float rate) { 367 if (getScale() >= mMaxZoom) { 368 return; // Don't let the user zoom into the molecular level. 369 } 370 if (mBitmapDisplayed.getBitmap() == null) { 371 return; 372 } 373 374 float cx = getWidth() / 2F; 375 float cy = getHeight() / 2F; 376 377 mSuppMatrix.postScale(rate, rate, cx, cy); 378 setImageMatrix(getImageViewMatrix()); 379 } 380 381 protected void zoomOut(float rate) { 382 if (mBitmapDisplayed.getBitmap() == null) { 383 return; 384 } 385 386 float cx = getWidth() / 2F; 387 float cy = getHeight() / 2F; 388 389 // Zoom out to at most 1x. 390 Matrix tmp = new Matrix(mSuppMatrix); 391 tmp.postScale(1F / rate, 1F / rate, cx, cy); 392 393 if (getScale(tmp) < 1F) { 394 mSuppMatrix.setScale(1F, 1F, cx, cy); 395 } else { 396 mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy); 397 } 398 setImageMatrix(getImageViewMatrix()); 399 center(true, true); 400 } 401 402 protected void postTranslate(float dx, float dy) { 403 mSuppMatrix.postTranslate(dx, dy); 404 } 405 406 protected void panBy(float dx, float dy) { 407 postTranslate(dx, dy); 408 setImageMatrix(getImageViewMatrix()); 409 } 410} 411