您好,登錄后才能下訂單哦!
不說廢話了,進入我們今天的主題吧。
先貼上前面內容的地址:
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); 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(); } } }
其它的類似哈~~~ 代碼還是挺多的(考慮的情況比較多)可想而之,要寫好一個自定義組件還不是那么簡單的事哦,不過還是加油吧~!
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。