OSDN Git Service

initial
[gokigen/A01d.git] / app / src / main / java / net / osdn / gokigen / a01d / liveview / ScalableImageViewPanel.java
1 package net.osdn.gokigen.a01d.liveview;
2
3 import android.content.Context;
4 import android.graphics.Bitmap;
5 import android.graphics.Canvas;
6 import android.graphics.Matrix;
7 import android.graphics.drawable.Drawable;
8 import android.net.Uri;
9 import android.support.v7.widget.AppCompatImageView;
10 import android.util.AttributeSet;
11 import android.view.GestureDetector;
12 import android.view.MotionEvent;
13 import android.view.View;
14
15 /**
16  *  イメージを表示する ... ImageViewerSampleから持ってくる
17  *
18  */
19 public class ScalableImageViewPanel extends AppCompatImageView
20 {
21
22     private static enum GestureMode {
23         None,
24         Move,
25         Zoom,
26     }
27
28     private ICameraPanelDrawer cameraPanelDrawer;
29     private boolean overrideDrawer;
30     private Context mContext;
31     private GestureDetector mDoubleTapDetector;
32     private GestureMode mGestureMode;
33
34     /** The affine transformation matrix. */
35     private Matrix mMatrix;
36
37     /** The horizontal moving factor after scaling. */
38     private float mMoveX;
39     /** The vertical moving factor after scaling. */
40     private float mMoveY;
41     /** The X-coordinate origin for calculating the amount of movement. */
42     private float mMovingBaseX;
43     /** The Y-coordinate origin for calculating the amount of movement. */
44     private float mMovingBaseY;
45
46     /** The scaling factor. */
47     private float mScale;
48     /** The minimum value of scaling factor. */
49     private float mScaleMin;
50     /** The maximum value of scaling factor. */
51     private float mScaleMax;
52     /** The distance from the center for determining the amount of scaling. */
53     private float mScalingBaseDistance;
54     /** The center X-coordinate to determine the amount of scaling. */
55     private float mScalingCenterX;
56     /** The center Y-coordinate to determine the amount of scaling. */
57     private float mScalingCenterY;
58
59     /** The width of the view. */
60     private int mViewWidth;
61     /** The height of the view. */
62     private int mViewHeight;
63     /** The width of the image. */
64     private int mImageWidth;
65     /** The height of the image. */
66     private int mImageHeight;
67
68
69     @Override
70     public void setImageDrawable(Drawable drawable) {
71         super.setImageDrawable(drawable);
72         reset();
73     };
74
75     @Override
76     public void setImageBitmap(Bitmap bm) {
77         super.setImageBitmap(bm);
78         reset();
79     };
80
81     @Override
82     public void setImageURI(Uri uri) {
83         super.setImageURI(uri);
84         reset();
85     }
86
87     /**
88      *
89      *
90      */
91     public void setCameraPanelDrawer(boolean isOverrideDrawer, ICameraPanelDrawer drawer)
92     {
93         this.overrideDrawer = isOverrideDrawer;
94         this.cameraPanelDrawer = drawer;
95     }
96
97     /**
98      * Constructs a new CapturedImageView.
99      *
100      * @param context
101      */
102     public ScalableImageViewPanel(Context context) {
103         super(context);
104         mContext = context;
105         init();
106     }
107
108     /**
109      * Constructs a new CapturedImageView.
110      *
111      * @param context
112      * @param attrs
113      */
114     public ScalableImageViewPanel(Context context, AttributeSet attrs) {
115         super(context, attrs);
116         mContext = context;
117         init();
118     }
119
120     /**
121      * Constructs a new CapturedImageView.
122      *
123      * @param context
124      * @param attrs
125      * @param defStyle
126      */
127     public ScalableImageViewPanel(Context context, AttributeSet attrs, int defStyle) {
128         super(context, attrs, defStyle);
129         mContext = context;
130         init();
131     }
132
133     /**
134      * Initializes this instance.
135      */
136     private void init() {
137         this.cameraPanelDrawer = null;
138         this.overrideDrawer = false;
139         this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
140         this.setScaleType(ScaleType.MATRIX);
141         mMatrix = new Matrix();
142         mViewWidth = 0;
143         mViewHeight = 0;
144         mImageWidth = 0;
145         mImageHeight = 0;
146         mGestureMode = GestureMode.None;
147         mMoveX = 0;
148         mMoveY = 0;
149         mScale = 1.f;
150         mScaleMin = 1.f;
151         mScaleMax = 4.f;
152
153         // Setups touch gesture.
154         mDoubleTapDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {
155             @Override
156             public boolean onDoubleTap(MotionEvent e) {
157                 if (mScale != 1.0f) {
158                     // Zooms at tapped point.
159                     updateScaleWithBasePoint(1.0f, e.getX(), e.getY());
160                 } else {
161                     // Zooms out.
162                     fitScreen();
163                 }
164                 updateMatrix();
165                 return true;
166             }
167         });
168     }
169
170     /**
171      * Resets current scaling.
172      */
173     private void reset() {
174         Drawable drawable = this.getDrawable();
175         if (drawable != null) {
176             mImageWidth = drawable.getIntrinsicWidth();
177             mImageHeight = drawable.getIntrinsicHeight();
178             fitScreen();
179             updateMatrix();
180         }
181     }
182
183     @Override
184     protected boolean setFrame(int l, int t, int r, int b) {
185         mViewWidth = r - l;
186         mViewHeight = b - t;
187         if (this.getDrawable() != null) {
188             fitScreen();
189         }
190         updateMatrix();
191         return super.setFrame(l, t, r, b);
192     }
193
194     /**
195      * Returns a scaled X offset.
196      *
197      * @param scale A scaling factor.
198      * @param moveX A horizontal moving factor.
199      * @return A scaled X offset.
200      */
201     private float computeOffsetX(float scale, float moveX) {
202         // Offsets in order to center the image.
203         float scaledWidth = scale * mImageWidth;
204         float offsetX = (mViewWidth - scaledWidth) / 2;
205         // Moves specified offset.
206         offsetX += moveX;
207         return offsetX;
208     }
209
210     /**
211      * Returns a scaled Y offset.
212      *
213      * @param scale A scaling factor.
214      * @param moveY A vertical moving factor.
215      * @return A scaled Y offset.
216      */
217     private float computeOffsetY(float scale, float moveY) {
218         // Offsets in order to center the image.
219         float scaledHeight = scale * mImageHeight;
220         float offsetY = (mViewHeight - scaledHeight) / 2;
221         // Moves specified offset.
222         offsetY += moveY;
223         return offsetY;
224     }
225
226     /**
227      * Updates affine transformation matrix to display the image.
228      */
229     private void updateMatrix() {
230         // Creates new matrix.
231         mMatrix.reset();
232         mMatrix.postScale(mScale, mScale);
233         mMatrix.postTranslate(computeOffsetX(mScale, mMoveX), computeOffsetY(mScale, mMoveY));
234
235         // Updates the matrix.
236         this.setImageMatrix(mMatrix);
237         this.invalidate();
238     }
239
240     /**
241      * Calculates zoom scale. (for the image size to fit screen size).
242      */
243     private void fitScreen() {
244         if ((mImageWidth == 0) || (mImageHeight == 0) || (mViewWidth == 0) || (mViewHeight == 0)) {
245             return;
246         }
247
248         // Clears the moving factors.
249         updateMove(0, 0);
250
251         // Gets scaling ratio.
252         float scaleX = (float)mViewWidth / mImageWidth;
253         float scaleY = (float)mViewHeight / mImageHeight;
254
255         // Updates the scaling factor so that the image will not be larger than the screen size.
256         mScale = Math.min(scaleX, scaleY);
257         mScaleMin = mScale;
258         // 4 times of original image size or 4 times of the screen size.
259         mScaleMax = Math.max(4.f, mScale * 4);
260     }
261
262     /**
263      * Updates the moving factors.
264      *
265      * @param moveX A horizontal moving factor.
266      * @param moveY A vertical moving factor.
267      */
268     protected void updateMove(float moveX, float moveY) {
269         mMoveX = moveX;
270         mMoveY = moveY;
271
272         // Gets scaled size.
273         float scaledWidth = mImageWidth * mScale;
274         float scaledHeight = mImageHeight * mScale;
275
276         // Clips the moving factors.
277         if (scaledWidth <= mViewWidth) {
278             mMoveX = 0;
279         } else {
280             float minMoveX = -(scaledWidth - mViewWidth) / 2;
281             float maxMoveX = +(scaledWidth - mViewWidth) / 2;
282             mMoveX = Math.min(Math.max(mMoveX, minMoveX), maxMoveX);
283         }
284         if (scaledHeight <= mViewHeight) {
285             mMoveY = 0;
286         } else {
287             float minMoveY = -(scaledHeight - mViewHeight) / 2;
288             float maxMoveY = +(scaledHeight - mViewHeight) / 2;
289             mMoveY = Math.min(Math.max(mMoveY, minMoveY), maxMoveY);
290         }
291     }
292
293     /**
294      * Updates the scaling factor. The specified point doesn't change in appearance.
295      *
296      * @param newScale The new scaling factor.
297      * @param baseX The center position of scaling.
298      * @param baseY The center position of scaling.
299      */
300     protected void updateScaleWithBasePoint(float newScale, float baseX, float baseY) {
301         float lastScale = mScale;
302         float lastOffsetX = computeOffsetX(mScale, mMoveX);
303         float lastOffsetY = computeOffsetY(mScale, mMoveY);
304
305         // Updates the scale with clipping.
306         mScale = Math.min(Math.max(newScale, mScaleMin), mScaleMax);
307         mScalingCenterX = baseX;
308         mScalingCenterY = baseY;
309
310         // Gets scaling base point on the image world.
311         float scalingCenterXOnImage = (mScalingCenterX - lastOffsetX) / lastScale;
312         float scalingCenterYOnImage = (mScalingCenterY - lastOffsetY) / lastScale;
313         // Gets scaling base point on the scaled image world.
314         float scalingCenterXOnScaledImage = scalingCenterXOnImage * mScale;
315         float scalingCenterYOnScaledImage = scalingCenterYOnImage * mScale;
316         // Gets scaling base point on the view world.
317         float scalingCenterXOnView = computeOffsetX(mScale, 0) + scalingCenterXOnScaledImage;
318         float scalingCenterYOnView = computeOffsetY(mScale, 0) + scalingCenterYOnScaledImage;
319
320         // Updates moving.
321         updateMove(mScalingCenterX - scalingCenterXOnView, mScalingCenterY - scalingCenterYOnView);
322     }
323
324     @Override
325     public boolean onTouchEvent(MotionEvent event) {
326         if (mDoubleTapDetector.onTouchEvent(event)) {
327             return true;
328         }
329
330         int action = event.getAction() & MotionEvent.ACTION_MASK;
331         int touchCount = event.getPointerCount();
332         switch (action) {
333             case MotionEvent.ACTION_DOWN:
334                 if (mScale > mScaleMin) {
335                     // Starts to move the image and takes in the start point.
336                     mGestureMode = GestureMode.Move;
337                     mMovingBaseX = event.getX();
338                     mMovingBaseY = event.getY();
339                 }
340                 break;
341             case MotionEvent.ACTION_POINTER_DOWN:
342                 if (touchCount >= 2) {
343                     // Starts zooming and takes in the center point.
344                     mGestureMode = GestureMode.Zoom;
345                     float distance = (float)Math.hypot(event.getX(0) - event.getX(1), event.getY(0) - event.getY(1));
346                     mScalingBaseDistance = distance;
347                     mScalingCenterX = (event.getX(0) + event.getX(1)) / 2;
348                     mScalingCenterY = (event.getY(0) + event.getY(1)) / 2;
349                 }
350                 break;
351             case MotionEvent.ACTION_MOVE:
352                 if (mGestureMode == GestureMode.Move) {
353                     // Moves the image and updates the start point.
354                     float moveX = event.getX() - mMovingBaseX;
355                     float moveY = event.getY() - mMovingBaseY;
356                     mMovingBaseX = event.getX();
357                     mMovingBaseY = event.getY();
358                     updateMove(mMoveX + moveX, mMoveY + moveY);
359                     updateMatrix();
360                 } else if ((mGestureMode == GestureMode.Zoom) && (touchCount >= 2)) {
361                     // Zooms the image and updates the distance from the center point.
362                     float distance = (float)Math.hypot(event.getX(0) - event.getX(1), event.getY(0) - event.getY(1));
363                     float scale = distance / mScalingBaseDistance;
364                     mScalingBaseDistance = distance;
365                     updateScaleWithBasePoint(mScale * scale, mScalingCenterX, mScalingCenterY);
366                     updateMatrix();
367                 }
368                 break;
369             case MotionEvent.ACTION_UP:
370             case MotionEvent.ACTION_POINTER_UP:
371                 // Finishes all gestures.
372                 mGestureMode = GestureMode.None;
373                 break;
374         }
375         return true;
376     }
377
378     // The content in view can scroll to horizontal.
379     public boolean canHorizontalScroll()
380     {
381         if (mScale == mScaleMin) {
382             return false;
383         }
384         if (mGestureMode == GestureMode.None)
385         {
386             return false;
387         }
388
389         // TODO: Please improve UX.
390         // If the view rectangle is touching to the edge of the image, the view cannot be scrolled.
391
392         return true;
393     }
394
395     /**
396      *   Draw on Canvas
397      *
398      */
399     @Override
400     public void onDraw(Canvas canvas)
401     {
402         if (!overrideDrawer)
403         {
404             super.onDraw(canvas);
405         }
406         if (cameraPanelDrawer != null)
407         {
408             cameraPanelDrawer.drawCameraPanel(canvas);
409         }
410     }
411 }