Android自定義GestureDetector實(shí)現(xiàn)手勢(shì)ImageView
不說廢話了,進(jìn)入我們今天的主題吧。
先貼上前面內(nèi)容的地址:
前面我們講到了ScaleGestureDetector這個(gè)工具類,我在疑惑,為什么搞出一個(gè)ScaleGestureDetector,不順帶把什么旋轉(zhuǎn)、移動(dòng)、做了呢? 好吧~! 谷歌肯定還是想給開發(fā)者留一點(diǎn)自己的空間哈。
仿照ScaleGestureDetector,我們來定義一個(gè)叫MoveGestureDetector的工具類(專門用于檢測(cè)滑動(dòng)手勢(shì)),在定義MoveGestureDetector之前,因?yàn)槲覀冞€要考慮到之后的RotateGestureDetector等等..于是我們定一個(gè)叫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,這是一個(gè)什么概念呢? * 我們想象一下當(dāng)你手指按下然后滑動(dòng)并且到離開屏幕, * 手指觸碰到屏幕的壓力會(huì)越來越小,直到手指移開屏幕 */ 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; /** * 是否調(diào)用handleInProgressEvent方法 */ if (!mGestureInProgress) { //如果mGestureInProgress為false的時(shí)候,執(zhí)行開始操作 handleStartProgressEvent(actionCode, event); } else { //處理手勢(shì) handleInProgressEvent(actionCode, event); } return true; } /** * 準(zhǔn)備處理手勢(shì) * @param actionCode * @param event */ protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event); /** * 正在處理手勢(shì) * @param actionCode * @param event */ protected abstract void handleInProgressEvent(int actionCode, MotionEvent event); /** * 更新event的狀態(tài),保存之前的event,獲取當(dāng)前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跟現(xiàn)在的event之間的時(shí)間差 mTimeDelta = curr.getEventTime() - prev.getEventTime(); // 之前的event跟腺癌的event之間的手指壓力值 mCurrPressure = curr.getPressure(curr.getActionIndex()); mPrevPressure = prev.getPressure(prev.getActionIndex()); } /** * 重置所有狀態(tài) */ 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(); } }
然后我們定義一個(gè)叫MoveGestureDetector的類去繼承BaseGestureDetector,然后事件兩個(gè)抽象方法:
public class MoveGestureDetector extends BaseGestureDetector{ @Override protected void handleStartProgressEvent(int actionCode, MotionEvent event){ } @Override protected void handleInProgressEvent(int actionCode, MotionEvent event){ } }
那我們?nèi)绻麢z測(cè)到了事件的話該怎么通知調(diào)用者呢?是的,我們需要用到回調(diào),我們看看ScaleGestureDetector的回調(diào)接口咋定義的:
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 } }
里面定義了一個(gè)接口一個(gè)叫OnScaleGestureListener,一個(gè)類叫SimpleOnScaleGestureListener,SimpleOnScaleGestureListener是實(shí)現(xiàn)了OnScaleGestureListener,于是我們MoveGestureDetector的接口可以這么定義了:
/** * 仿照ScaleGestureDetector我們也定義三個(gè)方法 */ public interface OnMoveGestureListener { /** * 移動(dòng)的時(shí)候回調(diào) */ public boolean onMove(MoveGestureDetector detector); /** * 移動(dòng)開始的時(shí)候回調(diào) */ public boolean onMoveBegin(MoveGestureDetector detector); /** * 移動(dòng)結(jié)束的時(shí)候回調(diào) */ 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 } }
好啦!框子都搭好了,我們用的時(shí)候呢,就可以這么用了:
1、創(chuàng)建一個(gè)MoveGestureDetector
public MatrixImageView(Context context, AttributeSet attrs) { super(context, attrs); initView(); //創(chuàng)建一個(gè)縮放手勢(shì)監(jiān)測(cè)器 scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener); //創(chuàng)建一個(gè)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、獲取回調(diào)值
private MoveGestureDetector.SimpleOnMoveGestureListener onMoveGestureListener=new MoveGestureDetector.SimpleOnMoveGestureListener(){ @Override public boolean onMove(MoveGestureDetector detector) { return super.onMove(detector); } };
怎么樣?是不是跟ScaleGestureDetector一樣了呢?清晰明了哈,框子是搭起來了,下面我們來實(shí)現(xiàn)下它的邏輯(也就是實(shí)現(xiàn)下handleStartProgressEvent跟handleInProgressEvent方法):
每行都有注釋,我就直接上代碼了
*/ public class MoveGestureDetector extends BaseGestureDetector { /** * 仿照ScaleGestureDetector我們也定義三個(gè)方法 */ public interface OnMoveGestureListener { /** * 移動(dòng)的時(shí)候回調(diào) */ public boolean onMove(MoveGestureDetector detector); /** * 移動(dòng)開始的時(shí)候回調(diào) */ public boolean onMoveBegin(MoveGestureDetector detector); /** * 移動(dòng)結(jié)束的時(shí)候回調(diào) */ 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) { //當(dāng)手指按下的時(shí)候 case MotionEvent.ACTION_DOWN: //重置一下所有狀態(tài)(currevent跟preevent) resetState(); // In case we missed an UP/CANCEL event //獲取當(dāng)前event作為mPrevEvent mPrevEvent = MotionEvent.obtain(event); //重置兩次event的時(shí)間間隔 mTimeDelta = 0; //更新state updateStateByEvent(event); break; case MotionEvent.ACTION_MOVE: //回調(diào)onMoveBegin,mGestureInProgress決定是否繼續(xù)處理事件(執(zhí)行handleInProgressEvent) //mGestureInProgress由調(diào)用者決定 mGestureInProgress = mListener.onMoveBegin(this); break; } } /** * 處理移動(dòng)事件 */ @Override protected void handleInProgressEvent(int actionCode, MotionEvent event){ switch (actionCode) { //當(dāng)抬起或者取消的時(shí)候 case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: //回調(diào)onMoveEnd,move處理結(jié)束 mListener.onMoveEnd(this); //重置所有的state resetState(); break; case MotionEvent.ACTION_MOVE: //更新狀態(tài) updateStateByEvent(event); //當(dāng)上一次event的press值/這一次event值大于臨界值的時(shí)候開始觸發(fā)onMove //因?yàn)槿绻鸆urrPressure / mPrevPressure很小的話,可能手指已經(jīng)離開屏幕了 if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { /** * 回調(diào)onMove方法,并獲取updatePrevious * updatePrevious標(biāo)記是由調(diào)用者決定, * updatePrevious是否更新之前的event, * 如果為false的話mPrevEvent一直是我們?cè)赿own的時(shí)候賦值的event * 如果為true的話,每次move事件處理完都會(huì)把最新的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; // 獲取當(dāng)前所有手指的中心點(diǎn) mCurrFocusInternal = determineFocalPoint(curr); //獲取之前event所有手指的中心點(diǎn) mPrevFocusInternal = determineFocalPoint(prev); //判斷是否有手指中途添加或者移除 boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount(); //有移除的話mFocusDeltaExternal就等于空(0,0),沒有的話就算出前面event跟當(dāng)前event中心點(diǎn)距離 mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x, mCurrFocusInternal.y - mPrevFocusInternal.y); //累加距離值 mFocusExternal.x += mFocusDeltaExternal.x; mFocusExternal.y += mFocusDeltaExternal.y; } /** * 獲取所有手指的中間點(diǎn)坐標(biāo)(參考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; } /** * 獲取上一個(gè)事件到下一個(gè)事件之間的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;//當(dāng)前圖片的縮放值 private float transX,transY; private ScaleGestureDetector scaleDetector; private MoveGestureDetector moveGestureDetector; public MatrixImageView(Context context, AttributeSet attrs) { super(context, attrs); initView(); //創(chuàng)建一個(gè)縮放手勢(shì)監(jiān)測(cè)器 scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener); //創(chuàng)建一個(gè)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(); /** * 因?yàn)間etScaleFactor=當(dāng)前兩個(gè)手指之間的距離(preEvent)/手指按下時(shí)候兩個(gè)點(diǎn)的距離(currEvent) * 這里如果返回true的話,會(huì)在move操作的時(shí)候去更新之前的event, * 如果為false的話,不會(huì)去更新之前按下時(shí)候保存的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; } }; }
好啦~??! 短短幾行代碼就可以玩起來了,效果圖我就不附了哈,小伙伴自己運(yùn)行一下,那么MoveGestureDetector我們實(shí)現(xiàn)了,想必RotateGestureDetector也是很快就會(huì)實(shí)現(xiàn)了,哈哈~~! 我就直接用貼上國(guó)外大神寫的代碼了:
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); } }
最后把我們結(jié)合了ScaleDetector、MoveDetector、RotateDetector的一個(gè)手勢(shì)縮放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() { //初始化模式為初始狀態(tài) DisplayMetrics dm = getResources().getDisplayMetrics(); //給ImageView設(shè)置一張圖片(此處為了測(cè)試直接在imageview里面設(shè)置了一張測(cè)試圖片) 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); //把旋轉(zhuǎn)事件個(gè)mRotateDetector mRotateDetector.onTouchEvent(event); //把移動(dòng)事件給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); } }
好啦~~~小伙伴也可以自己下載一下這個(gè)框架的代碼去研究,我這呢也只是把自己學(xué)習(xí)的心得分享給大家。
https://github.com/Almeros/android-gesture-detectors
嗯嗯!說了那么多,最后讓我們看看傳說中的PhotoView到底是咋實(shí)現(xiàn)的。
photoview的github鏈接:
https://github.com/chrisbanes/PhotoViewary/
看完我們之前的內(nèi)容,再去看PhotoView的話,你可能不會(huì)那么迷茫了,下面讓我們一起揭開它的神秘面紗:
首先PhotoView的用法呢,很簡(jiǎn)單,小伙伴像用ImageView一樣用它就可以了:
<uk.co.senab.photoview.PhotoView android:clickable="true" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitxy" />
好啦?。‖F(xiàn)在就可以對(duì)圖片進(jìn)行縮放、旋轉(zhuǎn)、移動(dòng)操作啦~是不是很爽呢?
但是注意:
photoview的縮放類型不支持,不然就直接報(bào)錯(cuò)退出了:
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多行(哈哈??!我們自己實(shí)現(xiàn)的MatrixImageView 100行都還不到呢??!開玩笑哈,PhotoView里面考慮的東西跟兼容性,我們寫的MatrixImageView遠(yuǎn)遠(yuǎn)不及哈),主要的處理所及都在PhotoViewAttacher這個(gè)類中:
PhotoViewAttacher.java:
代碼太多,我們看看它的構(gòu)造方法
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); }
可以看到,它也是創(chuàng)建了一個(gè)mScaleDragDetector跟一個(gè)mGestureDetector用于監(jiān)聽手勢(shì)變幻,那么事件處理在什么地方呢?
我們?cè)跇?gòu)造方法還發(fā)現(xiàn)了一行代碼,給當(dāng)前imageView設(shè)置觸碰監(jiān)聽:
imageView.setOnTouchListener(this);
小伙伴猜都猜到了,現(xiàn)在就是把事件給事件監(jiān)聽器了:
@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; }
最后處理完畢事件后,就是一系列的回調(diào)了,回調(diào)完畢后就應(yīng)該給ImageView重新設(shè)置matrix對(duì)象了,比如縮放:
@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(); } } }
其它的類似哈~~~ 代碼還是挺多的(考慮的情況比較多)可想而之,要寫好一個(gè)自定義組件還不是那么簡(jiǎn)單的事哦,不過還是加油吧~!
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android GestureDetector用戶手勢(shì)檢測(cè)實(shí)例講解
- android使用gesturedetector手勢(shì)識(shí)別示例分享
- Android GestureDetector手勢(shì)滑動(dòng)使用實(shí)例講解
- Android手勢(shì)識(shí)別器GestureDetector使用詳解
- Android自定義viewgroup可滾動(dòng)布局 GestureDetector手勢(shì)監(jiān)聽(5)
- Android GestureDetector實(shí)現(xiàn)手勢(shì)滑動(dòng)效果
- Android編程使用GestureDetector實(shí)現(xiàn)簡(jiǎn)單手勢(shì)監(jiān)聽與處理的方法
- Android觸摸及手勢(shì)操作GestureDetector
- Android使用手勢(shì)監(jiān)聽器GestureDetector遇到的不響應(yīng)問題
- Android如何使用GestureDetector進(jìn)行手勢(shì)檢測(cè)詳解
相關(guān)文章
關(guān)于Fragment?already?added問題的解決方案
這篇文章主要介紹了關(guān)于Fragment?already?added問題的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10android ViewPager實(shí)現(xiàn)滑動(dòng)翻頁效果實(shí)例代碼
本篇文章主要介紹了android ViewPager實(shí)現(xiàn)滑動(dòng)翻頁效果實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03Android BaseAdapter應(yīng)用實(shí)例
這篇文章主要介紹了Android BaseAdapter應(yīng)用方法,結(jié)合生成聯(lián)系人Items的實(shí)例形式分析了BaseAdapter的使用技巧,需要的朋友可以參考下2016-01-01android實(shí)現(xiàn)注冊(cè)登錄程序
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)注冊(cè)登錄程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Android實(shí)現(xiàn)ListView控件的多選和全選功能實(shí)例
這篇文章主要介紹了Android實(shí)現(xiàn)ListView控件的多選和全選功能,結(jié)合實(shí)例形式分析了ListView控件多選及全選功能的布局與功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-07-07Android自定義View仿大眾點(diǎn)評(píng)星星評(píng)分控件
這篇文章主要為大家詳細(xì)介紹了Android自定義View仿大眾點(diǎn)評(píng)星星評(píng)分控件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03Android編程實(shí)現(xiàn)獲取當(dāng)前系統(tǒng)語言及地區(qū)并更改語言的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)獲取當(dāng)前系統(tǒng)語言及地區(qū)并更改語言的方法,涉及Android針對(duì)系統(tǒng)語言及地區(qū)的獲取與設(shè)置相關(guān)操作技巧,需要的朋友可以參考下2017-10-10如何修改Android Studio創(chuàng)建module時(shí)默認(rèn)的compileSdkVersion
這篇文章主要給大家介紹了如何修改Android Studio創(chuàng)建module時(shí)默認(rèn)的compileSdkVersion的相關(guān)資料,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-05-05