Android手势ImageView三部曲 第三部

接着上一节 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上大神已经实现了,但是如果没有的话,你能写出来么?

先写到这,未完待续。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

时间: 2024-07-30 21:14:00

Android手势ImageView三部曲 第三部的相关文章

Android手势ImageView三部曲 第二部

废话不多说了,还记得上一节Android手势ImageView三部曲(一)最后我们提及的那个框架么?这一节我们重点了掌握一下GestureDetector这个类相关的属性方法. 一.那么GestureDetector是干嘛的呢? 顾名思义,字面意思就是"手势检测器"的意思,还记得我们上一节中实现的GestureImageView么?我们在onTouchEvent中检测到了各种个样的手势(手指按下.抬起.什么时候属于拖拽.什么时候属于缩放)都是通过我们的计算得到的,但是有了Gesture

Android手势ImageView三部曲 第一部

这几天一直在研究github上的PhotoView跟GestureImageView,发现写的都很牛,看了很久的代码,于是打算把自己所看的一些东西总结一下,内容还是很多的,但是很有含金量哈-- 先附上两个开源项目的链接: GestureImageView: https://github.com/jasonpolites/gesture-imageview PhotoView:https://github.com/chrisbanes/PhotoView 这样说有点乏味哈,先看看我们今天要实现的效

Android手势滑动实现ImageView缩放图片大小_Android

本文推出了两种Android手势实现ImageView缩放图片大小的方法,分享给大家供大家参考,具体内容如下 方法一:将以下代码写到MulitPointTouchListener.java中,然后对你相应的图片进行OnTouchListener. 例如:imageView.setOnTouchListener(new MulitPointTouchListener ()); 在xml中要将ImageView的缩放格式改成Matrix 例如:android:scaleType="matrix&qu

Android使用ImageView实现支持手势缩放效果_Android

TouchImageView继承自ImageView具有ImageView的所有功能:除此之外,还有缩放.拖拽.双击放大等功能,支持viewpager和scaletype,并伴有动画效果. sharedConstructing private void sharedConstructing(Context context) { super.setClickable(true); this.context = context; mScaleDetector = new ScaleGestureDe

Android手势滑动实现ImageView缩放图片大小

本文推出了两种Android手势实现ImageView缩放图片大小的方法,分享给大家供大家参考,具体内容如下 方法一: 将以下代码写到MulitPointTouchListener.java中,然后对你相应的图片进行OnTouchListener. 例如:imageView.setOnTouchListener(new MulitPointTouchListener ()); 在xml中要将ImageView的缩放格式改成Matrix 例如:android:scaleType="matrix&q

Android手势控制实现缩放、移动图片_Android

本文实例为大家分享了Android手势控制实现缩放.移动图片的方法,供大家参考,具体内容如下 新建一个触摸监听器类 package com.liyong.btprinter; import android.graphics.Matrix; import android.graphics.PointF; import android.util.FloatMath; import android.view.MotionEvent; import android.view.View; import a

Android 手势操作编程详解_Android

      手势操作在我们使用智能设备的过程中奉献了不一样的体验.Android开发中必然会进行手势操作方面的编程.那么它的原理是怎样的呢?我们如何进行手势操作编程呢?        手势操作原理        首先,在Android系统中,每一次手势交互都会依照以下顺序执行.        1. 接触接触屏一刹那,触发一个MotionEvent事件.        2. 该事件被OnTouchListener监听,在其onTouch()方法里获得该MotionEvent对象.        3

Android手势密码的实现_Android

一.大致界面介绍: 图1 图2 图3 图4 图1:手势密码绘制界面 [主要是绘制上方的9个提示图标和9个宫格密码图标] 图2:设置手势密码 [监听手势的输入,TouchEvent的事件处理,获取输入的手势密码,同时显示在上方的提示区域] 图3:再绘制一次,两次密码不一致提示界面 [这里在实现的时候,错误提示文字加了"左右晃动的动画",错误路径颜色标记为红色] 图4:校验手势密码,输入的密码错误,给予红色路径+错误文字提示 二.实现思路: 1. 正上方的提示区域,用一个类(LockInd

Android手势滑动GestureDetector和OnGestureListener(二)

main.xml如下: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" >