android ImageView 手勢縮放,雙擊放大,自由滑動,多點觸控,慣性滑動
阿新 • • 發佈:2018-12-29
相容viewPager
import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Matrix; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.animation.AccelerateInterpolator; import android.widget.OverScroller; import android.widget.Scroller; public class ZoomImageView extends android.support.v7.widget.AppCompatImageView implements ViewTreeObserver.OnGlobalLayoutListener { private boolean mIsOneLoad = true; //初始化的比例,也就是最小比例 private float mInitScale; //圖片最大比例 private float mMaxScale; //雙擊能達到的最大比例 private float mMidScale; private Matrix mScaleMatrix; //捕獲使用者多點觸控 private ScaleGestureDetector mScaleGestureDetector; //移動 private GestureDetector gestureDetector; //雙擊 private boolean isEnlarge = false;//是否放大 private ValueAnimator mAnimator; //雙擊縮放動畫 //滾動 private OverScroller scroller; private int mCurrentX, mCurrentY; private ValueAnimator translationAnimation; //慣性移動動畫 public ZoomImageView(Context context) { this(context, null); } public ZoomImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //記住,一定要把ScaleType設定成ScaleType.MATRIX,否則無法縮放 setScaleType(ScaleType.MATRIX); scroller = new OverScroller(context); mScaleMatrix = new Matrix(); //手勢縮放 mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { @Override public boolean onScale(ScaleGestureDetector detector) { scale(detector); return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { scaleEnd(detector); } }); //滑動和雙擊監聽 gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, final float distanceX, final float distanceY) { //滑動監聽 onTranslationImage(-distanceX, -distanceY); return true; } @Override public boolean onDoubleTap(MotionEvent e) { //雙擊監聽 onDoubleDrowScale(e.getX(), e.getY()); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //滑動慣性處理 mCurrentX = (int) e2.getX(); mCurrentY = (int) e2.getY(); RectF rectF = getMatrixRectF(); if (rectF == null) { return false; } //startX為當前圖片左邊界的x座標 int startX = mCurrentX; int startY = mCurrentY; int minX = 0, maxX = 0, minY = 0, maxY = 0; int vX = Math.round(velocityX); int vY = Math.round(velocityY); maxX = Math.round(rectF.width()); maxY = Math.round(rectF.height()); if (startX != maxX || startY != maxY) { //呼叫fling方法,然後我們可以通過呼叫getCurX和getCurY來獲得當前的x和y座標 //這個座標的計算是模擬一個慣性滑動來計算出來的,我們根據這個x和y的變化可以模擬 //出圖片的慣性滑動 scroller.fling(startX, startY, vX, vY, 0, maxX, 0, maxY, maxX, maxY); } if (translationAnimation != null && translationAnimation.isStarted()) translationAnimation.end(); translationAnimation = ObjectAnimator.ofFloat(0, 1); translationAnimation.setDuration(500); translationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if (scroller.computeScrollOffset()) { //獲得當前的x座標 int newX = scroller.getCurrX(); int dx = newX - mCurrentX; mCurrentX = newX; //獲得當前的y座標 int newY = scroller.getCurrY(); int dy = newY - mCurrentY; mCurrentY = newY; //進行平移操作 if (dx != 0 && dy != 0) onTranslationImage(dx, dy); } } }); translationAnimation.start(); return super.onFling(e1, e2, velocityX, velocityY); } }); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getViewTreeObserver().removeOnGlobalLayoutListener(this); } /** * imageView載入完成後呼叫,獲取imageView載入完成後的圖片大小 */ @Override public void onGlobalLayout() { if (mIsOneLoad) { //得到控制元件的寬和高 int width = getWidth(); int height = getHeight(); //獲取圖片,如果沒有圖片則直接退出 Drawable d = getDrawable(); if (d == null) return; //獲取圖片的寬和高 int dw = d.getIntrinsicWidth(); int dh = d.getIntrinsicHeight(); float scale = 1.0f; if (dw > width && dh <= height) { scale = width * 1.0f / dw; } if (dw <= width && dh > height) { scale = height * 1.0f / dh; } if ((dw <= width && dh <= height) || (dw >= width && dh >= height)) { scale = Math.min(width * 1.0f / dw, height * 1.0f / dh); } //圖片原始比例,圖片回覆原始大小時使用 mInitScale = scale; //圖片雙擊後放大的比例 mMidScale = mInitScale * 2; //手勢放大時最大比例 mMaxScale = mInitScale * 4; //設定移動資料,把改變比例後的圖片移到中心點 float translationX = width * 1.0f / 2 - dw / 2; float translationY = height * 1.0f / 2 - dh / 2; mScaleMatrix.postTranslate(translationX, translationY); mScaleMatrix.postScale(mInitScale, mInitScale, width * 1.0f / 2, height * 1.0f / 2); setImageMatrix(mScaleMatrix); mIsOneLoad = false; } } @Override public boolean onTouchEvent(MotionEvent event) { mScaleGestureDetector.onTouchEvent(event); gestureDetector.onTouchEvent(event); return true; } //手勢操作(縮放) public void scale(ScaleGestureDetector detector) { Drawable drawable = getDrawable(); if (drawable == null) return; float scale = getScale(); //獲取手勢操作的值,scaleFactor>1說明放大,<1則說明縮小 float scaleFactor = detector.getScaleFactor(); //獲取手勢操作後的比例,當放操作後比例在[mInitScale,mMaxScale]區間時允許放大 mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); setImageMatrix(mScaleMatrix); removeBorderAndTranslationCenter(); } //手勢操作結束 public void scaleEnd(ScaleGestureDetector detector) { float scale = getScale(); scale = detector.getScaleFactor() * scale; if (scale < mInitScale) { scaleAnimation(mInitScale, getWidth() / 2, getHeight() / 2); } else if (scale > mMaxScale) { scaleAnimation(mMaxScale, getWidth() / 2, getHeight() / 2); } } //手勢操作(移動) private void onTranslationImage(float dx, float dy) { if (getDrawable() == null) return; RectF rect = getMatrixRectF(); //圖片寬度小於控制元件寬度時不允許左右移動 if (rect.width() <= getWidth()) dx = 0.0f; //圖片高度小於控制元件寬度時,不允許上下移動 if (rect.height() <= getHeight()) dy = 0.0f; //移動距離等於0,那就不需要移動了 if (dx == 0.0f && dy == 0.0f) return; mScaleMatrix.postTranslate(dx, dy); setImageMatrix(mScaleMatrix); //去除移動邊界 removeBorderAndTranslationCenter(); } //消除控制元件邊界和把圖片移動到中間 private void removeBorderAndTranslationCenter() { RectF rectF = getMatrixRectF(); if (rectF == null) return; int width = getWidth(); int height = getHeight(); float widthF = rectF.width(); float heightF = rectF.height(); float left = rectF.left; float right = rectF.right; float top = rectF.top; float bottom = rectF.bottom; float translationX = 0.0f, translationY = 0.0f; if (left > 0) { //左邊有邊界 if (widthF > width) { //圖片寬度大於控制元件寬度,移動到左邊貼邊 translationX = -left; } else { //圖片寬度小於控制元件寬度,移動到中間 translationX = width * 1.0f / 2f - (widthF * 1.0f / 2f + left); } } else if (right < width) { //右邊有邊界 if (widthF > width) { //圖片寬度大於控制元件寬度,移動到右邊貼邊 translationX = width - right; } else { //圖片寬度小於控制元件寬度,移動到中間 translationX = width * 1.0f / 2f - (widthF * 1.0f / 2f + left); } } if (top > 0) { //頂部有邊界 if (heightF > height) { //圖片高度大於控制元件高度,去除頂部邊界 translationY = -top; } else { //圖片高度小於控制元件寬度,移動到中間 translationY = height * 1.0f / 2f - (top + heightF * 1.0f / 2f); } } else if (bottom < height) { //底部有邊界 if (heightF > height) { //圖片高度大於控制元件高度,去除頂部邊界 translationY = height - bottom; } else { //圖片高度小於控制元件寬度,移動到中間 translationY = height * 1.0f / 2f - (top + heightF * 1.0f / 2f); } } mScaleMatrix.postTranslate(translationX, translationY); setImageMatrix(mScaleMatrix); } /** * 雙擊改變大小 * * @param x 點選的中心點 * @param y 點選的中心點 */ private void onDoubleDrowScale(float x, float y) { //如果縮放動畫已經在執行,那就不執行任何事件 if (mAnimator != null && mAnimator.isRunning()) return; float drowScale = getDoubleDrowScale(); //執行動畫縮放,不然太難看了 scaleAnimation(drowScale, x, y); } /** * 縮放動畫 * * @param drowScale 縮放的比例 * @param x 中心點 * @param y 中心點 */ private void scaleAnimation(final float drowScale, final float x, final float y) { if (mAnimator != null && mAnimator.isRunning()) return; mAnimator = ObjectAnimator.ofFloat(getScale(), drowScale); mAnimator.setDuration(300); mAnimator.setInterpolator(new AccelerateInterpolator()); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = ((float) animation.getAnimatedValue()) / getScale(); mScaleMatrix.postScale(value, value, x, y); setImageMatrix(mScaleMatrix); removeBorderAndTranslationCenter(); } }); mAnimator.start(); } //返回雙擊後改變的大小比例(我們希望縮放誤差在deviation範圍內) private float getDoubleDrowScale() { float deviation = 0.05f; float drowScale = 1.0f; float scale = getScale(); if (Math.abs(mInitScale - scale) < deviation) scale = mInitScale; if (Math.abs(mMidScale - scale) < deviation) scale = mMidScale; if (Math.abs(mMaxScale - scale) < deviation) scale = mMaxScale; if (scale != mMidScale) { //當前大小不等於mMidScale,則調整到mMidScale drowScale = mMidScale; isEnlarge = scale < mMidScale; } else { //如果等於mMidScale,則判斷放大或者縮小 //判斷是放大或者縮小,如果上次是放大,則繼續放大,縮小則繼續縮小 if (isEnlarge) { //放大 drowScale = mMaxScale; } else { //縮小 drowScale = mInitScale; } } return drowScale; } //獲取圖片寬高以及左右上下邊界 private RectF getMatrixRectF() { Drawable drawable = getDrawable(); if (drawable == null) { return null; } RectF rectF = new RectF(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight()); Matrix matrix = getImageMatrix(); matrix.mapRect(rectF); return rectF; } /** * 獲取當前圖片的縮放值 * * @return */ private float getScale() { float[] values = new float[9]; mScaleMatrix.getValues(values); return values[Matrix.MSCALE_X]; } /** * 解決和父控制元件滑動衝突 只要圖片邊界超過控制元件邊界,返回true * * @param direction * @return true 禁止父控制元件滑動 */ @Override public boolean canScrollHorizontally(int direction) { RectF rect = getMatrixRectF(); if (rect == null || rect.isEmpty()) return false; if (direction > 0) { return rect.right >= getWidth() + 1; } else { return rect.left <= 0 - 1; } } /** * 同樓上 * * @param direction * @return */ @Override public boolean canScrollVertically(int direction) { RectF rect = getMatrixRectF(); if (rect == null || rect.isEmpty()) return false; if (direction > 0) { return rect.bottom >= getHeight() + 1; } else { return rect.top <= 0 - 1; } } }