/* * Copyright (c) 2012 Jason Polites Licensed under the Apache License, Version * 2.0 (the "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law * or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package com.tweetlanes.android.core.widget.gestureimageview; import android.content.res.Configuration; import android.graphics.PointF; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; class GestureImageViewTouchListener implements OnTouchListener { private final GestureImageView mImage; private OnClickListener mOnClickListener; private final PointF mCurrent = new PointF(); private final PointF mLast = new PointF(); private final PointF mNext = new PointF(); private final PointF mMidpoint = new PointF(); private final VectorF mScaleVector = new VectorF(); private final VectorF mPinchVector = new VectorF(); private boolean mTouched = false; private boolean mInZoom = false; private float mInitialDistance; private float mLastScale = 1.0f; private float mCurrentScale = 1.0f; private float mBoundaryLeft = 0; private float mBoundaryTop = 0; private float mBoundaryRight = 0; private float mBoundaryBottom = 0; private float mMaxScale = 5.0f; private float mMinScale = 0.25f; private float mFitScaleHorizontal = 1.0f; private float mFitScaleVertical = 1.0f; private int mCanvasWidth = 0; private int mCanvasHeight = 0; private float mCenterX = 0; private float mCenterY = 0; private float mStartingScale = 0; private boolean mCanDragX = false; private boolean mCanDragY = false; private boolean mMultiTouch = false; private final int mDisplayWidth; private final int mDisplayHeight; private final int mImageWidth; private final int mImageHeight; private final FlingListener mFlingListener; private final FlingAnimation mFlingAnimation; private final ZoomAnimation mZoomAnimation; private final GestureDetector mTapDetector; private final GestureDetector mFlingDetector; private final GestureImageViewListener mImageListener; public GestureImageViewTouchListener(final GestureImageView image, int displayWidth, int displayHeight) { super(); this.mImage = image; this.mDisplayWidth = displayWidth; this.mDisplayHeight = displayHeight; this.mCenterX = (float) displayWidth / 2.0f; this.mCenterY = (float) displayHeight / 2.0f; this.mImageWidth = image.getImageWidth(); this.mImageHeight = image.getImageHeight(); mStartingScale = image.getScale(); mCurrentScale = mStartingScale; mLastScale = mStartingScale; mBoundaryRight = displayWidth; mBoundaryBottom = displayHeight; mBoundaryLeft = 0; mBoundaryTop = 0; mNext.x = image.getImageX(); mNext.y = image.getImageY(); mFlingListener = new FlingListener(); mFlingAnimation = new FlingAnimation(); mZoomAnimation = new ZoomAnimation(); MoveAnimation moveAnimation = new MoveAnimation(); mFlingAnimation.setListener(new FlingAnimationListener() { @Override public void onMove(float x, float y) { handleDrag(mCurrent.x + x, mCurrent.y + y); } @Override public void onComplete() { } }); mZoomAnimation.setZoom(2.0f); mZoomAnimation.setZoomAnimationListener(new ZoomAnimationListener() { @Override public void onZoom(float scale, float x, float y) { if (scale <= mMaxScale && scale >= mMinScale) { handleScale(scale, x, y); } } @Override public void onComplete() { mInZoom = false; handleUp(); } }); moveAnimation.setMoveAnimationListener(new MoveAnimationListener() { @Override public void onMove(float x, float y) { image.setPosition(x, y); image.redraw(); } }); mTapDetector = new GestureDetector(image.getContext(), new SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { startZoom(e); return true; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { if (!mInZoom) { if (mOnClickListener != null) { mOnClickListener.onClick(image); return true; } } return false; } }); mFlingDetector = new GestureDetector(image.getContext(), mFlingListener); mImageListener = image.getGestureImageViewListener(); calculateBoundaries(); } private void startFling() { mFlingAnimation.setVelocityX(mFlingListener.getVelocityX()); mFlingAnimation.setVelocityY(mFlingListener.getVelocityY()); mImage.animationStart(mFlingAnimation); } private void startZoom(MotionEvent e) { mInZoom = true; mZoomAnimation.reset(); float zoomTo; if (mImage.isLandscape()) { if (mImage.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) { int scaledHeight = mImage.getScaledHeight(); if (scaledHeight < mCanvasHeight) { zoomTo = mFitScaleVertical / mCurrentScale; mZoomAnimation.setTouchX(e.getX()); mZoomAnimation.setTouchY(mImage.getCenterY()); } else { zoomTo = mFitScaleHorizontal / mCurrentScale; mZoomAnimation.setTouchX(mImage.getCenterX()); mZoomAnimation.setTouchY(mImage.getCenterY()); } } else { int scaledWidth = mImage.getScaledWidth(); if (scaledWidth == mCanvasWidth) { zoomTo = mCurrentScale * 4.0f; mZoomAnimation.setTouchX(e.getX()); mZoomAnimation.setTouchY(e.getY()); } else if (scaledWidth < mCanvasWidth) { zoomTo = mFitScaleHorizontal / mCurrentScale; mZoomAnimation.setTouchX(mImage.getCenterX()); mZoomAnimation.setTouchY(e.getY()); } else { zoomTo = mFitScaleHorizontal / mCurrentScale; mZoomAnimation.setTouchX(mImage.getCenterX()); mZoomAnimation.setTouchY(mImage.getCenterY()); } } } else { if (mImage.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) { int scaledHeight = mImage.getScaledHeight(); if (scaledHeight == mCanvasHeight) { zoomTo = mCurrentScale * 4.0f; mZoomAnimation.setTouchX(e.getX()); mZoomAnimation.setTouchY(e.getY()); } else if (scaledHeight < mCanvasHeight) { zoomTo = mFitScaleVertical / mCurrentScale; mZoomAnimation.setTouchX(e.getX()); mZoomAnimation.setTouchY(mImage.getCenterY()); } else { zoomTo = mFitScaleVertical / mCurrentScale; mZoomAnimation.setTouchX(mImage.getCenterX()); mZoomAnimation.setTouchY(mImage.getCenterY()); } } else { int scaledWidth = mImage.getScaledWidth(); if (scaledWidth < mCanvasWidth) { zoomTo = mFitScaleHorizontal / mCurrentScale; mZoomAnimation.setTouchX(mImage.getCenterX()); mZoomAnimation.setTouchY(e.getY()); } else { zoomTo = mFitScaleVertical / mCurrentScale; mZoomAnimation.setTouchX(mImage.getCenterX()); mZoomAnimation.setTouchY(mImage.getCenterY()); } } } mZoomAnimation.setZoom(zoomTo); mImage.animationStart(mZoomAnimation); } private void stopAnimations() { mImage.animationStop(); } @Override public boolean onTouch(View v, MotionEvent event) { if (!mInZoom) { if (!mTapDetector.onTouchEvent(event)) { if (event.getPointerCount() == 1 && mFlingDetector.onTouchEvent(event)) { startFling(); } if (event.getAction() == MotionEvent.ACTION_UP) { handleUp(); } else if (event.getAction() == MotionEvent.ACTION_DOWN) { stopAnimations(); mLast.x = event.getX(); mLast.y = event.getY(); if (mImageListener != null) { mImageListener.onTouch(mLast.x, mLast.y); } mTouched = true; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (event.getPointerCount() > 1) { mMultiTouch = true; if (mInitialDistance > 0) { mPinchVector.set(event); mPinchVector.calculateLength(); float distance = mPinchVector.length; if (mInitialDistance != distance) { float newScale = (distance / mInitialDistance) * mLastScale; if (newScale <= mMaxScale) { mScaleVector.length *= newScale; mScaleVector.calculateEndPoint(); mScaleVector.length /= newScale; float newX = mScaleVector.end.x; float newY = mScaleVector.end.y; handleScale(newScale, newX, newY); } } } else { mInitialDistance = MathUtils.distance(event); MathUtils.midpoint(event, mMidpoint); mScaleVector.setStart(mMidpoint); mScaleVector.setEnd(mNext); mScaleVector.calculateLength(); mScaleVector.calculateAngle(); mScaleVector.length /= mLastScale; } } else { if (!mTouched) { mTouched = true; mLast.x = event.getX(); mLast.y = event.getY(); mNext.x = mImage.getImageX(); mNext.y = mImage.getImageY(); } else if (!mMultiTouch) { if (handleDrag(event.getX(), event.getY())) { mImage.redraw(); } } } } } } return true; } void handleUp() { mMultiTouch = false; mInitialDistance = 0; mLastScale = mCurrentScale; if (!mCanDragX) { mNext.x = mCenterX; } if (!mCanDragY) { mNext.y = mCenterY; } boundCoordinates(); if (!mCanDragX && !mCanDragY) { if (mImage.isLandscape()) { mCurrentScale = mFitScaleHorizontal; mLastScale = mFitScaleHorizontal; } else { mCurrentScale = mFitScaleVertical; mLastScale = mFitScaleVertical; } } mImage.setScale(mCurrentScale); mImage.setPosition(mNext.x, mNext.y); if (mImageListener != null) { mImageListener.onScale(mCurrentScale); mImageListener.onPosition(mNext.x, mNext.y); } mImage.redraw(); } void handleScale(float scale, float x, float y) { mCurrentScale = scale; if (mCurrentScale > mMaxScale) { mCurrentScale = mMaxScale; } else if (mCurrentScale < mMinScale) { mCurrentScale = mMinScale; } else { mNext.x = x; mNext.y = y; } calculateBoundaries(); mImage.setScale(mCurrentScale); mImage.setPosition(mNext.x, mNext.y); if (mImageListener != null) { mImageListener.onScale(mCurrentScale); mImageListener.onPosition(mNext.x, mNext.y); } mImage.redraw(); } boolean handleDrag(float x, float y) { mCurrent.x = x; mCurrent.y = y; float diffX = (mCurrent.x - mLast.x); float diffY = (mCurrent.y - mLast.y); if (diffX != 0 || diffY != 0) { if (mCanDragX) mNext.x += diffX; if (mCanDragY) mNext.y += diffY; boundCoordinates(); mLast.x = mCurrent.x; mLast.y = mCurrent.y; if (mCanDragX || mCanDragY) { mImage.setPosition(mNext.x, mNext.y); if (mImageListener != null) { mImageListener.onPosition(mNext.x, mNext.y); } return true; } } return false; } public void reset() { mCurrentScale = mStartingScale; mNext.x = mCenterX; mNext.y = mCenterY; calculateBoundaries(); mImage.setScale(mCurrentScale); mImage.setPosition(mNext.x, mNext.y); mImage.redraw(); } public float getMaxScale() { return mMaxScale; } public void setMaxScale(float maxScale) { this.mMaxScale = maxScale; } public float getMinScale() { return mMinScale; } public void setMinScale(float minScale) { this.mMinScale = minScale; } public void setOnClickListener(OnClickListener onClickListener) { this.mOnClickListener = onClickListener; } void setCanvasWidth(int canvasWidth) { this.mCanvasWidth = canvasWidth; } void setCanvasHeight(int canvasHeight) { this.mCanvasHeight = canvasHeight; } void setFitScaleHorizontal(float fitScale) { this.mFitScaleHorizontal = fitScale; } void setFitScaleVertical(float fitScaleVertical) { this.mFitScaleVertical = fitScaleVertical; } void boundCoordinates() { if (mNext.x < mBoundaryLeft) { mNext.x = mBoundaryLeft; } else if (mNext.x > mBoundaryRight) { mNext.x = mBoundaryRight; } if (mNext.y < mBoundaryTop) { mNext.y = mBoundaryTop; } else if (mNext.y > mBoundaryBottom) { mNext.y = mBoundaryBottom; } } void calculateBoundaries() { int effectiveWidth = Math.round((float) mImageWidth * mCurrentScale); int effectiveHeight = Math.round((float) mImageHeight * mCurrentScale); mCanDragX = effectiveWidth > mDisplayWidth; mCanDragY = effectiveHeight > mDisplayHeight; if (mCanDragX) { float diff = (float) (effectiveWidth - mDisplayWidth) / 2.0f; mBoundaryLeft = mCenterX - diff; mBoundaryRight = mCenterX + diff; } if (mCanDragY) { float diff = (float) (effectiveHeight - mDisplayHeight) / 2.0f; mBoundaryTop = mCenterY - diff; mBoundaryBottom = mCenterY + diff; } } }