需求背景
滑动放大缩小:
- 滑动放大:长按拍摄模式下,往上滑动手指,可以放大镜头,手指往哪个方向滑动,就放大哪个区域;
- 双指捻合手势放大
- 滑动放大提示出现时机:长按拍摄2秒后出现提示,提示显示2秒后消失;
- 滑动放大提示:功能上线后的版本,前三次长按拍摄时均进行提示;
实现方案
手势监听器
GestureDetector
GestureDetector.OnGestureListener{
// 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发
public boolean onDown(MotionEvent e) {
Log.i("MyGesture", "onDown");
return false;
}
/*
* 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发
* 注意和onDown()的区别,强调的是没有松开或者拖动的状态
*
* 而onDown也是由一个MotionEventACTION_DOWN触发的,但是他没有任何限制,
* 也就是说当用户点击的时候,首先MotionEventACTION_DOWN,onDown就会执行,
* 如果在按下的瞬间没有松开或者是拖动的时候onShowPress就会执行,如果是按下的时间超过瞬间
* (这块我也不太清楚瞬间的时间差是多少,一般情况下都会执行onShowPress),拖动了,就不执行onShowPress。
*/
public void onShowPress(MotionEvent e) {
Log.i("MyGesture", "onShowPress");
}
// 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
///轻击一下屏幕,立刻抬起来,才会有这个触发
//从名子也可以看出,一次单独的轻击抬起操作,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以这个事件 就不再响应
public boolean onSingleTapUp(MotionEvent e) {
Log.i("MyGesture", "onSingleTapUp");
return true;
}
// 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
Log.i("MyGesture22", "onScroll:"+(e2.getX()-e1.getX()) +" "+distanceX);
return true;
}
// 用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发
public void onLongPress(MotionEvent e) {
Log.i("MyGesture", "onLongPress");
}
// 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.i("MyGesture", "onFling");
return true;
}
};
这些函数都在什么情况下才会触发呢:
-
OnDown(MotionEvent e) :用户按下屏幕就会触发;
-
onShowPress(MotionEvent e) :如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行,具体这个瞬间是多久,我也不清楚呃……
-
onLongPress(MotionEvent e) :长按触摸屏,超过一定时长,就会触发这个事件 触发顺序: onDown->onShowPress->onLongPresson
-
SingleTapUp(MotionEvent e) :从名子也可以看出,一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以也就不会触发这个事件 触发顺序 点击一下非常快的(不滑动) Touchup: onDown->onSingleTapUp->onSingleTapConfirmed 点击一下稍微慢点的(不滑动) Touchup: onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed
-
onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) :滑屏,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发 参数解释: e1:第1个ACTION_DOWN MotionEvent e2:最后一个ACTION_MOVE MotionEvent velocityX:X轴上的移动速度,像素/秒 velocityY:Y轴上的移动速度,像素/秒
-
onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY) :在屏幕上拖动事件。无论是用手拖动view,或者是以抛的动作滚动,都会多次触发,这个方法 在ACTION_MOVE动作发生时就会触发 滑屏:手指触动屏幕后,稍微滑动后立即松开 onDown-----》onScroll----》onScroll----》onScroll----》………----->onFling 拖动 onDown------》onScroll----》onScroll------》onFiling 可见,无论是滑屏,还是拖动,影响的只是中间OnScroll触发的数量多少而已,最终都会触发onFling事件!
代码示例
GestureDetector.SimpleOnGestureListener myGestureListener = new
GestureDetector.SimpleOnGestureListener();
GestureDetector mDetector = new GestureDetector(mContext, mListener);
monitorView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mDetector.onTouchEvent(event);
return false;
}
});
IGestureDetectorMonitor View手势监测器抽象接口
/**
* Author : CharlesChen
* Time : 2019/5/8.
* Desc : View手势监测器
* version : v1.0
*/
public interface IGestureDetectorMonitor {
/**
* 手机监听
*/
IGestureDetector getDetector();
/**
* 绑定监听
*
* @param monitorView 绑定需要监听的View
*/
void bindMonitorView(View monitorView);
/**
* 绑定监听
*
* @param monitorView 绑定需要监听的View
* @param listener 外部还需要监听额外的Touch时
*/
void bindMonitorView(View monitorView, final View.OnTouchListener listener);
}
GestureDetectorMonitor View手势监测器
/**
* Author : CharlesChen
* Time : 2019/5/8.
* Email : CharlesChen@kugou.net
* Desc : 手势控器
* version : v1.0
*/
public abstract class GestureDetectorMonitor implements IGestureDetectorMonitor {
protected Context mContext;
public GestureDetectorMonitor(Context context) {
this.mContext = context;
}
public void bindMonitorView(View monitorView) {
bindMonitorView(monitorView, null);
}
public void bindMonitorView(View monitorView, final View.OnTouchListener listener) {
if (monitorView == null) {
return;
}
monitorView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
onTouchEvent(event);
if (listener != null) {
return listener.onTouch(v, event);
}
return false;
}
});
}
protected boolean onTouchEvent(MotionEvent event) {
if (getDetector() != null) {
return getDetector().onTouchEvent(event);
} else {
return false;
}
}
}
CameraScrollGestureDetectorMonitor 滑动变焦监控器
/**
* Author : CharlesChen
* Time : 2019/5/8.
* Email : CharlesChen@kugou.net
* Desc : 滑动变焦监控器
* version : v1.0
*/
public class CameraScrollGestureDetectorMonitor extends ScrollGestureDetectorMonitor {
private static final String TAG = "CameraScrollGestureDetectorMonitor";
private float curRawY = 0;
private CameraZoomTrigger cameraZoomTrigger;
private float maxDistance = 1;
private OnZoomStateListener mZoomStateListener;
//开始缩放标识
private boolean isStartZoom = false;
//是否突破上次放大倍数标识
private boolean isZoomHolder = false;
//起始变焦区间 0~1
private float initialZoom = 0f;
private GestureDetector.SimpleOnGestureListener myGestureListener = new GestureDetector.SimpleOnGestureListener() {
private float initialY = 0;
private float pressHeight = SystemUtils.dip2px(ApplicationController.getApplication(), 80);
private float triggerHeight = SystemUtils.dip2px(ApplicationController.getApplication(), 40);
@Override
public void onLongPress(MotionEvent e) {
FxLog.d(TAG, " onLongPress " + e.getRawY());
//记录按下的起始点
initialY = e.getRawY();
//开始缩放标识
isStartZoom = true;
onZoomStart();
try {//由于内部判断 如果触发了长按,就不继续响应滚动事件
Field mInLongPress = GestureDetector.class.getDeclaredField("mInLongPress");
mInLongPress.setAccessible(true);
mInLongPress.setBoolean(getDetector(), false);
} catch (Exception e1) {
e1.printStackTrace();
}
super.onLongPress(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!isStartZoom) {
return false;
}
curRawY = e2.getRawY();
FxLog.d(TAG, " onScroll getRawY=" + curRawY);
//计算滑动距离 最小触发距离triggerHeight
float scrollDistance = initialY - curRawY - triggerHeight;
//转换成变焦区间 0~1
float zoomValue = CameraZoomTrigger.range(scrollDistance / (maxDistance - pressHeight));
//是在放大还是缩小 true 放大, false 缩小
boolean isIncrease = distanceY > 0;
//本次变焦区间 必须大于上次 变焦值 才可以触发相机变焦(产品逻辑)
if (initialZoom <= zoomValue) {
isZoomHolder = false;
}
onZoom(zoomValue, isIncrease);
return false;
}
};
@Override
protected boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//手指抬起或移除区域则结束本次缩放
if (isStartZoom) {
onZoomEnd();
}
isStartZoom = false;
break;
}
return super.onTouchEvent(event);
}
public CameraScrollGestureDetectorMonitor(Context context) {
super(context);
setOnGestureListener(myGestureListener);
}
public void setScrollMaxDistance(int maxDistance) {
if (maxDistance <= 0) {
throw new RuntimeException("maxDistance 不能为小于或等于0的数");
}
this.maxDistance = maxDistance;
}
public void setCameraZoomTrigger(CameraZoomTrigger cameraZoomTrigger) {
this.cameraZoomTrigger = cameraZoomTrigger;
}
private void onZoomStart() {
if (mZoomStateListener != null) {
mZoomStateListener.onZoomStarted();
}
if (cameraZoomTrigger != null) {//记录按下的当前变焦区间
initialZoom = cameraZoomTrigger.getCameraZoom();
}
isZoomHolder = true;
}
private void onZoom(float zoomValue, boolean isIncrease) {
FxLog.d(TAG, " onZoom zoomValue = " + zoomValue + " isIncrease =" + isIncrease);
//本次变焦区间 必须大于上次 变焦值 才可以触发相机变焦(产品逻辑)
if (!isZoomHolder && cameraZoomTrigger != null) {
cameraZoomTrigger.setCameraZoom(zoomValue, isIncrease);
}
if (mZoomStateListener != null) {
mZoomStateListener.onZoom();
}
}
private void onZoomEnd() {
FxLog.d(TAG, " onZoomEnd");
if (mZoomStateListener != null) {
mZoomStateListener.onZoomEnded();
}
}
public void setZoomStateListener(OnZoomStateListener mZoomStateListener) {
this.mZoomStateListener = mZoomStateListener;
}
public interface OnZoomStateListener {
public void onZoomStarted();
public void onZoom();
public void onZoomEnded();
}
}
CameraScaleGestureDetectorMonitor 双指变焦监控器
/**
* Author : CharlesChen
* Time : 2019/5/8.
* Desc : 双指变焦监控器
* version : v1.0
*/
public class CameraScaleGestureDetectorMonitor extends ScaleGestureDetectorMonitor {
/**
* 最大放大倍数
*/
public static final float SCALE_MAX = 4.0f;
private final int mHeight;
private final int mWidth;
/**
* 默认缩放
*/
private float initScale = 1.0f;
private Matrix scaleMatrix = new Matrix();
/**
* 处理矩阵的9个值
*/
private float[] martiXValue = new float[9];
private final ScaleGestureDetector.SimpleOnScaleGestureListener listener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
private static final String TAG = "CameraScaleGesture";
//缩放开始
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
if (cameraZoomTrigger != null) {
//获取当前相机放大倍数
float zoom = cameraZoomTrigger.getCameraZoom();
//转换成缩放区间 0~1
float scale = calculateScale(zoom);
FxLog.d(TAG, " onScaleBegin scale =" + scale + " zoom = " + zoom);
//同步当前缩放倍数
scaleMatrix.reset();
scaleMatrix.postScale(scale, scale, mWidth, mHeight);
}
if (scaleGestureListener != null) {
scaleGestureListener.onScaleBegin(detector);
}
return super.onScaleBegin(detector);
}
//缩放结束
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
super.onScaleEnd(detector);
if (scaleGestureListener != null) {
scaleGestureListener.onScaleEnd(detector);
}
}
//缩放中
@Override
public boolean onScale(ScaleGestureDetector detector) {
//获取当前矩阵缩放比例
float scale = getScale();
//获取缩放因子
float scaleFactor = detector.getScaleFactor();
//计算当前的缩放区间 0~1
float zoomValue = calculatePercent(scale);
//是在放大还是缩小 true 放大, false 缩小
boolean isIncrease = scaleFactor > 1;
//通知相机缩放
if (cameraZoomTrigger != null) {
cameraZoomTrigger.setCameraZoom(zoomValue, isIncrease);
}
//计算矩阵缩放值
if (scaleFactor * scale < initScale)
scaleFactor = initScale / scale;
if (scaleFactor * scale > SCALE_MAX)
scaleFactor = SCALE_MAX / scale;
FxLog.d(TAG, " scale =" + scale + " scaleFactor = " + scaleFactor + " zoomValue= " + zoomValue);
//设置缩放比例
scaleMatrix.postScale(scaleFactor, scaleFactor, mWidth, mHeight);
if (scaleGestureListener != null) {
scaleGestureListener.onScale(detector);
}
return true;
}
//计算当前的缩放区间 0~1
private float calculatePercent(float scale) {
return CameraZoomTrigger.range((scale - initScale) / (SCALE_MAX - initScale));
}
//区间转换成缩放倍数
private float calculateScale(float zoomValue) {
return zoomValue * (SCALE_MAX - initScale) + initScale;
}
};
private CameraZoomTrigger cameraZoomTrigger;
private ScaleGestureDetector.OnScaleGestureListener scaleGestureListener;
public CameraScaleGestureDetectorMonitor(Context context) {
super(context);
mHeight = SystemUtils.getScreenHeight(mContext) / 2;
mWidth = SystemUtils.getScreenWidth(mContext) / 2;
setOnGestureListener(listener);
}
/**
* 获取当前矩阵缩放比例
*/
public float getScale() {
scaleMatrix.getValues(martiXValue);
return martiXValue[Matrix.MSCALE_X];
}
/**
* 设置相机变焦触发器
* @param cameraZoomTrigger 相机变焦触发器
*/
public void setCameraZoomTrigger(CameraZoomTrigger cameraZoomTrigger) {
this.cameraZoomTrigger = cameraZoomTrigger;
}
/**
* 设置相机缩放监听
* @param scaleGestureListener
*/
public void setOnScaleGestureListener(ScaleGestureDetector.OnScaleGestureListener scaleGestureListener) {
this.scaleGestureListener = scaleGestureListener;
}
}
CameraZoomTrigger 相机变焦的触发器
/**
* Author : CharlesChen
* Time : 2019/5/9.
* Desc : 相机变焦的触发器
* version : v1.0
*/
public class CameraZoomTrigger {
private OnZoomListener listener;
public CameraZoomTrigger(OnZoomListener listener){
this.listener = listener;
}
public void setCameraZoom(float zoomValue, boolean isIncrease) {
if (listener != null) {
listener.onZoom(range(zoomValue), isIncrease);
}
}
public float getCameraZoom() {
if (listener != null) {
return listener.getCameraZoom();
}
return 0;
}
/**
* @param value 取[0~1]区间内的值
* @return
*/
public static float range(float value) {
return Math.min(Math.max(value, 0f), 1f);
}
public interface OnZoomListener {
void onZoom(float zoomValue, boolean isIncrease);
float getCameraZoom();
}
}