主要功能特点:
- 支持双指手势缩放图片,通过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(); // 启动动画}
}