OSDN Git Service

Initial commit of 3D gallery source code to project.
[android-x86/packages-apps-Gallery2.git] / src / com / cooliris / media / ImageViewTouchBase.java
1 package com.cooliris.media;
2
3 /*
4  * Copyright (C) 2009 The Android Open Source Project
5  *
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
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  */
18
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;
28
29 abstract 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     final protected 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 && 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.
98             zoomTo(1.0f);
99             return true;
100         }
101         return super.onKeyDown(keyCode, event);
102     }
103
104     protected Handler mHandler = new Handler();
105
106     protected int mLastXTouchPos;
107     protected int mLastYTouchPos;
108
109     @Override
110     public void setImageBitmap(Bitmap bitmap) {
111         setImageBitmap(bitmap, 0);
112     }
113
114     private void setImageBitmap(Bitmap bitmap, int rotation) {
115         super.setImageBitmap(bitmap);
116         Drawable d = getDrawable();
117         if (d != null) {
118             d.setDither(true);
119         }
120
121         Bitmap old = mBitmapDisplayed.getBitmap();
122         mBitmapDisplayed.setBitmap(bitmap);
123         mBitmapDisplayed.setRotation(rotation);
124
125         if (old != null && old != bitmap && mRecycler != null) {
126             mRecycler.recycle(old);
127         }
128     }
129
130     public void clear() {
131         setImageBitmapResetBase(null, true);
132     }
133
134     private Runnable mOnLayoutRunnable = null;
135
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);
141     }
142
143     public void setImageRotateBitmapResetBase(final RotateBitmap bitmap,
144             final boolean resetSupp) {
145         final int viewWidth = getWidth();
146
147         if (viewWidth <= 0)  {
148             mOnLayoutRunnable = new Runnable() {
149                 public void run() {
150                     setImageRotateBitmapResetBase(bitmap, resetSupp);
151                 }
152             };
153             return;
154         }
155
156         if (bitmap.getBitmap() != null) {
157             getProperBaseMatrix(bitmap, mBaseMatrix);
158             setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
159         } else {
160             mBaseMatrix.reset();
161             setImageBitmap(null);
162         }
163
164         if (resetSupp) {
165             mSuppMatrix.reset();
166         }
167         setImageMatrix(getImageViewMatrix());
168         mMaxZoom = maxZoom();
169     }
170
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) {
178             return;
179         }
180
181         Matrix m = getImageViewMatrix();
182
183         RectF rect = new RectF(0, 0,
184                 mBitmapDisplayed.getBitmap().getWidth(),
185                 mBitmapDisplayed.getBitmap().getHeight());
186
187         m.mapRect(rect);
188
189         float height = rect.height();
190         float width  = rect.width();
191
192         float deltaX = 0, deltaY = 0;
193
194         if (vertical) {
195             int viewHeight = getHeight();
196             if (height < viewHeight) {
197                 deltaY = (viewHeight - height) / 2 - rect.top;
198             } else if (rect.top > 0) {
199                 deltaY = -rect.top;
200             } else if (rect.bottom < viewHeight) {
201                 deltaY = getHeight() - rect.bottom;
202             }
203         }
204
205         if (horizontal) {
206             int viewWidth = getWidth();
207             if (width < viewWidth) {
208                 deltaX = (viewWidth - width) / 2 - rect.left;
209             } else if (rect.left > 0) {
210                 deltaX = -rect.left;
211             } else if (rect.right < viewWidth) {
212                 deltaX = viewWidth - rect.right;
213             }
214         }
215
216         postTranslate(deltaX, deltaY);
217         setImageMatrix(getImageViewMatrix());
218     }
219
220     public ImageViewTouchBase(Context context) {
221         super(context);
222         init();
223     }
224
225     public ImageViewTouchBase(Context context, AttributeSet attrs) {
226         super(context, attrs);
227         init();
228     }
229
230     private void init() {
231         setScaleType(ImageView.ScaleType.MATRIX);
232     }
233
234     protected float getValue(Matrix matrix, int whichValue) {
235         matrix.getValues(mMatrixValues);
236         return mMatrixValues[whichValue];
237     }
238
239     // Get the scale factor out of the matrix.
240     protected float getScale(Matrix matrix) {
241         return getValue(matrix, Matrix.MSCALE_X);
242     }
243
244     protected float getScale() {
245         return getScale(mSuppMatrix);
246     }
247
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();
252
253         float w = bitmap.getWidth();
254         float h = bitmap.getHeight();
255         matrix.reset();
256
257         // We limit up-scaling to 2x otherwise the result may look bad if it's
258         // a small icon.
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);
262
263         matrix.postConcat(bitmap.getRotateMatrix());
264         matrix.postScale(scale, scale);
265
266         matrix.postTranslate(
267                 (viewWidth  - w * scale) / 2F,
268                 (viewHeight - h * scale) / 2F);
269     }
270
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;
278     }
279
280     static final float SCALE_RATE = 1.25F;
281
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) {
288             return 1F;
289         }
290
291         float fw = (float) mBitmapDisplayed.getWidth()  / (float) mThisWidth;
292         float fh = (float) mBitmapDisplayed.getHeight() / (float) mThisHeight;
293         float max = Math.max(fw, fh) * 4;
294         return max;
295     }
296
297     protected void zoomTo(float scale, float centerX, float centerY) {
298         if (scale > mMaxZoom) {
299             scale = mMaxZoom;
300         }
301
302         float oldScale = getScale();
303         float deltaScale = scale / oldScale;
304
305         mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
306         setImageMatrix(getImageViewMatrix());
307         center(true, true);
308     }
309
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();
315
316         mHandler.post(new Runnable() {
317             public void run() {
318                 long now = System.currentTimeMillis();
319                 float currentMs = Math.min(durationMs, now - startTime);
320                 float target = oldScale + (incrementPerMs * currentMs);
321                 zoomTo(target, centerX, centerY);
322
323                 if (currentMs < durationMs) {
324                     mHandler.post(this);
325                 }
326             }
327         });
328     }
329
330     protected void zoomTo(float scale) {
331         float cx = getWidth() / 2F;
332         float cy = getHeight() / 2F;
333
334         zoomTo(scale, cx, cy);
335     }
336
337     protected void zoomIn() {
338         zoomIn(SCALE_RATE);
339     }
340
341     protected void zoomOut() {
342         zoomOut(SCALE_RATE);
343     }
344
345     protected void zoomIn(float rate) {
346         if (getScale() >= mMaxZoom) {
347             return;     // Don't let the user zoom into the molecular level.
348         }
349         if (mBitmapDisplayed.getBitmap() == null) {
350             return;
351         }
352
353         float cx = getWidth() / 2F;
354         float cy = getHeight() / 2F;
355
356         mSuppMatrix.postScale(rate, rate, cx, cy);
357         setImageMatrix(getImageViewMatrix());
358     }
359
360     protected void zoomOut(float rate) {
361         if (mBitmapDisplayed.getBitmap() == null) {
362             return;
363         }
364
365         float cx = getWidth() / 2F;
366         float cy = getHeight() / 2F;
367
368         // Zoom out to at most 1x.
369         Matrix tmp = new Matrix(mSuppMatrix);
370         tmp.postScale(1F / rate, 1F / rate, cx, cy);
371
372         if (getScale(tmp) < 1F) {
373             mSuppMatrix.setScale(1F, 1F, cx, cy);
374         } else {
375             mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
376         }
377         setImageMatrix(getImageViewMatrix());
378         center(true, true);
379     }
380
381     protected void postTranslate(float dx, float dy) {
382         mSuppMatrix.postTranslate(dx, dy);
383     }
384
385     protected void panBy(float dx, float dy) {
386         postTranslate(dx, dy);
387         setImageMatrix(getImageViewMatrix());
388     }
389 }