主要功能特點:
- 支持雙指手勢縮放圖片,通過ScaleGestureDetector實現平滑的縮放效果25
- 雙擊圖片可切換初始大小和中等放大比例16
- 使用Matrix進行圖像變換,保持縮放中心點為手勢焦點位置57
- 自動縮放動畫通過Runnable實現漸進式變化1
- 限制最小和最大縮放比例,防止過度縮放25
使用方式:
- 在布局文件中直接使用ZoomImageView替代普通ImageView
- 通過setImageResource()或setImageBitmap()設置圖片
- 可通過setInitScale()等方法自定義縮放參數
注意事項:
- 需要處理onGlobalLayout回調確保視圖完成初始化1
- 建議添加邊界檢查防止圖片縮放后超出視圖范圍68
- 如需添加拖動功能,需擴展onTouchEvent實現移動邏輯
/*** 支持手勢縮放的ImageView實現類* 功能:雙指縮放、雙擊放大/還原、自動居中、縮放比例限制*/
public class ZoomImageView extends AppCompatImageView implements ViewTreeObserver.OnGlobalLayoutListener, // 用于監聽視圖布局完成ScaleGestureDetector.OnScaleGestureListener, // 縮放手勢監聽View.OnTouchListener { // 觸摸事件監聽// 標志位:是否已初始化private boolean mOnce = false;// 初始縮放比例private float mInitScale;// 中等縮放比例(雙擊第一次)private float mMidScale;// 最大縮放比例private float mMaxScale;// 用于圖片變換的矩陣private Matrix mScaleMatrix;// 縮放手勢檢測器private ScaleGestureDetector mScaleDetector;// 手勢檢測器(用于雙擊檢測)private GestureDetector mGestureDetector;// 構造方法鏈public ZoomImageView(Context context) {this(context, null);}public ZoomImageView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public ZoomImageView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// 初始化變換矩陣mScaleMatrix = new Matrix();// 必須設置為MATRIX才能通過矩陣控制縮放setScaleType(ScaleType.MATRIX);// 初始化縮放手勢檢測器mScaleDetector = new ScaleGestureDetector(context, this);// 初始化手勢檢測器(主要用于雙擊檢測)mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {@Overridepublic boolean onDoubleTap(MotionEvent e) {// 雙擊事件處理:當前縮放小于中等比例則放大到中等比例,否則還原float scale = getScale();float targetScale = scale < mMidScale ? mMidScale : mInitScale;// 啟動自動縮放動畫post(new AutoScaleRunnable(targetScale, e.getX(), e.getY()));return true;}});// 設置觸摸監聽setOnTouchListener(this);}/*** 縮放手勢回調 - 縮放過程中持續觸發*/@Overridepublic boolean onScale(ScaleGestureDetector detector) {float scale = getScale();float scaleFactor = detector.getScaleFactor();// 縮放條件判斷:放大時不超過最大比例,縮小時不小于初始比例if ((scale < mMaxScale && scaleFactor > 1.0f) || (scale > mInitScale && scaleFactor < 1.0f)) {// 限制縮放因子范圍scaleFactor = Math.max(0.1f, Math.min(scaleFactor, 5.0f));// 以手勢焦點為中心進行縮放mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());// 應用變換矩陣setImageMatrix(mScaleMatrix);}return true;}/*** 觸摸事件處理*/@Overridepublic boolean onTouch(View v, MotionEvent event) {// 將觸摸事件傳遞給手勢檢測器mScaleDetector.onTouchEvent(event);mGestureDetector.onTouchEvent(event);return true;}/*** 自動縮放動畫Runnable*/private class AutoScaleRunnable implements Runnable {private float mTargetScale; // 目標縮放比例private float x, y; // 縮放中心點坐標private final float BIGGER = 1.07f; // 放大系數private final float SMALLER = 0.93f; // 縮小系數public AutoScaleRunnable(float targetScale, float x, float y) {this.mTargetScale = targetScale;this.x = x;this.y = y;}@Overridepublic void run() {float currentScale = getScale();// 判斷應該放大還是縮小float tmpScale = currentScale < mTargetScale ? BIGGER : SMALLER;// 執行縮放變換mScaleMatrix.postScale(tmpScale, tmpScale, x, y);setImageMatrix(mScaleMatrix);// 判斷是否需要繼續動畫if ((tmpScale > 1f && currentScale < mTargetScale) || (tmpScale < 1f && currentScale > mTargetScale)) {// 16ms后繼續執行(約60FPS)postDelayed(this, 16);}}}
}
代碼功能說明:
- 核心功能:通過Matrix實現圖片的縮放變換,支持雙指手勢縮放和雙擊切換
- 手勢檢測:使用ScaleGestureDetector處理捏合手勢,GestureDetector處理雙擊事件
- 比例控制:限制最小/最大縮放比例,防止圖片過度縮放
- 動畫效果:通過Runnable實現平滑的自動縮放動畫
- 交互優化:縮放中心始終為手勢焦點位置,提升用戶體驗
在Android開發中,我做過最復雜的自定義View是一個支持多手勢操作的圖片瀏覽器,主要實現了以下高級功能:
- 核心難點實現:
- 多層級手勢沖突處理(雙指縮放+單指拖動+雙擊+長按)
- 基于Matrix的精準圖像變換控制
- 邊緣回彈效果和慣性滑動
- 動態加載大圖的分塊顯示
- 自定義View開發要點:
(1)繪制流程
- 測量(onMeasure)→布局(onLayout)→繪制(onDraw)
- 處理wrap_content和padding
- 優化invalidate()的調用范圍
(2)事件處理
- 使用GestureDetector處理單擊/雙擊/長按
- 通過ScaleGestureDetector實現雙指縮放
- VelocityTracker計算滑動速度
- 自定義事件分發邏輯解決手勢沖突
(3)性能優化
- 使用ValueAnimator實現平滑動畫
- 通過Canvas.clipRect()限制繪制區域
- 大圖采用BitmapRegionDecoder分塊加載
- 使用View.post()保證線程安全
- 典型問題解決方案:
- 縮放中心點計算:通過Matrix映射坐標
- 邊界檢測:計算圖像變換后的位置矩陣
- 內存優化:及時recycle()不再使用的Bitmap
- 過渡繪制:關閉硬件加速時單獨處理
這個自定義View最終實現了類似微信圖片瀏覽器的完整交互:
- 支持雙指自由縮放(帶慣性效果)
- 雙擊智能縮放(自動適配屏幕/原始尺寸)
- 拖動時邊緣回彈
- 長按彈出操作菜單
- 支持超高清圖片的流暢瀏覽
開發過程中最大的挑戰是處理各種手勢的優先級沖突,最終通過狀態機模式管理不同交互狀態,并引入手勢閾值判定機制來完美解決。
在Android開發中,我做過最復雜的自定義View是一個支持多手勢操作的圖片瀏覽器,主要實現了以下高級功能:
- 核心難點實現:
- 多層級手勢沖突處理(雙指縮放+單指拖動+雙擊+長按)
- 基于Matrix的精準圖像變換控制
- 邊緣回彈效果和慣性滑動
- 動態加載大圖的分塊顯示
- 自定義View開發要點:
(1)繪制流程
- 測量(onMeasure)→布局(onLayout)→繪制(onDraw)
- 處理wrap_content和padding
- 優化invalidate()的調用范圍
(2)事件處理
- 使用GestureDetector處理單擊/雙擊/長按
- 通過ScaleGestureDetector實現雙指縮放
- VelocityTracker計算滑動速度
- 自定義事件分發邏輯解決手勢沖突
(3)性能優化
- 使用ValueAnimator實現平滑動畫
- 通過Canvas.clipRect()限制繪制區域
- 大圖采用BitmapRegionDecoder分塊加載
- 使用View.post()保證線程安全
- 典型問題解決方案:
- 縮放中心點計算:通過Matrix映射坐標
- 邊界檢測:計算圖像變換后的位置矩陣
- 內存優化:及時recycle()不再使用的Bitmap
- 過渡繪制:關閉硬件加速時單獨處理
這個自定義View最終實現了類似微信圖片瀏覽器的完整交互:
- 支持雙指自由縮放(帶慣性效果)
- 雙擊智能縮放(自動適配屏幕/原始尺寸)
- 拖動時邊緣回彈
- 長按彈出操作菜單
- 支持超高清圖片的流暢瀏覽
開發過程中最大的挑戰是處理各種手勢的優先級沖突,最終通過狀態機模式管理不同交互狀態,并引入手勢閾值判定機制來完美解決。
以下是一個完整的Android自定義View示例,實現帶進度動畫的圓形進度條:
CircleProgressView.java
該自定義View主要實現以下功能特點:
- 繼承View基類并實現三種構造方法5
- 在onMeasure()中處理View的尺寸測量邏輯3
- 通過Paint和Canvas在onDraw()中完成圓形進度條的繪制6
- 使用ValueAnimator實現進度變化的平滑動畫4
- 支持通過setProgress()方法動態更新進度值1
使用方式:
- 在XML布局中添加:
xmlCopy Code
<com.example.CircleProgressView android:layout_width="200dp" android:layout_height="200dp"/>
- 在代碼中控制進度:
javaCopy Code
progressView.setProgress(75); // 設置75%進度
如需添加自定義屬性(如進度條顏色/寬度等),可參考以下擴展:
- 在res/values/attrs.xml定義屬性7
- 在構造方法中解析屬性值4
- 添加屬性設置方法實現動態修改
/*** 自定義圓形進度條View* 功能特點:* 1. 支持動態設置進度值* 2. 內置平滑過渡動畫* 3. 可自定義進度條樣式*/
public class CircleProgressView extends View {// 背景圓畫筆private Paint mBackgroundPaint;// 進度條畫筆private Paint mProgressPaint;// 繪制弧形的矩形區域private RectF mArcRect = new RectF();// 當前進度值(0-100)private float mCurrentProgress = 0;// 進度動畫控制器private ValueAnimator mProgressAnimator;// 構造方法1:代碼創建View時調用public CircleProgressView(Context context) {this(context, null);}// 構造方法2:XML布局中聲明時調用public CircleProgressView(Context context, AttributeSet attrs) {super(context, attrs);initPaints(); // 初始化畫筆}/*** 初始化畫筆配置*/private void initPaints() {// 背景圓畫筆配置mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mBackgroundPaint.setColor(Color.LTGRAY); // 默認灰色背景mBackgroundPaint.setStyle(Paint.Style.STROKE); // 空心樣式mBackgroundPaint.setStrokeWidth(20); // 線條寬度// 進度條畫筆配置mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mProgressPaint.setColor(Color.BLUE); // 默認藍色進度mProgressPaint.setStyle(Paint.Style.STROKE); // 空心樣式mProgressPaint.setStrokeWidth(20); // 線條寬度mProgressPaint.setStrokeCap(Paint.Cap.ROUND); // 圓角線帽}/*** 測量View尺寸* 確保View始終是正方形*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 取寬高最小值作為正方形邊長int size = Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));setMeasuredDimension(size, size); // 設置最終測量尺寸}/*** 繪制View內容*/@Overrideprotected void onDraw(Canvas canvas) {// 計算圓心坐標和半徑float center = getWidth() / 2f;float radius = center - mProgressPaint.getStrokeWidth();// 設置繪制弧形的矩形區域mArcRect.set(center - radius, center - radius, center + radius, center + radius);// 繪制背景圓canvas.drawCircle(center, center, radius, mBackgroundPaint);// 繪制進度弧線(從-90度開始,順時針繪制)canvas.drawArc(mArcRect, -90, mCurrentProgress * 3.6f, false, mProgressPaint);}/*** 設置進度值(帶動畫效果)* @param progress 目標進度值(0-100)*/public void setProgress(float progress) {// 取消之前的動畫(如果存在)if (mProgressAnimator != null) {mProgressAnimator.cancel();}// 創建屬性動畫(從當前進度到目標進度)mProgressAnimator = ValueAnimator.ofFloat(mCurrentProgress, progress);mProgressAnimator.setDuration(800); // 動畫時長800ms// 動畫更新監聽器mProgressAnimator.addUpdateListener(animation -> {mCurrentProgress = (float) animation.getAnimatedValue();invalidate(); // 觸發重繪});mProgressAnimator.start(); // 啟動動畫}
}