AndroidジェスチャーImageView之(カスタムGestureDetector)

71283 ワード

前言:週末にジェスチャーImageView編の内容を作るつもりでしたが、結局また2日間昏睡してしまいました.ああ~~これからはそうはいかないようです.くだらないことは言わないで、今日のテーマに入りましょう.
先に前の内容の住所を貼り付けます.
  • AndroidジェスチャーImageView三部作(一)
  • AndroidジェスチャーImageView三部作(二)
  • AndroidジェスチャーImageView三部作(三)
  • 前にScaleGestureDetectorというツール類についてお話ししましたが、なぜScaleGestureDetectorを作ったのか疑問に思っています.よし~!Googleは開発者に自分のスペースを残したいに違いない~~
    スライドジェスチャーの検出に特化した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を継承し、イベントの2つの抽象的な方法を定義します.
    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のように使えばいいです.
    .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でははるかに及ばないです)、主な処理所はPhotoViewというクラスにあります.
    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();
                }
            }
        }

    他にも似たようなハ~~~コードが多いと思いますが、カスタマイズしたコンポーネントを書くのはそんなに簡単なことではありませんが、頑張りましょう~!!!
    end~