自定义ImageView实现图片的拖动、缩放和边界回弹
来源:互联网 发布:护肤品使用手法 知乎 编辑:程序博客网 时间:2024/05/02 02:52
图片的缩放和拖动在上自定义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
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.监听触摸事件,实现回弹效果
@Overridepublic 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); } }}
0 0
- 自定义ImageView实现图片的拖动、缩放和边界回弹
- android 自定义ImageView实现图片缩放边界回弹和缩小回弹
- android-ImageView的拖动、旋转、缩放、边界回弹、双击缩放、单击销毁及源码下载
- 让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅
- 让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅
- 让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅 .
- 让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅
- 让Android自带的Gallery实现多点缩放,拖动和边界回弹效果,效果流畅
- 自定义类继承ImageView 实现多点图片触碰的拖动和缩放
- 自定义ImageView实现图片的单指拖动和两指缩放
- 自定义类继承ImageView 实现多点图片触碰的拖动和缩放
- 自定义imageview,实现多点缩放回弹
- Android:自定义ImageView实现缩放,回弹效果
- 自定义ImageView实现单点缩放回弹、拖拽、多点缩放功能
- 捏合与拖动手势实现视图的缩放拖动 以及 任一边界拖进屏幕自动回弹
- 自定义View实现图片的拖动和缩放
- 自定义View实现图片的拖动和缩放
- 自定义的ImageView控制,可对图片进行多点触控缩放和拖动
- 数据结构----BFS和DFS详解
- 图像检索
- Activity之间传递参数
- POJ 1442 Black Box [Treap]
- gethostbyname函数用域名或主机名获取IP地址
- 自定义ImageView实现图片的拖动、缩放和边界回弹
- Nodejs定时器
- stm8s开发(四) CLOCK的使用:时钟控制!
- 离线记录+树状数组(hdu 5869 统计任意区间的不同gcd值)
- Struts2自学入门(一)
- js计时器
- Winform中来自web接口的Json解析——反序列化
- 2016ACM-ICPC大连网络预选赛1001 Different Circle Permutation
- Postgresql 常见数据库操作命令记录