1 package net.osdn.gokigen.a01d.liveview;
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;
16 * イメージを表示する ... ImageViewerSampleから持ってくる
19 public class ScalableImageViewPanel extends AppCompatImageView
22 private static enum GestureMode {
28 private ICameraPanelDrawer cameraPanelDrawer;
29 private boolean overrideDrawer;
30 private Context mContext;
31 private GestureDetector mDoubleTapDetector;
32 private GestureMode mGestureMode;
34 /** The affine transformation matrix. */
35 private Matrix mMatrix;
37 /** The horizontal moving factor after scaling. */
39 /** The vertical moving factor after scaling. */
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;
46 /** The scaling factor. */
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;
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;
70 public void setImageDrawable(Drawable drawable) {
71 super.setImageDrawable(drawable);
76 public void setImageBitmap(Bitmap bm) {
77 super.setImageBitmap(bm);
82 public void setImageURI(Uri uri) {
83 super.setImageURI(uri);
91 public void setCameraPanelDrawer(boolean isOverrideDrawer, ICameraPanelDrawer drawer)
93 this.overrideDrawer = isOverrideDrawer;
94 this.cameraPanelDrawer = drawer;
98 * Constructs a new CapturedImageView.
102 public ScalableImageViewPanel(Context context) {
109 * Constructs a new CapturedImageView.
114 public ScalableImageViewPanel(Context context, AttributeSet attrs) {
115 super(context, attrs);
121 * Constructs a new CapturedImageView.
127 public ScalableImageViewPanel(Context context, AttributeSet attrs, int defStyle) {
128 super(context, attrs, defStyle);
134 * Initializes this instance.
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();
146 mGestureMode = GestureMode.None;
153 // Setups touch gesture.
154 mDoubleTapDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {
156 public boolean onDoubleTap(MotionEvent e) {
157 if (mScale != 1.0f) {
158 // Zooms at tapped point.
159 updateScaleWithBasePoint(1.0f, e.getX(), e.getY());
171 * Resets current scaling.
173 private void reset() {
174 Drawable drawable = this.getDrawable();
175 if (drawable != null) {
176 mImageWidth = drawable.getIntrinsicWidth();
177 mImageHeight = drawable.getIntrinsicHeight();
184 protected boolean setFrame(int l, int t, int r, int b) {
187 if (this.getDrawable() != null) {
191 return super.setFrame(l, t, r, b);
195 * Returns a scaled X offset.
197 * @param scale A scaling factor.
198 * @param moveX A horizontal moving factor.
199 * @return A scaled X offset.
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.
211 * Returns a scaled Y offset.
213 * @param scale A scaling factor.
214 * @param moveY A vertical moving factor.
215 * @return A scaled Y offset.
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.
227 * Updates affine transformation matrix to display the image.
229 private void updateMatrix() {
230 // Creates new matrix.
232 mMatrix.postScale(mScale, mScale);
233 mMatrix.postTranslate(computeOffsetX(mScale, mMoveX), computeOffsetY(mScale, mMoveY));
235 // Updates the matrix.
236 this.setImageMatrix(mMatrix);
241 * Calculates zoom scale. (for the image size to fit screen size).
243 private void fitScreen() {
244 if ((mImageWidth == 0) || (mImageHeight == 0) || (mViewWidth == 0) || (mViewHeight == 0)) {
248 // Clears the moving factors.
251 // Gets scaling ratio.
252 float scaleX = (float)mViewWidth / mImageWidth;
253 float scaleY = (float)mViewHeight / mImageHeight;
255 // Updates the scaling factor so that the image will not be larger than the screen size.
256 mScale = Math.min(scaleX, scaleY);
258 // 4 times of original image size or 4 times of the screen size.
259 mScaleMax = Math.max(4.f, mScale * 4);
263 * Updates the moving factors.
265 * @param moveX A horizontal moving factor.
266 * @param moveY A vertical moving factor.
268 protected void updateMove(float moveX, float moveY) {
273 float scaledWidth = mImageWidth * mScale;
274 float scaledHeight = mImageHeight * mScale;
276 // Clips the moving factors.
277 if (scaledWidth <= mViewWidth) {
280 float minMoveX = -(scaledWidth - mViewWidth) / 2;
281 float maxMoveX = +(scaledWidth - mViewWidth) / 2;
282 mMoveX = Math.min(Math.max(mMoveX, minMoveX), maxMoveX);
284 if (scaledHeight <= mViewHeight) {
287 float minMoveY = -(scaledHeight - mViewHeight) / 2;
288 float maxMoveY = +(scaledHeight - mViewHeight) / 2;
289 mMoveY = Math.min(Math.max(mMoveY, minMoveY), maxMoveY);
294 * Updates the scaling factor. The specified point doesn't change in appearance.
296 * @param newScale The new scaling factor.
297 * @param baseX The center position of scaling.
298 * @param baseY The center position of scaling.
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);
305 // Updates the scale with clipping.
306 mScale = Math.min(Math.max(newScale, mScaleMin), mScaleMax);
307 mScalingCenterX = baseX;
308 mScalingCenterY = baseY;
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;
321 updateMove(mScalingCenterX - scalingCenterXOnView, mScalingCenterY - scalingCenterYOnView);
325 public boolean onTouchEvent(MotionEvent event) {
326 if (mDoubleTapDetector.onTouchEvent(event)) {
330 int action = event.getAction() & MotionEvent.ACTION_MASK;
331 int touchCount = event.getPointerCount();
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();
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;
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);
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);
369 case MotionEvent.ACTION_UP:
370 case MotionEvent.ACTION_POINTER_UP:
371 // Finishes all gestures.
372 mGestureMode = GestureMode.None;
378 // The content in view can scroll to horizontal.
379 public boolean canHorizontalScroll()
381 if (mScale == mScaleMin) {
384 if (mGestureMode == GestureMode.None)
389 // TODO: Please improve UX.
390 // If the view rectangle is touching to the edge of the image, the view cannot be scrolled.
400 public void onDraw(Canvas canvas)
404 super.onDraw(canvas);
406 if (cameraPanelDrawer != null)
408 cameraPanelDrawer.drawCameraPanel(canvas);