首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Android 自定義 ImageView 實現圖片縮放、平移、雙擊縮放與慣性滑動

     編輯:Java 相關知識

前言

在 Android 開發中,圖片查看功能是常見需求。本文將詳細介紹如何通過繼承 ImageView 並結合 Matrix、ScaleGestureDetector、GestureDetector 以及 Scroller 實現一個功能完整的圖片查看控件,支持雙指縮放、雙擊縮放、邊界檢查、自動回彈、慣性滑動等核心交互。

核心架構設計

自定義 ZoomImageView 實現以下接口:

  • ScaleGestureDetector.OnScaleGestureListener:處理雙指縮放手勢
  • View.OnTouchListener:處理觸摸事件(拖動、雙擊、單指滑動)
  • ViewTreeObserver.OnGlobalLayoutListener:在佈局完成後初始化圖片矩陣

關鍵屬性與初始化

public class ZoomImageView extends ImageView implements ... {
    private ScaleGestureDetector mScaleGestureDetector;
    private GestureDetector mGestureDetector;
    private Matrix mScaleMatrix;
    private boolean mFirst = true;
    private float mInitScale, mMaxScale, mMidScale, mMinScale, mMaxOverScale;
    private boolean isAutoScale;
    // 觸摸相關
    private int mLastPointerCount;
    private float mLastX, mLastY;
    private int mTouchSlop;
    private boolean isCanDrag, isCheckLeftAndRight, isCheckTopAndBottom;
    // 慣性滑動
    private VelocityTracker mVelocityTracker;
    private FlingRunnable mFlingRunnable;
    ...
}

佈局完成時的初始化邏輯

在 onGlobalLayout() 中完成圖片的初始縮放與居中:

@Override
public void onGlobalLayout() {
    if (!mFirst) return;
    mFirst = true;
    int width = getWidth(), height = getHeight();
    Drawable d = getDrawable();
    if (d == null) return;
    int dw = d.getIntrinsicWidth(), dh = d.getIntrinsicHeight();
    float scale = 1.0f;
    // 根據圖片與控件尺寸比例計算初始縮放比
    if (dw > width && dh < height) scale = width * 1.0f / dw;
    else if (dw < width && dh > height) scale = height * 1.0f / dh;
    else scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
    // 平移至中心
    int dx = width/2 - dw/2, dy = height/2 - dh/2;
    mScaleMatrix.postTranslate(dx, dy);
    mScaleMatrix.postScale(scale, scale, width/2, height/2);
    setImageMatrix(mScaleMatrix);
    // 設置縮放邊界值
    mInitScale = scale;
    mMaxScale = mInitScale * 4;
    mMidScale = mInitScale * 2;
    mMinScale = mInitScale / 4;
    mMaxOverScale = mMaxScale * 5;
}

雙指縮放處理

在 onScale() 中根據 scaleFactor 縮放,並限制在 [mMinScale, mMaxOverScale] 範圍內,縮放中心為兩指中點:

@Override
public boolean onScale(ScaleGestureDetector detector) {
    float scaleFactor = detector.getScaleFactor();
    float scale = getScale();
    if (getDrawable() == null) return true;
    if ((scaleFactor > 1.0f && scale * scaleFactor < mMaxOverScale) ||
        (scaleFactor < 1.0f && scale * scaleFactor > mMinScale)) {
        // 邊界修正
        if (scale * scaleFactor > mMaxScale) scaleFactor = mMaxScale / scale;
        if (scale * scaleFactor < mInitScale) scaleFactor = mInitScale / scale;
        mScaleMatrix.postScale(scaleFactor, scaleFactor,
                detector.getFocusX(), detector.getFocusY());
        checkBorderAndCenterWhenScale();
        setImageMatrix(mScaleMatrix);
    }
    return true;
}

雙擊縮放與自動縮放動畫

雙擊時根據當前縮放比決定放大至 mMidScale 或縮小至 mInitScale,通過 AutoScaleRunnable 實現漸變動畫:

private class AutoScaleRunnable implements Runnable {
    private float mTargetScale, tempScale, x, y;
    private final float BIGGER = 1.07f, SMALLER = 0.93f;
    public AutoScaleRunnable(float targetScale, float x, float y) { ... }
    @Override public void run() {
        mScaleMatrix.postScale(tempScale, tempScale, x, y);
        checkBorderAndCenterWhenScale();
        setImageMatrix(mScaleMatrix);
        float currentScale = getScale();
        if ((tempScale > 1.0f && currentScale < mTargetScale) ||
            (tempScale < 1.0f && currentScale > mTargetScale)) {
            postDelayed(this, 16);
        } else {
            // 修正最終比例
            float scale = mTargetScale / currentScale;
            mScaleMatrix.postScale(scale, scale, x, y);
            checkBorderAndCenterWhenScale();
            setImageMatrix(mScaleMatrix);
            isAutoScale = false;
        }
    }
}

拖動與邊界檢查

在 onTouch() 的 ACTION_MOVE 中計算位移 dx, dy,並根據圖片是否超出控件邊界決定是否允許水平/垂直拖動:

if (isCanDrag) {
    if (rectF.width() < getWidth()) { dx = 0; isCheckLeftAndRight = false; }
    if (rectF.height() < getHeight()) { dy = 0; isCheckTopAndBottom = false; }
    mScaleMatrix.postTranslate(dx, dy);
    checkBorderWhenTranslate();
    setImageMatrix(mScaleMatrix);
}

手指抬起時的自動回彈

在 ACTION_UP 中檢測當前縮放比,若小於 mInitScale 則彈回初始比例,大於 mMaxScale 則彈回最大比例:

case MotionEvent.ACTION_UP:
    mLastPointerCount = 0;
    if (getScale() < mInitScale) {
        post(new AutoScaleRunnable(mInitScale, getWidth()/2, getHeight()/2));
    } else if (getScale() > mMaxScale) {
        post(new AutoScaleRunnable(mMaxScale, getWidth()/2, getHeight()/2));
    }
    // 啟動慣性滑動...
    break;

慣性滑動實現

利用 VelocityTracker 獲取手指抬起瞬間的速度,配合 Scroller 在 FlingRunnable 中模擬慣性滑動:

private class FlingRunnable implements Runnable {
    private Scroller mScroller;
    private int mCurrentX, mCurrentY;
    public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
        RectF rectF = getMatrixRectF();
        // 計算滑動範圍 [minX, maxX], [minY, maxY]
        mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
    }
    @Override public void run() {
        if (mScroller.computeScrollOffset()) {
            int dx = mScroller.getCurrX() - mCurrentX;
            int dy = mScroller.getCurrY() - mCurrentY;
            mScaleMatrix.postTranslate(dx, dy);
            checkBorderWhenTranslate();
            setImageMatrix(mScaleMatrix);
            mCurrentX = mScroller.getCurrX();
            mCurrentY = mScroller.getCurrY();
            post(this);
        }
    }
}

總結

本文完整展示了一个生產級的圖片查看控件實現,涵蓋了手勢檢測、矩陣變換、邊界約束、動畫插值、慣性滑動等核心技術點。代碼結構清晰,可直接集成到項目中使用或根據業務需求擴展。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved