Android手势ImageView三部曲(三)

来源:互联网 发布:飞驰网络加速器 编辑:程序博客网 时间:2024/06/11 19:30

接着上一节 Android手势ImageView三部曲(二)的往下走,我们讲到了github上的GestureDetector框架,
先附上github链接:
https://github.com/Almeros/android-gesture-detectors
其实把这个框架的主体思想也是参考的Android自带的ScaleGestureDetector工具类,ScaleGestureDetector估计是参考的GestureDetector工具类,不管谁参考谁的,既然被我们遇到了,我们就要变成自己的东西,真不能全变成自己的东西的话,至少我们要了解下它的思想。
我们先了解一下android自带的ScaleGestureDetector(缩放手势监测器):

ScaleGestureDetector跟GestureDetector构造都差不多,但是ScaleGestureDetector只能用于监测缩放的手势,而GestureDetector监测的手势就比较多了,我们上一节内容中有提到。
ScaleGestureDetector的一些用法跟api,小伙伴自行去查看官网文档:
https://developer.android.google.cn/reference/android/view/ScaleGestureDetector.html

我们怎么使用它呢(我以第一节中最后一个demo为例)?
首先创建一个ScaleGestureDetector对象:

 private void initView() {       ....        mScaleDetector  = new ScaleGestureDetector(getContext(), new ScaleListener());        ....    }

然后传递一个叫ScaleListener的回调接口给它,ScaleListener里面有三个回调方法:

 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;        }        @Override        public boolean onScaleBegin(ScaleGestureDetector detector) {            return super.onScaleBegin(detector);        }        @Override        public void onScaleEnd(ScaleGestureDetector detector) {            super.onScaleEnd(detector);        }    }

小伙伴应该看得懂哈,就是onScale放缩时回调,onScaleBegin缩放开始时回调,onScaleEnd缩放完毕后回调。

最后在view的onTouchEvent方法中把事件给ScaleGestureDetector对象:

@Override    public boolean onTouchEvent(MotionEvent event) {        //把缩放事件给mScaleDetector        mScaleDetector.onTouchEvent(event);        return true;    }

好啦~!!上一节最后的时候,我写了一个小demo去实现了图片的位移,下面我们继续加上图片的缩放:

public class MatrixImageView extends ImageView {    private Matrix currMatrix;    private GestureDetector detector;    private ScaleGestureDetector scaleDetector;    private float currX;//当前图片的x坐标值    private float currY;//当前图片的y坐标值    private float scaleFactor=1f;//当前图片的缩放值    public MatrixImageView(Context context, AttributeSet attrs) {        super(context, attrs);        initView();        detector=new GestureDetector(context,onGestureListener);        //创建一个缩放手势监测器        scaleDetector=new ScaleGestureDetector(context,onScaleGestureListener);    }    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) {        detector.onTouchEvent(event);        //把事件给scaleDetector        scaleDetector.onTouchEvent(event);        return true;    }    private void setMatrix(){        currMatrix.reset();        currMatrix.postTranslate(currX,currY);        currMatrix.postScale(scaleFactor,scaleFactor,getMeasuredWidth()/2,getMeasuredHeight()/2);        setImageMatrix(currMatrix);    }    private GestureDetector.SimpleOnGestureListener onGestureListener=new GestureDetector.SimpleOnGestureListener(){        @Override        public boolean onDown(MotionEvent e) {            return true;        }        @Override        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {            currX-=distanceX;            currY-=distanceY;            setMatrix();            return super.onScroll(e1, e2, distanceX, distanceY);        }    };    private ScaleGestureDetector.SimpleOnScaleGestureListener onScaleGestureListener=new ScaleGestureDetector.SimpleOnScaleGestureListener(){        @Override        public boolean onScale(ScaleGestureDetector detector) {            scaleFactor*=detector.getScaleFactor();            setMatrix();            /**             * 因为getScaleFactor=当前两个手指之间的距离(preEvent)/手指按下时候两个点的距离(currEvent)             * 这里如果返回true的话,会在move操作的时候去更新之前的event,             * 如果为false的话,不会去更新之前按下时候保存的event             */            return true;        }    };}

尴尬了,模拟器不太好用于两个手指缩放的录制,所以效果小伙伴自己拿到代码运行一下哈~!!!

下面一起撸一撸ScaleGestureDetector的源码:
我们知道,ScaleGestureDetector核心代码也就是onTouchEvent,于是我们点开onTouchEvent:

@SuppressLint("NewApi")    public boolean onTouchEvent(MotionEvent event) {        //获取当前event事件        mCurrTime = event.getEventTime();        final int action = event.getActionMasked();        // Forward the event to check for double tap gesture        if (mQuickScaleEnabled) {            mGestureDetector.onTouchEvent(event);        }        //获取手指个数        final int count = event.getPointerCount();        final boolean isStylusButtonDown =                (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;        final boolean anchoredScaleCancelled =                mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;        final boolean streamComplete = action == MotionEvent.ACTION_UP ||                action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;        //手指按下的时候        if (action == MotionEvent.ACTION_DOWN || streamComplete) {            // Reset any scale in progress with the listener.            // If it's an ACTION_DOWN we're beginning a new event stream.            // This means the app probably didn't give us all the events. Shame on it.            if (mInProgress) {                mListener.onScaleEnd(this);                mInProgress = false;                mInitialSpan = 0;                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;            } else if (inAnchoredScaleMode() && streamComplete) {                mInProgress = false;                mInitialSpan = 0;                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;            }            if (streamComplete) {                return true;            }        }        if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()                && !streamComplete && isStylusButtonDown) {            // Start of a button scale gesture            mAnchoredScaleStartX = event.getX();            mAnchoredScaleStartY = event.getY();            mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;            mInitialSpan = 0;        }        final boolean configChanged = action == MotionEvent.ACTION_DOWN ||                action == MotionEvent.ACTION_POINTER_UP ||                action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;        final int skipIndex = pointerUp ? event.getActionIndex() : -1;        //处理多点之间距离        float sumX = 0, sumY = 0;        final int div = pointerUp ? count - 1 : count;        final float focusX;        final float focusY;        if (inAnchoredScaleMode()) {            // In anchored scale mode, the focal pt is always where the double tap            // or button down gesture started            focusX = mAnchoredScaleStartX;            focusY = mAnchoredScaleStartY;            if (event.getY() < focusY) {                mEventBeforeOrAboveStartingGestureEvent = true;            } else {                mEventBeforeOrAboveStartingGestureEvent = false;            }        } else {            for (int i = 0; i < count; i++) {                if (skipIndex == i) continue;                sumX += event.getX(i);                sumY += event.getY(i);            }            focusX = sumX / div;            focusY = sumY / div;        }        // Determine average deviation from focal point        float devSumX = 0, devSumY = 0;        for (int i = 0; i < count; i++) {            if (skipIndex == i) continue;            // Convert the resulting diameter into a radius.            devSumX += Math.abs(event.getX(i) - focusX);            devSumY += Math.abs(event.getY(i) - focusY);        }        final float devX = devSumX / div;        final float devY = devSumY / div;        final float spanX = devX * 2;        final float spanY = devY * 2;        final float span;        if (inAnchoredScaleMode()) {            span = spanY;        } else {            span = (float) Math.hypot(spanX, spanY);        }        // Dispatch begin/end events as needed.        // If the configuration changes, notify the app to reset its current state by beginning        // a fresh scale event stream.        final boolean wasInProgress = mInProgress;        mFocusX = focusX;        mFocusY = focusY;        if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {            mListener.onScaleEnd(this);            mInProgress = false;            mInitialSpan = span;        }        if (configChanged) {            mPrevSpanX = mCurrSpanX = spanX;            mPrevSpanY = mCurrSpanY = spanY;            mInitialSpan = mPrevSpan = mCurrSpan = span;        }        final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;        if (!mInProgress && span >=  minSpan &&                (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {            mPrevSpanX = mCurrSpanX = spanX;            mPrevSpanY = mCurrSpanY = spanY;            mPrevSpan = mCurrSpan = span;            mPrevTime = mCurrTime;            mInProgress = mListener.onScaleBegin(this);        }        // Handle motion; focal point and span/scale factor are changing.        if (action == MotionEvent.ACTION_MOVE) {            mCurrSpanX = spanX;            mCurrSpanY = spanY;            mCurrSpan = span;            boolean updatePrev = true;            if (mInProgress) {                updatePrev = mListener.onScale(this);            }            if (updatePrev) {                mPrevSpanX = mCurrSpanX;                mPrevSpanY = mCurrSpanY;                mPrevSpan = mCurrSpan;                mPrevTime = mCurrTime;            }        }        return true;    }

一堆吊毛代码,数学不太好的看起来还真比较艰难,大概就是根据多个触碰点的x坐标算出一个x轴平均值,然后y轴也一样,然后通过Math.hypot(spanX, spanY);算出斜边长,斜边长即为两点之间的距离,然后保存当前的span跟移动过后的span。

最后当我们调用getScaleFactor获取缩放比例的时候,即用现在的span/之前的span:

 public float getScaleFactor() {        if (inAnchoredScaleMode()) {            // Drag is moving up; the further away from the gesture            // start, the smaller the span should be, the closer,            // the larger the span, and therefore the larger the scale            final boolean scaleUp =                    (mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) ||                            (!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan));            final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);            return mPrevSpan <= 0 ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);        }        return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;    }

卧槽~~! 这尼玛数学渣真的是硬伤啊~~~
有了android自带的ScaleGestureDetector作为参考,我们能自己实现ScaleGestureDetector吗?? 当然github上大神已经实现了,但是如果没有的话,你能写出来么???
先写到这,未完待续…

0 0
原创粉丝点击