接着上一节 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上大神已经实现了,但是如果没有的话,你能写出来么?
先写到这,未完待续。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# Android
# 手势
# ImageView
# Android手势ImageView三部曲 第二部
# Android手势ImageView三部曲 第一部
# Android自定义GestureDetector实现手势ImageView
# Android使用ImageView实现支持手势缩放效果
# Android ImageView随手势变化动态缩放图片
# Android手势滑动实现ImageView缩放图片大小
# Android实现手势控制ImageView图片大小
# Android通过手势实现的缩放处理实例代码
# android开发之为activity增加左右手势识别示例
# android使用gesturedetector手势识别示例分享
# 回调
# 自己的
# 按下
# 自带
# 小伙伴
# 创建一个
# 的是
# 实现了
# 多点
# 多个
# 中有
# 坐标值
# 会在
# 不太好
# 你能
# 大神
# 如果没有
# 我以
# 较多
# 写了
相关文章:
招商网站制作流程,网站招商广告语?
长沙企业网站制作哪家好,长沙水业集团官方网站?
如何用搬瓦工VPS快速搭建个人网站?
如何挑选高效建站主机与优质域名?
c++怎么实现高并发下的无锁队列_c++ std::atomic原子变量与CAS操作【详解】
我的世界制作壁纸网站下载,手机怎么换我的世界壁纸?
建站之星后台管理系统如何操作?
在线制作视频的网站有哪些,电脑如何制作视频短片?
相亲简历制作网站推荐大全,新相亲大会主持人小萍萍资料?
威客平台建站流程解析:高效搭建教程与设计优化方案
建站之星上传入口如何快速找到?
Swift开发中switch语句值绑定模式
建站之星免费版是否永久可用?
专业的网站制作设计是什么,如何制作一个企业网站,建设网站的基本步骤有哪些?
建站之星logo尺寸如何设置最合适?
北京企业网站设计制作公司,北京铁路集团官方网站?
C++如何编写函数模板?(泛型编程入门)
上海网站制作网站建设公司,建筑电工证网上查询系统入口?
一键制作网站软件下载安装,一键自动采集网页文档制作步骤?
如何用5美元大硬盘VPS安全高效搭建个人网站?
建站之星安装失败:服务器环境不兼容?
厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?
公司网站的制作公司,企业网站制作基本流程有哪些?
如何获取开源自助建站系统免费下载链接?
如何彻底删除建站之星生成的Banner?
网站制作专业公司有哪些,如何制作一个企业网站,建设网站的基本步骤有哪些?
如何制作一个表白网站视频,关于勇敢表白的小标题?
如何生成腾讯云建站专用兑换码?
如何在Mac上搭建Golang开发环境_使用Homebrew安装和管理Go版本
如何自定义建站之星模板颜色并下载新样式?
c# F# 的 MailboxProcessor 和 C# 的 Actor 模型
如何在万网主机上快速搭建网站?
行程制作网站有哪些,第三方机票电子行程单怎么开?
无锡营销型网站制作公司,无锡网选车牌流程?
图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?
如何在西部数码注册域名并快速搭建网站?
视频网站制作教程,怎么样制作优酷网的小视频?
,怎么用自己头像做动态表情包?
保定网站制作方案定制,保定招聘的渠道有哪些?找工作的人一般都去哪里看招聘信息?
如何在IIS中新建站点并解决端口绑定冲突?
如何在腾讯云服务器上快速搭建个人网站?
深圳 网站制作,深圳招聘网站哪个比较好一点啊?
如何快速建站并高效导出源代码?
广州营销型建站服务商推荐:技术优势与SEO优化解析
网站建设设计制作营销公司南阳,如何策划设计和建设网站?
怎么用手机制作网站链接,dw怎么把手机适应页面变成网页?
建站主机助手选型指南:2025年热门推荐与高效部署技巧
网站制作网站,深圳做网站哪家比较好?
建站之星安装路径如何正确选择及配置?
Swift中swift中的switch 语句
*请认真填写需求信息,我们会在24小时内与您取得联系。