我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android自定义GestureDetector实现手势ImageView

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android自定义GestureDetector实现手势ImageView

不说废话了,进入我们今天的主题吧。

先贴上前面内容的地址:

Android手势ImageView三部曲(一)

Android手势ImageView三部曲(二)

Android手势ImageView三部曲(三)

前面我们讲到了ScaleGestureDetector这个工具类,我在疑惑,为什么搞出一个ScaleGestureDetector,不顺带把什么旋转、移动、做了呢? 好吧~! 谷歌肯定还是想给开发者留一点自己的空间哈。

仿照ScaleGestureDetector,我们来定义一个叫MoveGestureDetector的工具类(专门用于检测滑动手势),在定义MoveGestureDetector之前,因为我们还要考虑到之后的RotateGestureDetector等等..于是我们定一个叫BaseGestureDetector把一些公共的方法抽取出来:


public abstract class BaseGestureDetector {
 protected final Context mContext;
 protected boolean mGestureInProgress;
 protected MotionEvent mPrevEvent;
 protected MotionEvent mCurrEvent;
 protected float mCurrPressure;
 protected float mPrevPressure;
 protected long mTimeDelta;
 
 protected static final float PRESSURE_THRESHOLD = 0.67f;
 public BaseGestureDetector(Context context) {
 mContext = context; 
 }
 
 public boolean onTouchEvent(MotionEvent event){
 //为了获取到ACTION_POINTER_UP等事件必须加上& MotionEvent.ACTION_MASK
 final int actionCode = event.getAction() & MotionEvent.ACTION_MASK;
 
 if (!mGestureInProgress) {
 //如果mGestureInProgress为false的时候,执行开始操作
 handleStartProgressEvent(actionCode, event);
 } else {
 //处理手势
 handleInProgressEvent(actionCode, event);
 }
 return true;
 }
 
 protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event);
 
 protected abstract void handleInProgressEvent(int actionCode, MotionEvent event);
 
 protected void updateStateByEvent(MotionEvent curr){
 final MotionEvent prev = mPrevEvent;
 // Reset mCurrEvent
 if (mCurrEvent != null) {
 mCurrEvent.recycle();
 mCurrEvent = null;
 }
 mCurrEvent = MotionEvent.obtain(curr);
 // 之前的event跟现在的event之间的时间差
 mTimeDelta = curr.getEventTime() - prev.getEventTime();
 // 之前的event跟腺癌的event之间的手指压力值
 mCurrPressure = curr.getPressure(curr.getActionIndex());
 mPrevPressure = prev.getPressure(prev.getActionIndex());
 }
 
 protected void resetState() {
 if (mPrevEvent != null) {
 mPrevEvent.recycle();
 mPrevEvent = null;
 }
 if (mCurrEvent != null) {
 mCurrEvent.recycle();
 mCurrEvent = null;
 }
 mGestureInProgress = false;
 }
 
 public boolean isInProgress() {
 return mGestureInProgress;
 }
 
 public long getTimeDelta() {
 return mTimeDelta;
 }
 
 public long getEventTime() {
 return mCurrEvent.getEventTime();
 }
}

然后我们定义一个叫MoveGestureDetector的类去继承BaseGestureDetector,然后事件两个抽象方法:


public class MoveGestureDetector extends BaseGestureDetector{
 @Override
 protected void handleStartProgressEvent(int actionCode, MotionEvent event){
 }
 @Override
 protected void handleInProgressEvent(int actionCode, MotionEvent event){ 
 }
}

那我们如果检测到了事件的话该怎么通知调用者呢?是的,我们需要用到回调,我们看看ScaleGestureDetector的回调接口咋定义的:


public interface OnScaleGestureListener {
 public boolean onScale(ScaleGestureDetector detector);
 public boolean onScaleBegin(ScaleGestureDetector detector);
 public void onScaleEnd(ScaleGestureDetector detector);
 }
 public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
 public boolean onScale(ScaleGestureDetector detector) {
 return false;
 }
 public boolean onScaleBegin(ScaleGestureDetector detector) {
 return true;
 }
 public void onScaleEnd(ScaleGestureDetector detector) {
 // Intentionally empty
 }
 }

里面定义了一个接口一个叫OnScaleGestureListener,一个类叫SimpleOnScaleGestureListener,SimpleOnScaleGestureListener是实现了OnScaleGestureListener,于是我们MoveGestureDetector的接口可以这么定义了:



 public interface OnMoveGestureListener {
 
 public boolean onMove(MoveGestureDetector detector);
 
 public boolean onMoveBegin(MoveGestureDetector detector);
 
 public void onMoveEnd(MoveGestureDetector detector);
 }
 public static class SimpleOnMoveGestureListener implements OnMoveGestureListener {
 public boolean onMove(MoveGestureDetector detector) {
 return false;
 }
 public boolean onMoveBegin(MoveGestureDetector detector) {
 return true;
 }
 public void onMoveEnd(MoveGestureDetector detector) {
 // Do nothing, overridden implementation may be used
 }
 }

好啦!框子都搭好了,我们用的时候呢,就可以这么用了:

1、创建一个MoveGestureDetector


 public MatrixImageView(Context context, AttributeSet attrs) {
 super(context, attrs);
 initView();
 //创建一个缩放手势监测器
 scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener);
 //创建一个MoveGestureDetector
 moveGestureDetector=new MoveGestureDetector(context,onMoveGestureListener);
 }

2、把事件给MoveGestureDetector


 @Override
 public boolean onTouchEvent(MotionEvent event) {
 //把事件给scaleDetector
 scaleDetector.onTouchEvent(event);
 //把事件给moveGestureDetector
 moveGestureDetector.onTouchEvent(event);
 return true;
 }

3、获取回调值


private MoveGestureDetector.SimpleOnMoveGestureListener onMoveGestureListener=new MoveGestureDetector.SimpleOnMoveGestureListener(){
 @Override
 public boolean onMove(MoveGestureDetector detector) {
 return super.onMove(detector);
 }
 };

怎么样?是不是跟ScaleGestureDetector一样了呢?清晰明了哈,框子是搭起来了,下面我们来实现下它的逻辑(也就是实现下handleStartProgressEvent跟handleInProgressEvent方法):

每行都有注释,我就直接上代码了


 */
public class MoveGestureDetector extends BaseGestureDetector {
 
 public interface OnMoveGestureListener {
 
 public boolean onMove(MoveGestureDetector detector);
 
 public boolean onMoveBegin(MoveGestureDetector detector);
 
 public void onMoveEnd(MoveGestureDetector detector);
 }
 public static class SimpleOnMoveGestureListener implements OnMoveGestureListener {
 public boolean onMove(MoveGestureDetector detector) {
 return false;
 }
 public boolean onMoveBegin(MoveGestureDetector detector) {
 return true;
 }
 public void onMoveEnd(MoveGestureDetector detector) {
 // Do nothing, overridden implementation may be used
 }
 }
 private static final PointF FOCUS_DELTA_ZERO = new PointF();
 private final OnMoveGestureListener mListener;
 private PointF mCurrFocusInternal;
 private PointF mPrevFocusInternal; 
 private PointF mFocusExternal = new PointF();
 private PointF mFocusDeltaExternal = new PointF();
 public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
 super(context);
 mListener = listener;
 }
 @Override
 protected void handleStartProgressEvent(int actionCode, MotionEvent event){
 switch (actionCode) {
 //当手指按下的时候
 case MotionEvent.ACTION_DOWN:
 //重置一下所有状态(currevent跟preevent)
 resetState(); // In case we missed an UP/CANCEL event
 //获取当前event作为mPrevEvent
 mPrevEvent = MotionEvent.obtain(event);
 //重置两次event的时间间隔
 mTimeDelta = 0;
 //更新state
 updateStateByEvent(event);
 break;
 case MotionEvent.ACTION_MOVE:
 //回调onMoveBegin,mGestureInProgress决定是否继续处理事件(执行handleInProgressEvent)
 //mGestureInProgress由调用者决定
 mGestureInProgress = mListener.onMoveBegin(this);
 break;
 }
 }
 
 @Override
 protected void handleInProgressEvent(int actionCode, MotionEvent event){ 
 switch (actionCode) {
 //当抬起或者取消的时候
 case MotionEvent.ACTION_UP:
 case MotionEvent.ACTION_CANCEL:
 //回调onMoveEnd,move处理结束
 mListener.onMoveEnd(this);
 //重置所有的state
 resetState();
 break;
 case MotionEvent.ACTION_MOVE:
 //更新状态
 updateStateByEvent(event);
 //当上一次event的press值/这一次event值大于临界值的时候开始触发onMove
 //因为如果CurrPressure / mPrevPressure很小的话,可能手指已经离开屏幕了
 if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
 
 final boolean updatePrevious = mListener.onMove(this);
 if (updatePrevious) {
 mPrevEvent.recycle();
 mPrevEvent = MotionEvent.obtain(event);
 }
 }
 break;
 }
 }
 
 protected void updateStateByEvent(MotionEvent curr) {
 super.updateStateByEvent(curr);
 final MotionEvent prev = mPrevEvent;
 // 获取当前所有手指的中心点
 mCurrFocusInternal = determineFocalPoint(curr);
 //获取之前event所有手指的中心点
 mPrevFocusInternal = determineFocalPoint(prev);
 //判断是否有手指中途添加或者移除
 boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount();
 //有移除的话mFocusDeltaExternal就等于空(0,0),没有的话就算出前面event跟当前event中心点距离
 mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x, mCurrFocusInternal.y - mPrevFocusInternal.y);
 //累加距离值
 mFocusExternal.x += mFocusDeltaExternal.x;
 mFocusExternal.y += mFocusDeltaExternal.y; 
 }
 
 private PointF determineFocalPoint(MotionEvent e){
 // Number of fingers on screen
 final int pCount = e.getPointerCount(); 
 float x = 0f;
 float y = 0f;
 for(int i = 0; i < pCount; i++){
 x += e.getX(i);
 y += e.getY(i);
 }
 return new PointF(x/pCount, y/pCount);
 }
 
 public float getFocusX() {
 return mFocusExternal.x;
 }
 public float getFocusY() {
 return mFocusExternal.y;
 }
 
 public PointF getFocusDelta() {
 return mFocusDeltaExternal;
 }
}

好啦!!写完哈,我们来使用一下:


package com.leo.gestureimageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
import com.leo.gestureimageview.GestureDetectors.MoveGestureDetector;
public class MatrixImageView extends ImageView {
 private Matrix currMatrix;
 private float scaleFactor=1f;//当前图片的缩放值
 private float transX,transY;
 private ScaleGestureDetector scaleDetector;
 private MoveGestureDetector moveGestureDetector;
 public MatrixImageView(Context context, AttributeSet attrs) {
 super(context, attrs);
 initView();
 //创建一个缩放手势监测器
 scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener);
 //创建一个MoveGestureDetector
 moveGestureDetector=new MoveGestureDetector(context,onMoveGestureListener);
 }
 private void initView() {
 currMatrix = new Matrix();
 DisplayMetrics dm = getResources().getDisplayMetrics();
 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
 bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
 setImageBitmap(bitmap);
 }
 @Override
 public boolean onTouchEvent(MotionEvent event) {
 //把事件给scaleDetector
 scaleDetector.onTouchEvent(event);
 //把事件给moveGestureDetector
 moveGestureDetector.onTouchEvent(event);
 return true;
 }
 private void setMatrix(){
 currMatrix.reset();
 currMatrix.postScale(scaleFactor,scaleFactor,getMeasuredWidth()/2,getMeasuredHeight()/2);
 currMatrix.postTranslate(transX,transY);
 setImageMatrix(currMatrix);
 }
 private ScaleGestureDetector.SimpleOnScaleGestureListener onScaleGestureListener=new ScaleGestureDetector.SimpleOnScaleGestureListener(){
 @Override
 public boolean onScale(ScaleGestureDetector detector) {
 scaleFactor *= detector.getScaleFactor(); // scale change since previous event
 // Don't let the object get too small or too large.
 scaleFactor = Math.max(0.1f, Math.min(scaleFactor, 10.0f));
 setMatrix();
 
 return true;
 }
 };
 private MoveGestureDetector.SimpleOnMoveGestureListener onMoveGestureListener=new MoveGestureDetector.SimpleOnMoveGestureListener(){
 @Override
 public boolean onMove(MoveGestureDetector detector) {
 transX=detector.getFocusX();
 transY=detector.getFocusY();
 setMatrix();
 return true;
 }
 };
}

好啦~!! 短短几行代码就可以玩起来了,效果图我就不附了哈,小伙伴自己运行一下,那么MoveGestureDetector我们实现了,想必RotateGestureDetector也是很快就会实现了,哈哈~~! 我就直接用贴上国外大神写的代码了:


public class RotateGestureDetector extends TwoFingerGestureDetector {
 
 public interface OnRotateGestureListener {
 public boolean onRotate(RotateGestureDetector detector);
 public boolean onRotateBegin(RotateGestureDetector detector);
 public void onRotateEnd(RotateGestureDetector detector);
 }
 
 public static class SimpleOnRotateGestureListener implements OnRotateGestureListener {
 public boolean onRotate(RotateGestureDetector detector) {
 return false;
 }
 public boolean onRotateBegin(RotateGestureDetector detector) {
 return true;
 }
 public void onRotateEnd(RotateGestureDetector detector) {
 // Do nothing, overridden implementation may be used
 }
 }
 private final OnRotateGestureListener mListener;
 private boolean mSloppyGesture;
 public RotateGestureDetector(Context context, OnRotateGestureListener listener) {
 super(context);
 mListener = listener;
 }
 @Override
 protected void handleStartProgressEvent(int actionCode, MotionEvent event){
 switch (actionCode) {
 case MotionEvent.ACTION_POINTER_DOWN:
 // At least the second finger is on screen now
 resetState(); // In case we missed an UP/CANCEL event
 mPrevEvent = MotionEvent.obtain(event);
 mTimeDelta = 0;
 updateStateByEvent(event);
 // See if we have a sloppy gesture
 mSloppyGesture = isSloppyGesture(event);
 if(!mSloppyGesture){
 // No, start gesture now
 mGestureInProgress = mListener.onRotateBegin(this);
 } 
 break;
 case MotionEvent.ACTION_MOVE:
 if (!mSloppyGesture) {
 break;
 }
 // See if we still have a sloppy gesture
 mSloppyGesture = isSloppyGesture(event);
 if(!mSloppyGesture){
 // No, start normal gesture now
 mGestureInProgress = mListener.onRotateBegin(this);
 }
 break;
 case MotionEvent.ACTION_POINTER_UP:
 if (!mSloppyGesture) {
 break;
 }
 break; 
 }
 }
 @Override
 protected void handleInProgressEvent(int actionCode, MotionEvent event){ 
 switch (actionCode) {
 case MotionEvent.ACTION_POINTER_UP:
 // Gesture ended but 
 updateStateByEvent(event);
 if (!mSloppyGesture) {
 mListener.onRotateEnd(this);
 }
 resetState();
 break;
 case MotionEvent.ACTION_CANCEL:
 if (!mSloppyGesture) {
 mListener.onRotateEnd(this);
 }
 resetState();
 break;
 case MotionEvent.ACTION_MOVE:
 updateStateByEvent(event);
 // Only accept the event if our relative pressure is within
 // a certain limit. This can help filter shaky data as a
 // finger is lifted.
 if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
 final boolean updatePrevious = mListener.onRotate(this);
 if (updatePrevious) {
 mPrevEvent.recycle();
 mPrevEvent = MotionEvent.obtain(event);
 }
 }
 break;
 }
 }
 @Override
 protected void resetState() {
 super.resetState();
 mSloppyGesture = false;
 }
 
 public float getRotationDegreesDelta() {
 double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX) - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX);
 return (float) (diffRadians * 180 / Math.PI);
 }
}

最后把我们结合了ScaleDetector、MoveDetector、RotateDetector的一个手势缩放ImageView的代码给大家:


package com.leo.gestureimageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
import com.leo.gestureimageview.GestureDetectors.MoveGestureDetector;
import com.leo.gestureimageview.GestureDetectors.RotateGestureDetector;
public class MatrixImageView2 extends ImageView {
 private Matrix mMatrix = new Matrix();
 private float mScaleFactor =1f;
 private float mRotationDegrees = 0.f;
 private float mFocusX = 0.f;
 private float mFocusY = 0.f;
 private ScaleGestureDetector mScaleDetector;
 private RotateGestureDetector mRotateDetector;
 private MoveGestureDetector mMoveDetector;
 public MatrixImageView2(Context context, AttributeSet attrs) {
 super(context, attrs);
 initView();
 }
 private void initView() {
 //初始化模式为初始状态
 DisplayMetrics dm = getResources().getDisplayMetrics();
 //给ImageView设置一张图片(此处为了测试直接在imageview里面设置了一张测试图片)
 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
 bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
 setImageBitmap(bitmap);
 mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
 mRotateDetector = new RotateGestureDetector(getContext(), new RotateListener());
 mMoveDetector = new MoveGestureDetector(getContext(), new MoveListener());
 mFocusX = dm.widthPixels/2f;
 mFocusY = dm.heightPixels/2f;
 }
 @Override
 public boolean onTouchEvent(MotionEvent event) {
 //把缩放事件给mScaleDetector
 mScaleDetector.onTouchEvent(event);
 //把旋转事件个mRotateDetector
 mRotateDetector.onTouchEvent(event);
 //把移动事件给mMoveDetector
 mMoveDetector.onTouchEvent(event);
 return true;
 }
 private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
 @Override
 public boolean onScale(ScaleGestureDetector detector) {
 mScaleFactor *= detector.getScaleFactor(); // scale change since previous event
 // Don't let the object get too small or too large.
 mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
 changeMatrix();
 return true;
 }
 }
 private class RotateListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
 @Override
 public boolean onRotate(RotateGestureDetector detector) {
 mRotationDegrees -= detector.getRotationDegreesDelta();
 changeMatrix();
 return true;
 }
 }
 private class MoveListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
 @Override
 public boolean onMove(MoveGestureDetector detector) {
 PointF d = detector.getFocusDelta();
 mFocusX += d.x;
 mFocusY += d.y;
 changeMatrix();
 return true;
 }
 }
 private void changeMatrix(){
 float scaledImageCenterX = (getDrawable().getIntrinsicWidth()*mScaleFactor)/2;
 float scaledImageCenterY = (getDrawable().getIntrinsicHeight()*mScaleFactor)/2;
 mMatrix.reset();
 mMatrix.postScale(mScaleFactor, mScaleFactor);
 mMatrix.postRotate(mRotationDegrees, scaledImageCenterX, scaledImageCenterY);
 mMatrix.postTranslate(mFocusX - scaledImageCenterX, mFocusY - scaledImageCenterY);
 setImageMatrix(mMatrix);
 }
}

好啦~~~小伙伴也可以自己下载一下这个框架的代码去研究,我这呢也只是把自己学习的心得分享给大家。
https://github.com/Almeros/android-gesture-detectors

嗯嗯!说了那么多,最后让我们看看传说中的PhotoView到底是咋实现的。

photoview的github链接:
https://github.com/chrisbanes/PhotoViewary/

看完我们之前的内容,再去看PhotoView的话,你可能不会那么迷茫了,下面让我们一起揭开它的神秘面纱:

首先PhotoView的用法呢,很简单,小伙伴像用ImageView一样用它就可以了:


<uk.co.senab.photoview.PhotoView
 android:clickable="true"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:scaleType="fitxy"
 />

好啦!!现在就可以对图片进行缩放、旋转、移动操作啦~是不是很爽呢?

但是注意:

photoview的缩放类型不支持,不然就直接报错退出了:

android:scaleType="matrix"

我们来看看它的源码:


public class PhotoView extends ImageView implements IPhotoView {
 private PhotoViewAttacher mAttacher;
 private ScaleType mPendingScaleType;
 public PhotoView(Context context) {
 this(context, null);
 }
 public PhotoView(Context context, AttributeSet attr) {
 this(context, attr, 0);
 }
 public PhotoView(Context context, AttributeSet attr, int defStyle) {
 super(context, attr, defStyle);
 super.setScaleType(ScaleType.MATRIX);
 init();
 }
 protected void init() {
 if (null == mAttacher || null == mAttacher.getImageView()) {
 mAttacher = new PhotoViewAttacher(this);
 }
 if (null != mPendingScaleType) {
 setScaleType(mPendingScaleType);
 mPendingScaleType = null;
 }
 }
 @Override
 public void setRotationTo(float rotationDegree) {
 mAttacher.setRotationTo(rotationDegree);
 }
 @Override
 public void setRotationBy(float rotationDegree) {
 mAttacher.setRotationBy(rotationDegree);
 }
 @Override
 public boolean canZoom() {
 return mAttacher.canZoom();
 }
 @Override
 public RectF getDisplayRect() {
 return mAttacher.getDisplayRect();
 }
 @Override
 public void getDisplayMatrix(Matrix matrix) {
 mAttacher.getDisplayMatrix(matrix);
 }
 @Override
 public boolean setDisplayMatrix(Matrix finalRectangle) {
 return mAttacher.setDisplayMatrix(finalRectangle);
 }
 @Override
 public float getMinimumScale() {
 return mAttacher.getMinimumScale();
 }
 @Override
 public float getMediumScale() {
 return mAttacher.getMediumScale();
 }
 @Override
 public float getMaximumScale() {
 return mAttacher.getMaximumScale();
 }
 @Override
 public float getScale() {
 return mAttacher.getScale();
 }
 @Override
 public ScaleType getScaleType() {
 return mAttacher.getScaleType();
 }
 @Override
 public Matrix getImageMatrix() {
 return mAttacher.getImageMatrix();
 }
 @Override
 public void setAllowParentInterceptOnEdge(boolean allow) {
 mAttacher.setAllowParentInterceptOnEdge(allow);
 }
 @Override
 public void setMinimumScale(float minimumScale) {
 mAttacher.setMinimumScale(minimumScale);
 }
 @Override
 public void setMediumScale(float mediumScale) {
 mAttacher.setMediumScale(mediumScale);
 }
 @Override
 public void setMaximumScale(float maximumScale) {
 mAttacher.setMaximumScale(maximumScale);
 }
 @Override
 public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
 mAttacher.setScaleLevels(minimumScale, mediumScale, maximumScale);
 }
 @Override
 // setImageBitmap calls through to this method
 public void setImageDrawable(Drawable drawable) {
 super.setImageDrawable(drawable);
 if (null != mAttacher) {
 mAttacher.update();
 }
 }
 @Override
 public void setImageResource(int resId) {
 super.setImageResource(resId);
 if (null != mAttacher) {
 mAttacher.update();
 }
 }
 @Override
 public void setImageURI(Uri uri) {
 super.setImageURI(uri);
 if (null != mAttacher) {
 mAttacher.update();
 }
 }
 @Override
 protected boolean setFrame(int l, int t, int r, int b) {
 boolean changed = super.setFrame(l, t, r, b);
 if (null != mAttacher) {
 mAttacher.update();
 }
 return changed;
 }
 @Override
 public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
 mAttacher.setOnMatrixChangeListener(listener);
 }
 @Override
 public void setOnLongClickListener(OnLongClickListener l) {
 mAttacher.setOnLongClickListener(l);
 }
 @Override
 public void setOnPhotoTapListener(OnPhotoTapListener listener) {
 mAttacher.setOnPhotoTapListener(listener);
 }
 @Override
 public void setOnViewTapListener(OnViewTapListener listener) {
 mAttacher.setOnViewTapListener(listener);
 }
 @Override
 public void setScale(float scale) {
 mAttacher.setScale(scale);
 }
 @Override
 public void setScale(float scale, boolean animate) {
 mAttacher.setScale(scale, animate);
 }
 @Override
 public void setScale(float scale, float focalX, float focalY, boolean animate) {
 mAttacher.setScale(scale, focalX, focalY, animate);
 }
 @Override
 public void setScaleType(ScaleType scaleType) {
 if (null != mAttacher) {
 mAttacher.setScaleType(scaleType);
 } else {
 mPendingScaleType = scaleType;
 }
 }
 @Override
 public void setZoomable(boolean zoomable) {
 mAttacher.setZoomable(zoomable);
 }
 @Override
 public Bitmap getVisibleRectangleBitmap() {
 return mAttacher.getVisibleRectangleBitmap();
 }
 @Override
 public void setZoomTransitionDuration(int milliseconds) {
 mAttacher.setZoomTransitionDuration(milliseconds);
 }
 @Override
 public IPhotoView getIPhotoViewImplementation() {
 return mAttacher;
 }
 @Override
 public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
 mAttacher.setOnDoubleTapListener(newOnDoubleTapListener);
 }
 @Override
 public void setOnScaleChangeListener(PhotoViewAttacher.OnScaleChangeListener onScaleChangeListener) {
 mAttacher.setOnScaleChangeListener(onScaleChangeListener);
 }
 @Override
 public void setOnSingleFlingListener(PhotoViewAttacher.OnSingleFlingListener onSingleFlingListener) {
 mAttacher.setOnSingleFlingListener(onSingleFlingListener);
 }
 @Override
 protected void onDetachedFromWindow() {
 mAttacher.cleanup();
 mAttacher = null;
 super.onDetachedFromWindow();
 }
 @Override
 protected void onAttachedToWindow() {
 init();
 super.onAttachedToWindow();
 }
}

可以看到,代码并不多,才200多行(哈哈!!我们自己实现的MatrixImageView 100行都还不到呢!!开玩笑哈,PhotoView里面考虑的东西跟兼容性,我们写的MatrixImageView远远不及哈),主要的处理所及都在PhotoViewAttacher这个类中:

PhotoViewAttacher.java:

代码太多,我们看看它的构造方法


 public PhotoViewAttacher(ImageView imageView, boolean zoomable) {
 mImageView = new WeakReference<>(imageView);
 imageView.setDrawingCacheEnabled(true);
 imageView.setOnTouchListener(this);
 ViewTreeObserver observer = imageView.getViewTreeObserver();
 if (null != observer)
 observer.addOnGlobalLayoutListener(this);
 // Make sure we using MATRIX Scale Type
 setImageViewScaleTypeMatrix(imageView);
 if (imageView.isInEditMode()) {
 return;
 }
 // Create Gesture Detectors...
 mScaleDragDetector = VersionedGestureDetector.newInstance(
 imageView.getContext(), this);
 mGestureDetector = new GestureDetector(imageView.getContext(),
 new GestureDetector.SimpleOnGestureListener() {
 // forward long click listener
 @Override
 public void onLongPress(MotionEvent e) {
 if (null != mLongClickListener) {
 mLongClickListener.onLongClick(getImageView());
 }
 }
 @Override
 public boolean onFling(MotionEvent e1, MotionEvent e2,
  float velocityX, float velocityY) {
 if (mSingleFlingListener != null) {
 if (getScale() > DEFAULT_MIN_SCALE) {
 return false;
 }
 if (MotionEventCompat.getPointerCount(e1) > SINGLE_TOUCH
  || MotionEventCompat.getPointerCount(e2) > SINGLE_TOUCH) {
 return false;
 }
 return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY);
 }
 return false;
 }
 });
 mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
 mBaseRotation = 0.0f;
 // Finally, update the UI so that we're zoomable
 setZoomable(zoomable);
 }

可以看到,它也是创建了一个mScaleDragDetector跟一个mGestureDetector用于监听手势变幻,那么事件处理在什么地方呢?

我们在构造方法还发现了一行代码,给当前imageView设置触碰监听:

imageView.setOnTouchListener(this);

小伙伴猜都猜到了,现在就是把事件给事件监听器了:


@Override
 public boolean onTouch(View v, MotionEvent ev) {
 boolean handled = false;
 if (mZoomEnabled && hasDrawable((ImageView) v)) {
 ViewParent parent = v.getParent();
 switch (ev.getAction()) {
 case ACTION_DOWN:
 // First, disable the Parent from intercepting the touch
 // event
 if (null != parent) {
 parent.requestDisallowInterceptTouchEvent(true);
 } else {
 LogManager.getLogger().i(LOG_TAG, "onTouch getParent() returned null");
 }
 // If we're flinging, and the user presses down, cancel
 // fling
 cancelFling();
 break;
 case ACTION_CANCEL:
 case ACTION_UP:
 // If the user has zoomed less than min scale, zoom back
 // to min scale
 if (getScale() < mMinScale) {
 RectF rect = getDisplayRect();
 if (null != rect) {
 v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
  rect.centerX(), rect.centerY()));
 handled = true;
 }
 }
 break;
 }
 // Try the Scale/Drag detector
 if (null != mScaleDragDetector) {
 boolean wasScaling = mScaleDragDetector.isScaling();
 boolean wasDragging = mScaleDragDetector.isDragging();
 handled = mScaleDragDetector.onTouchEvent(ev);
 boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
 boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();
 mBlockParentIntercept = didntScale && didntDrag;
 }
 // Check to see if the user double tapped
 if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
 handled = true;
 }
 }
 return handled;
 }

最后处理完毕事件后,就是一系列的回调了,回调完毕后就应该给ImageView重新设置matrix对象了,比如缩放:


@Override
 public void setScale(float scale, float focalX, float focalY,
 boolean animate) {
 ImageView imageView = getImageView();
 if (null != imageView) {
 // Check to see if the scale is within bounds
 if (scale < mMinScale || scale > mMaxScale) {
 LogManager
 .getLogger()
 .i(LOG_TAG,
 "Scale must be within the range of minScale and maxScale");
 return;
 }
 if (animate) {
 imageView.post(new AnimatedZoomRunnable(getScale(), scale,
 focalX, focalY));
 } else {
 mSuppMatrix.setScale(scale, scale, focalX, focalY);
 checkAndDisplayMatrix();
 }
 }
 }

其它的类似哈~~~ 代码还是挺多的(考虑的情况比较多)可想而之,要写好一个自定义组件还不是那么简单的事哦,不过还是加油吧~!

您可能感兴趣的文章:Android编程使用GestureDetector实现简单手势监听与处理的方法Android手势识别器GestureDetector使用详解Android GestureDetector用户手势检测实例讲解Android自定义viewgroup可滚动布局 GestureDetector手势监听(5)Android GestureDetector手势滑动使用实例讲解Android触摸及手势操作GestureDetectorandroid使用gesturedetector手势识别示例分享Android GestureDetector实现手势滑动效果


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android自定义GestureDetector实现手势ImageView

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android自定义GestureDetector实现手势ImageView

不说废话了,进入我们今天的主题吧。 先贴上前面内容的地址: Android手势ImageView三部曲(一) Android手势ImageView三部曲(二) Android手势ImageView三部曲(三) 前面我们讲到了ScaleGes
2022-06-06

Android自定义viewgroup可滚动布局 GestureDetector手势监听(5)

这篇效果和上一篇://www.jb51.net/article/100638.htm的效果是一样的,但是不再在OnTouchEvent中写代码,而是使用系统自带的类GestureDetector来监听手势以及滑动事件等等,它内置了滑动,点击
2022-06-06

Android自定义控件实现手势密码

Android手势解锁密码效果图 首先呢想写这个手势密码的想法呢,完全是凭空而来的,然后笔者就花了一天时间弄出来了。本以为这个东西很简单,实际上手的时候发现,还有很多逻辑需要处理,稍不注意就容易乱套。写个UI效果图大约只花了3个小
2022-06-06

Android自定义View实现随手势滑动控件

本文控件为大家分享了Android随手势滑动控件的具体代码,供大家参考,具体内容如下 1.新建自定义控件类:MyViewpublic class MyView extends Button{ //记录上次滑动后的坐标值 private in
2022-06-06

Android 自定义imageview实现图片缩放实例详解

Android 自定义imageview实现图片缩放实例详解 觉得这个自定义的imageview很好用 性能不错 所以拿出来分享给大家 因为不会做gif图 所以项目效果 就不好贴出来了 把代码贴出来 1.项目结构图2.Compat
2022-06-06

Android 自定义手势--输入法手势技术

进行软件开发时,通常我们都喜欢使用较新版本的工具,但这里我为什么使用低版本的SDK来开发Android游戏呢?这里介绍下原因: 1、Android SDK 属于向下兼容!那么低版本可以运行的,高版本基本上更是没问题!(当然每次
2022-06-06

Android编程实现自定义手势的方法详解

本文实例讲述了Android编程实现自定义手势的方法。分享给大家供大家参考,具体如下: 之前介绍过如何在Android程序中使用手势,主要是系统默认提供的几个手势,这次介绍一下如何自定义手势,以及如何对其进行管理。 先介绍一下Android
2022-06-06

Android实现手势控制ImageView图片大小

本文实例实现的主要功能是在ImageView中识别手势用以控制图片放大或缩小,具有一定的参考价值,分享给大家。public class MatrixImageView extends ImageView {private GestureDe
2022-06-06

Android编程实现自定义ImageView圆图功能的方法

本文实例讲述了Android编程实现自定义ImageView圆图功能的方法。分享给大家供大家参考,具体如下:首先很感谢开源项目Universal Image Loader图片加载框架。之前也看过一段时间框架源码,但是却没有时间进行知识点的总
2023-05-30

Android自定义ImageView实现在图片上添加图层效果

首先我们先看下效果图实现思路 这是两张前后对比图,右边第二张图里面的已抢光标签图片当已经没有商品的时候就会显示了,在每个图片的中心位置,第一想法是在ImageView的外层再套一层RelativeLayout 实现方法
2022-06-06

Android自定义UI手势密码终结版

之前写过3篇手势密码的demo,不过没有集成到真实的企业项目中,这几天正好领到一个手势密码项目,昨天刚好弄完,今天抽空整理下,目前还没有完善,有一些地方需要更改,不过基本的流程都可以跑通了。源码下载地址:http://xiazai.jb51
2022-06-06

Android游戏开发 自定义手势--输入法手势技术

进行软件开发时,通常我们都喜欢使用较新版本的工具,但这里我为什么使用低版本的SDK来开发Android游戏呢?这里介绍下原因: 1、Android SDK 属于向下兼容!那么低版本可以运行的,高版本基本上更是没问
2022-06-06

Android自定义UI手势密码简单版

先看看效果图:ImageLockActivitypackage com.example.imagelock; import com.example.view.NinePointLineView; import android.os.Bu
2022-06-06

Android自定义UI手势密码改进版

接着第一个Android UI手势密码设计的基础上继续改进,效果图如下activity_main.xml2022-06-06

Android使用ImageView实现支持手势缩放效果

TouchImageView继承自ImageView具有ImageView的所有功能;除此之外,还有缩放、拖拽、双击放大等功能,支持viewpager和scaletype,并伴有动画效果。sharedConstructing private
2022-06-06

Android手势滑动实现ImageView缩放图片大小

本文推出了两种Android手势实现ImageView缩放图片大小的方法,分享给大家供大家参考,具体内容如下 方法一: 将以下代码写到MulitPointTouchListener.java中,然后对你相应的图片进行OnTouchListe
2022-06-06

Android实现价格走势自定义曲线图

本文是引用开源图表库框架 MPAndroidChart的LineChart地址:https://github.com/PhilJay/MPAndroidChart1.需求:(1)动态添加RadioButton,点击改变下面的LineChar
2022-06-06

Android中如何实现自定义ImageView添加文字设置按下效果

这篇文章主要为大家展示了“Android中如何实现自定义ImageView添加文字设置按下效果”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Android中如何实现自定义ImageView添加文
2023-05-30

Android实现自定义日历

自定义日历控件,支持旧历、节气、日期标注、点击操作 (参考网络上的日历控件改写) 注:将下面的四张资源图片拷贝到所建包的下一个image目录中,如Calendar.java 所在包为 cc.util.android.view,则需要再创建一
2022-06-06

Android通过自定义ImageView控件实现图片的缩放和拖动的实现代码

概述:通过自定义ImageView控件,在xml布局里面调用自定的组件实现图片的缩放。 public class
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录