自定义ImageView实现图片的拖动、缩放和边界回弹
2016-09-12 23:25
447 查看
图片的缩放和拖动在上自定义View实现图片的拖动和缩放中已经提到,这里是紧承上文新添加边界回弹功能。
所谓边界回弹指的是类似微信修改头像截取图片时,如果将图片的边缘拉开了屏幕(View)边界,松手后有个回弹动作。
小小的边界回弹功能还让我折腾了好几天,起初用的是Scroller,但是使用过程中发现,如果在边界频繁向一个方向拖动,scrollX(Y)会朝着一个方向不断增大,这时候换个方向,只有不停拖动让这个scrollX(Y)归0之后才能正常拖动图片。
后来经过查询研究得知:使用Matrix实现图片的拖动和Scroller原理是不一样的!
使用Scroller辅助类最终还是得依靠View的scrollTo方法,scrollTo方法改变View的mScrollX和mScrollY值,然后经过invalidate(),在View的onDraw过程中通过这两个参数平移画布实现图片的移动。
使用Matrix平移图片不会涉及到mScrollX和mScrollY。
所以夹杂着Matrix的平移,使用scroller实现回弹很容易出问题,所以我最后换一种思路:使用Handler结合Matrix实现图片的边界回弹。
实现边界回弹的核心代码:
1.用于回弹的Handler
2.检查是否超出边界,得到回弹的dx、dy
3.监听触摸事件,实现回弹效果
完整代码:
所谓边界回弹指的是类似微信修改头像截取图片时,如果将图片的边缘拉开了屏幕(View)边界,松手后有个回弹动作。
小小的边界回弹功能还让我折腾了好几天,起初用的是Scroller,但是使用过程中发现,如果在边界频繁向一个方向拖动,scrollX(Y)会朝着一个方向不断增大,这时候换个方向,只有不停拖动让这个scrollX(Y)归0之后才能正常拖动图片。
后来经过查询研究得知:使用Matrix实现图片的拖动和Scroller原理是不一样的!
使用Scroller辅助类最终还是得依靠View的scrollTo方法,scrollTo方法改变View的mScrollX和mScrollY值,然后经过invalidate(),在View的onDraw过程中通过这两个参数平移画布实现图片的移动。
使用Matrix平移图片不会涉及到mScrollX和mScrollY。
所以夹杂着Matrix的平移,使用scroller实现回弹很容易出问题,所以我最后换一种思路:使用Handler结合Matrix实现图片的边界回弹。
实现边界回弹的核心代码:
1.用于回弹的Handler
private static final int SPRING_BACK = 0; private static final int COUNTS = 25; private static final int DELAY_MILLIS = 2; private int mCount; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case SPRING_BACK: { mCount++; if (mCount <= COUNTS) { // 使用浮点数,避免回弹后出现间隙 mMatrix.postTranslate(msg.arg1*1.0f/COUNTS, msg.arg2*1.0f/COUNTS); setImageMatrix(mMatrix); Message next = Message.obtain(mHandler,SPRING_BACK,msg.arg1,msg.arg2); mHandler.sendMessageDelayed(next, DELAY_MILLIS); } else { mCount = 0; } break; } default: break; } } };
2.检查是否超出边界,得到回弹的dx、dy
// 超出边界时修正dx、dy,计算回弹距离 private PointF amendDelta(float dx, float dy) { RectF rectF = getRectF(); if (rectF.width() > viewWidth) {// 图片宽度超过视图宽度 if (rectF.left + dx > 0) {// 拖动会引起图片左边出现空白 dx = -rectF.left; } else if (rectF.right + dx < viewWidth) {// 拖动会引起图片右边出现空白 dx = viewWidth - rectF.right; } } else {// 图片宽度不及视图宽度,不允许图片宽度方向可视区域离开边界 if (rectF.left + dx < 0) { dx = -rectF.left; } else if (rectF.right + dx > viewWidth) { dx = viewWidth - rectF.right; } } if (rectF.height() > viewHeight) { if (rectF.top + dy > 0) { dy = -rectF.top; } else if (rectF.bottom + dy < viewHeight) { dy = viewHeight - rectF.bottom; } } else { if (rectF.top + dy < 0) { dy = -rectF.top; } else if (rectF.bottom + dy > viewHeight) { dy = viewHeight - rectF.bottom; } } return new PointF(dx, dy); }
3.监听触摸事件,实现回弹效果
@Override public boolean onTouchEvent(View v, MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getActionMasked()) { ... case MotionEvent.ACTION_UP: // 消除缩放造成的图片距视图边的空白 final PointF delta = amendDelta(0, 0); int springBackX = Math.round(delta.x); int springBackY = Math.round(delta.y); if (springBackX != 0 || springBackY != 0) { Message msg = Message.obtain(mHandler,SPRING_BACK,springBackX,springBackY); mHandler.sendMessage(msg); } break; } ... }
完整代码:
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
/**
* Created by Kevin on 2016/9/5.
*
*/
public class ScalableImageView extends ImageView implements ScaleGestureDetector.OnScaleGestureListener, ViewTreeObserver.OnGlobalLayoutListener {
private static final String TAG = "ScalableImageView";
private int gesture;
private static final int GESTURE_DRAG = 1;
private static final int GESTURE_ZOOM = 2;
// 最大的缩放比例
private static final float MAX_SCALE = 4.0f;
// 初始化时的缩放比例,如果图片宽或高大于屏幕,此值将小于1(HongYang大神的博客上有笔误)
private float initScale = 1.0f;
private Matrix mMatrix = new Matrix();
private ScaleGestureDetector mScaleGestureDetector;
private int viewWidth;
private int viewHeight;
private Drawable mDrawable;
public ScalableImageView(Context context) {
this(context, null);
}
public ScalableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
// 这一步很关键
super.setScaleType(ScaleType.MATRIX);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
}
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
// 全局缩放比例
float scale = getScale();
// 上一次缩放事件到当前事件的缩放比例(微分缩放比例)
float factor = scaleGestureDetector.getScaleFactor();
// 第一次获取的factor偏小,会引起缩放手势触屏的一瞬间图片缩小
if (resetFactor) {
factor = 1.0f;
resetFactor = false;
}
// drawable为空或者缩放比例超出范围,拒执行
if (getDrawable() == null || scale * factor > MAX_SCALE || scale * factor < initScale) {
return false;
} else {
mMatrix.postScale(factor, factor, getWidth() / 2, getHeight() / 2);
setImageMatrix(mMatrix);
return true;
}
}
private final float[] matrixValues = new float[9];
// 获取全局缩放比例(相对于未缩放时的缩放比例),和直接用getScaleX()有区别!
private float getScale() {
mMatrix.getValues(matrixValues);
return matrixValues[Matrix.MSCALE_X];
}
// 缩放开始时:可用于过滤一些手势,比如从有效区以外的区域划进来的手势
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
return true;
}
// 缩放结束时
@Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
}
private float mLastX;
private float mLastY;
private boolean resetFactor;
private boolean onZoomFinished;
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
gesture = GESTURE_DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
gesture = GESTURE_ZOOM;
resetFactor = true;
break;
case MotionEvent.ACTION_POINTER_UP:
gesture = GESTURE_DRAG;
onZoomFinished = true;
break;
case MotionEvent.ACTION_MOVE:
switch (gesture) {
case GESTURE_DRAG:
// 屏蔽缩放转换为拖动时的跳动,此时dx、dy偏大。
if (onZoomFinished) {
onZoomFinished = false;
break;
}
int dx = (int) (x - mLastX);
int dy = (int) (y - mLastY);
mMatrix.postTranslate(dx, dy);
setImageMatrix(mMatrix);
break;
case GESTURE_ZOOM:
mScaleGestureDetector.onTouchEvent(event);
break;
}
// 不同于scrollTo或scrollBy移动,这两个是通过改变View的mScrollX和mScrollY,然后在onDraw时平移画布的,
// 而通过矩阵是对图片的操作,
// 这里不曾调用过scrollTo或scrollBy(从View的源码中可以看到这两个方法是用来改变mScrollX和mScrollY的),
// 所以打印日志可以看到getX()和getScrollX()一直不变。
// Log.e(TAG,"getX() = "+getX()+", getScrollX() = "+getScrollX());
break;
case MotionEvent.ACTION_UP:
// 消除缩放造成的图片距视图边的空白
final PointF delta = amendDelta(0, 0);
int springBackX = Math.round(delta.x);
int springBackY = Math.round(delta.y);
if (springBackX != 0 || springBackY != 0) {
Message msg = Message.obtain(mHandler,SPRING_BACK,springBackX,springBackY);
mHandler.sendMessage(msg);
}
break;
}
mLastX = x;
mLastY = y;
return true;
}
private static final int SPRING_BACK = 0; private static final int COUNTS = 25; private static final int DELAY_MILLIS = 2; private int mCount; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case SPRING_BACK: { mCount++; if (mCount <= COUNTS) { // 使用浮点数,避免回弹后出现间隙 mMatrix.postTranslate(msg.arg1*1.0f/COUNTS, msg.arg2*1.0f/COUNTS); setImageMatrix(mMatrix); Message next = Message.obtain(mHandler,SPRING_BACK,msg.arg1,msg.arg2); mHandler.sendMessageDelayed(next, DELAY_MILLIS); } else { mCount = 0; } break; } default: break; } } };
@Override
public void computeScroll() {
super.computeScroll();
}
private PointF amendDelta(float dx, float dy) {
RectF rectF = getRectF();
if (rectF.width() > viewWidth) {// 图片宽度超过视图宽度
if (rectF.left + dx > 0) {// 拖动会引起图片左边出现空白
dx = -rectF.left;
} else if (rectF.right + dx < viewWidth) {// 拖动会引起图片右边出现空白
dx = viewWidth - rectF.right;
}
} else {// 图片宽度不及视图宽度,不允许图片宽度方向可视区域离开边界
if (rectF.left + dx < 0) {
dx = -rectF.left;
} else if (rectF.right + dx > viewWidth) {
dx = viewWidth - rectF.right;
}
}
if (rectF.height() > viewHeight) {
if (rectF.top + dy > 0) {
dy = -rectF.top;
} else if (rectF.bottom + dy < viewHeight) {
dy = viewHeight - rectF.bottom;
}
} else {
if (rectF.top + dy < 0) {
dy = -rectF.top;
} else if (rectF.bottom + dy > viewHeight) {
dy = viewHeight - rectF.bottom;
}
}
return new PointF(dx, dy);
}
private RectF mRectF;
public RectF getRectF() {
if (mDrawable == null) {
mDrawable = getDrawable();
}
if (mRectF == null) {
mRectF = new RectF();
}
if (mDrawable != null) {
mRectF.set(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
mMatrix.mapRect(mRectF);
}
return mRectF;
}
// onGlobalLayoutListener可能会多次触发
private boolean isFirstTime = true;
// 观察布局变化,目的是获取View的尺寸,在测量之前执行onMeasure getWidth()和getHeight()可能为0
@Override
public void onGlobalLayout() {
if (isFirstTime) {
Drawable drawable = getDrawable();
if (drawable == null) {
return;
}
isFirstTime = false;
int drawableWidth = drawable.getIntrinsicWidth();
int drawableHeight = drawable.getIntrinsicHeight();
viewWidth = getWidth();
viewHeight = getHeight();
// 图片长宽超出View的可见范围的处理
if (drawableWidth > viewWidth || drawableHeight > viewHeight) {
initScale = Math.min(viewWidth * 1.0f / drawableWidth, viewHeight * 1.0f / drawableHeight);
}
// 将图片偏移到中心位置
mMatrix.postTranslate((viewWidth - drawableWidth) / 2, (viewHeight - drawableHeight) / 2);
// 初始化缩放
mMatrix.postScale(initScale, initScale, viewWidth / 2, viewHeight / 2);
setImageMatrix(mMatrix);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
getViewTreeObserver().addOnGlobalLayoutListener(this);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
}
相关文章推荐
- android 自定义ImageView实现图片缩放边界回弹和缩小回弹
- 自定义类继承ImageView 实现多点图片触碰的拖动和缩放
- Android通过自定义ImageView控件实现图片的缩放和拖动的实现代码
- 自定义ImageView实现单点缩放回弹、拖拽、多点缩放功能
- Android实现ImageView图片缩放和拖动
- 自定义View实现仿朋友圈的图片查看器,缩放、双击、移动、回弹、下滑退出及动画等
- Android自定义ImageView实现图片缩放滑动,双击放大缩小,多点触控缩放
- ImageView里面的图片实现同时移动缩放旋转 photoView自定义
- 自定义Imageview控件实现多种手势操作 (拖动、水平缩放、竖直缩放、等比例缩放、双击、长按)
- Android:自定义ImageView实现缩放,回弹效果
- 自定义View实现图片的拖动和缩放
- 自定义ImageView实现拖动、旋转、缩放功能
- 自定义类继承ImageView 实现多点图片触碰的拖动和缩放
- 自定义的ImageView控制,可对图片进行多点触控缩放和拖动
- Android自定义ImageView实现图片缩放滑动,双击放大缩小,多点触控旋转,圆角矩形,圆形和仿刮刮卡效果
- android-ImageView的拖动、旋转、缩放、边界回弹、双击缩放、单击销毁及源码下载
- Android开发之ImageView通过matrix实现两点缩放和图片拖动
- 自定义ImageView实现拖动,缩放,旋转功能
- 【Android】自定义ImageView实现图片的平移、缩放、旋转(手势操作)
- Android自定义imageview可对图片进行多点缩放和拖动