Android手势ImageView之(自定义GestureDetector)

前言:本来是打算周末把手势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();
            }
        }
    }

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

end~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值