不说废话了,进入我们今天的主题吧。
先贴上前面内容的地址:
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; /** * 上一次event的pressure/这一次的pressure,这是一个什么概念呢? * 我们想象一下当你手指按下然后滑动并且到离开屏幕, * 手指触碰到屏幕的压力会越来越小,直到手指移开屏幕 */ protected static final float PRESSURE_THRESHOLD = 0.67f; public BaseGestureDetector(Context context) { mContext = context; } /** * 跟ScaleGesture一样,我们也把事件的处理放在此方法中 * @param event * @return */ public boolean onTouchEvent(MotionEvent event){ //为了获取到ACTION_POINTER_UP等事件必须加上& MotionEvent.ACTION_MASK final int actionCode = event.getAction() & MotionEvent.ACTION_MASK; /** * 是否调用handleInProgressEvent方法 */ if (!mGestureInProgress) { //如果mGestureInProgress为false的时候,执行开始操作 handleStartProgressEvent(actionCode, event); } else { //处理手势 handleInProgressEvent(actionCode, event); } return true; } /** * 准备处理手势 * @param actionCode * @param event */ protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event); /** * 正在处理手势 * @param actionCode * @param event */ protected abstract void handleInProgressEvent(int actionCode, MotionEvent event); /** * 更新event的状态,保存之前的event,获取当前event * @param curr */ 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; } /** * Returns {@code true} if a gesture is currently in progress. * @return {@code true} if a gesture is currently in progress, {@code false} otherwise. */ public boolean isInProgress() { return mGestureInProgress; } /** * Return the time difference in milliseconds between the previous accepted * GestureDetector event and the current GestureDetector event. * * @return Time difference since the last move event in milliseconds. */ public long getTimeDelta() { return mTimeDelta; } /** * Return the event time of the current GestureDetector event being * processed. * * @return Current GestureDetector event time in milliseconds. */ 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的接口可以这么定义了:
/** * 仿照ScaleGestureDetector我们也定义三个方法 */ 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 { /** * 仿照ScaleGestureDetector我们也定义三个方法 */ 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) { /** * 回调onMove方法,并获取updatePrevious * updatePrevious标记是由调用者决定, * updatePrevious是否更新之前的event, * 如果为false的话mPrevEvent一直是我们在down的时候赋值的event * 如果为true的话,每次move事件处理完都会把最新的event赋给mPrevEvent */ final boolean updatePrevious = mListener.onMove(this); if (updatePrevious) { mPrevEvent.recycle(); mPrevEvent = MotionEvent.obtain(event); } } break; } } /** * 参考ScaleGestureDetector * move核心处理方法 * 重写父类的updateStateByEvent * */ 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; } /** * 获取所有手指的中间点坐标(参考ScaleGestureDetector) */ 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; } /** * 获取上一个事件到下一个事件之间的x跟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(); /** * 因为getScaleFactor=当前两个手指之间的距离(preEvent)/手指按下时候两个点的距离(currEvent) * 这里如果返回true的话,会在move操作的时候去更新之前的event, * 如果为false的话,不会去更新之前按下时候保存的event */ 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 { /** * Listener which must be implemented which is used by RotateGestureDetector * to perform callbacks to any implementing class which is registered to a * RotateGestureDetector via the constructor. * * @see SimpleOnRotateGestureListener */ public interface OnRotateGestureListener { public boolean onRotate(RotateGestureDetector detector); public boolean onRotateBegin(RotateGestureDetector detector); public void onRotateEnd(RotateGestureDetector detector); } /** * Helper class which may be extended and where the methods may be * implemented. This way it is not necessary to implement all methods * of OnRotateGestureListener. */ 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; } /** * Return the rotation difference from the previous rotate event to the current * event. * * @return The current rotation //difference in degrees. */ 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); html" target="_blank">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实现自定义手势和识别手势的功能,包括了Android实现自定义手势和识别手势的功能的使用技巧和注意事项,需要的朋友参考一下 1. 先完成自定义手势的Activity 1.1 因为需要存储手势文件所以需要声明权限: 1.2 简单写一个布局文件,其中用到了GestureOverlayView,相当于一个绘制组件。其中有一个重要属性gestureStrokeType,值为si
本文向大家介绍Android自定义控件实现手势密码,包括了Android自定义控件实现手势密码的使用技巧和注意事项,需要的朋友参考一下 Android手势解锁密码效果图 首先呢想写这个手势密码的想法呢,完全是凭空而来的,然后笔者就花了一天时间弄出来了。本以为这个东西很简单,实际上手的时候发现,还有很多逻辑需要处理,稍不注意就容易乱套。写个UI效果图大约只花了3个小时,但是处理逻辑就处理
本文向大家介绍Android自定义View实现随手势滑动控件,包括了Android自定义View实现随手势滑动控件的使用技巧和注意事项,需要的朋友参考一下 本文控件为大家分享了Android随手势滑动控件的具体代码,供大家参考,具体内容如下 1.新建自定义控件类:MyView 上面代码就是一个自定义按钮类,重写onTouchEvent()方法来监听用户滑动,既然说到滑动肯定会存在偏移量的说法。 t
本文向大家介绍Android触摸及手势操作GestureDetector,包括了Android触摸及手势操作GestureDetector的使用技巧和注意事项,需要的朋友参考一下 现在的智能手机不敢说百分百的都是触摸屏,也应该是百分之九九以上为触摸屏了,触摸屏为我们操作无键盘、无鼠标的手机系统带来了很多的便利。当用户触摸屏幕时会产生很多的触摸事件,down、up、move等等。View类有个Vie
本文向大家介绍Android GestureDetector手势滑动使用实例讲解,包括了Android GestureDetector手势滑动使用实例讲解的使用技巧和注意事项,需要的朋友参考一下 Gesture在 ViewGroup中使用 GestureDetector类可以让我们快速的处理手势事件,如点击,滑动等。 使用GestureDetector分三步: 1. 定义GestureDetect
本文向大家介绍Android GestureDetector用户手势检测实例讲解,包括了Android GestureDetector用户手势检测实例讲解的使用技巧和注意事项,需要的朋友参考一下 一、概述 当用户触摸屏幕的时候,会产生许多手势,例如down,up,scroll,filing等等。 一般情况下,我们知道View类有个View.OnTouchListener内部接口,通过重写他的onT