Android 手势检测

手势检测

Android 手势检测,也就是对 GestureDetector 的使用。
而 GestureDetector 则可以使用 MotionEvents 检测各种手势和事件。
GestureDetector.OnGestureListener 是个回调方法,在发生特定事件时会调用 Listener 中对应的方法回调。
这个类只能用于检测触摸事件的 MotionEvent,不能用于轨迹球事件。

使用步骤

  1. 创建一个 GestrueDetector 实例
  2. 在 onTouchEvent(MontionEvent) 方法中,确保调用 GestrueDetector 实例的onTouchEvent(MotionEvent)。回调中定义的方法将在事件发生时执行。
  3. 如果侦听 onContextClick(MotionEvent),则必须在 View 的 onGenericMotionEvent(MotionEvent)中调用 GestureDetector OnGenericMotionEvent(MotionEvent)。

GestureDetector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package android.view;

import android.content.Context;
import android.os.Handler;

public class GestureDetector {

/**
* GestureDetector 共有5种构造函数
* 但前两种已舍弃,一种是重复的。
* 所以,只需要关注两种。
**/

/**
* 已舍弃
**/
@Deprecated
public GestureDetector(GestureDetector.OnGestureListener listener, Handler handler) {
throw new RuntimeException("Stub!");
}

/**
* 已舍弃
**/
@Deprecated
public GestureDetector(GestureDetector.OnGestureListener listener) {
throw new RuntimeException("Stub!");
}

/**
* 关注这个构造函数
* context:上下文
* listener:手势监听器
**/
public GestureDetector(Context context, GestureDetector.OnGestureListener listener) {
throw new RuntimeException("Stub!");
}

/**
* 关注这个构造函数
* context:上下文
* listener:手势监听器
* handler:用于给 GestureDetector 提供一个Looper
* 由于GestureDetector会在内部自动创建一个Handler用于处理数据,如果是在主线程创建的GestureDetector,其内部的Hanlder会自动获得主线程的Looper。
* 但如果是在一个没有创建Looper的子线程中创建的GestureDetector,则需要传递一个带有Looper的Handler给它,否则会因为无法获取到Looper导致创建失败。
**/
public GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler) {
throw new RuntimeException("Stub!");
}

public GestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler, boolean unused) {
throw new RuntimeException("Stub!");
}

public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) {
throw new RuntimeException("Stub!");
}

public void setContextClickListener(GestureDetector.OnContextClickListener onContextClickListener) {
throw new RuntimeException("Stub!");
}

public void setIsLongpressEnabled(boolean isLongpressEnabled) {
throw new RuntimeException("Stub!");
}

public boolean isLongpressEnabled() {
throw new RuntimeException("Stub!");
}

public boolean onTouchEvent(MotionEvent ev) {
throw new RuntimeException("Stub!");
}

public boolean onGenericMotionEvent(MotionEvent ev) {
throw new RuntimeException("Stub!");
}

/**
* 四种手势监听器:
* OnContextClickListener
* OnDoubleTapListener
* OnGestureListener
* SimpleOnGestureListener
**/

/**
* 另外三个接口的空实现。
* 一般情况下,使用这个就好了,比较方便。
**/
public static class SimpleOnGestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, GestureDetector.OnContextClickListener {
public SimpleOnGestureListener() {
throw new RuntimeException("Stub!");
}

public boolean onSingleTapUp(MotionEvent e) {
throw new RuntimeException("Stub!");
}

public void onLongPress(MotionEvent e) {
throw new RuntimeException("Stub!");
}

public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
throw new RuntimeException("Stub!");
}

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
throw new RuntimeException("Stub!");
}

public void onShowPress(MotionEvent e) {
throw new RuntimeException("Stub!");
}

public boolean onDown(MotionEvent e) {
throw new RuntimeException("Stub!");
}

public boolean onDoubleTap(MotionEvent e) {
throw new RuntimeException("Stub!");
}

public boolean onDoubleTapEvent(MotionEvent e) {
throw new RuntimeException("Stub!");
}

public boolean onSingleTapConfirmed(MotionEvent e) {
throw new RuntimeException("Stub!");
}

public boolean onContextClick(MotionEvent e) {
throw new RuntimeException("Stub!");
}
}

/**
* Android6.0(API23)添加的
* 用于检测外部设备上的按钮是否按下的,
* 例如蓝牙触控笔上的按钮,一般情况下,忽略即可。
**/
public interface OnContextClickListener {
boolean onContextClick(MotionEvent var1);
}

/**
* 双击事件,有三个回调类型:
* 双击(DoubleTap)、单击确认(SingleTapConfirmed) 和 双击事件回调(DoubleTapEvent)
**/
public interface OnDoubleTapListener {
boolean onSingleTapConfirmed(MotionEvent var1);

boolean onDoubleTap(MotionEvent var1);

boolean onDoubleTapEvent(MotionEvent var1);
}

/**
* 手势检测,主要有以下类型事件:
* 按下(Down)、 抛(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress) 和 单击抬起(SingleTapUp)
**/
public interface OnGestureListener {

// 保证控件拥有消费事件的能力,以接受后续的事件。
boolean onDown(MotionEvent var1);

// 监听用户按下时
// 延迟回调,延迟时间180ms
// 假如用户手指按下后立即抬起或者事件立即被拦截,时间没有超过 180 ms的话,这条消息会被 remove 掉
void onShowPress(MotionEvent var1);

// 监听用户单击抬起时
// 区别在于双击时的触发次数:
// onSingleTapUp:1次(在双击的第一次抬起时触发)
// onSingleTapConfirmed:0次(双击发生时不会触发)
// onClick:2次(在双击事件时触发两次)
//
// 触发顺序如下:
// onSingleTapUp
// onClick
// onDoubleTap // <- 双击
// onClick
boolean onSingleTapUp(MotionEvent var1);

// 监听滚动事件
// var1:手指按下时的Event
// var2:手指提起时的Event
// var3:在 X 轴上划过的距离
// var4:在 Y 轴上划过的距离
boolean onScroll(MotionEvent var1, MotionEvent var2, float var3, float var4);

// 检测长按事件
void onLongPress(MotionEvent var1);

// 扔,抛,常见与列表中
// var1:手指按下时的Event
// var2:手指提起时的Event
// var3:在 X 轴上的运动速度(像素/秒)
// var4:在 Y 轴上的运动速度(像素/秒)
boolean onFling(MotionEvent var1, MotionEvent var2, float var3, float var4);
}
}

示例

使用无 Handler 的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 创建一个监听回调
final GestureDetector.SimpleOnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener(){
/**
* 单击事件发生后300ms后触发
*/
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// 如果想同时监听双击与单击事件,则监听单击事件不推荐使用OnClickListener,原因有二:
// 一,它们是存在冲突的,若要同时出发,则 setOnTouchListener 不能消费事件,如果 onTouchListener 消费了事件,就可能导致 OnClick 无法正常触发。
// 二,使用 OnClickListener 会在双击事件发生时触发两次
Toast.makeText(MainActivity.this,"不要单击",Toast.LENGTH_SHORT).show();
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(MainActivity.this,"双击666",Toast.LENGTH_SHORT).show();
Log.e("TAG","第二次按下时触发");
return super.onDoubleTap(e);
}

/**
* 与 onDoubleTap 的差别很小
* onDoubleTapEvent 用来进行更细微的控制
*
* 函数的执行顺序如下:
* onDoubleTap
* onDoubleTapEvent - down
* onDoubleTapEvent - move
* onDoubleTapEvent - up
*/
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
switch (e.getActionMasked()){
case MotionEvent.ACTION_DOWN:
Log.e("TAG","ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e("TAG","ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e("TAG","第二次抬起时触发");
break;
default:
break;
}
return super.onDoubleTapEvent(e);
}
};

// 创建一个检测器
GestureDetector gestureDetector;

private void initData() {
// 使用无Handler的构造函数
gestureDetector = new GestureDetector(this,onGestureListener);

// 给监听器设置数据源
btn_doubleTap.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return gestureDetector.onTouchEvent(motionEvent);
}
});
}

使用有 Handler 的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* 由于GestureDetector会在内部自动创建一个Handler用于处理数据,
* 如果是在主线程创建的GestureDetector,其内部的Hanlder会自动获得主线程的Looper。
* 可以直接使用无Handler的构造函数
*
* 但如果是在一个没有创建Looper的子线程中创建的GestureDetector,
* 则需要传递一个带有Looper的Handler给它,否则会因为无法获取到Looper导致创建失败。
* 需要使用带Handler的构造函数
**/

// 创建一个监听回调
final GestureDetector.SimpleOnGestureListener onGestureListener = new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onDoubleTap(MotionEvent e) {
Toast.makeText(MainActivity.this,"双击666",Toast.LENGTH_SHORT).show();
return super.onDoubleTap(e);
}
};
// 创建一个检测器
GestureDetector gestureDetector;
private void initData() {

// 使用有Handler的构造函数的几种方式;
// 1.主线程中创建Handler
// 重点在于传递的 Handler 一定要有 Looper
final Handler handler = new Handler();
// 主线程创建的hanlder会自动关联主线程的Looper
new Thread(new Runnable() {
@Override
public void run() {
gestureDetector = new GestureDetector(MainActivity.this,onGestureListener,handler);
}
}).start();

// 2.子线程中创建Handler
// 重点在于传递的 Handler 一定要有 Looper
new Thread(new Runnable() {
@Override
public void run() {
Handler handler1 = new Handler(Looper.getMainLooper());
gestureDetector = new GestureDetector(MainActivity.this,onGestureListener,handler1);
}
}).start();

// 3.若子线程准备了 Looper 那么可以直接使用无Handler的构造函数进行创建
// 重点在于传递的 Handler 一定要有 Looper
new Thread(new Runnable() {
@Override public void run() {
Looper.prepare(); // <- 重点在这里
gestureDetector = new GestureDetector(MainActivity.this,onGestureListener);
}
}).start();

// 给监听器设置数据源
btn_doubleTap.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return gestureDetector.onTouchEvent(motionEvent);
}
});
}

相关方法

除了各类监听器之外,还有几个相关的方法

方法 说明
setIsLongpressEnabled 是否允许触发长按事件,true 表示允许,false 表示不允许。
isLongpressEnabled 判断当前是否允许触发长按事件,true 表示允许,false 表示不允许。
onTouchEvent 这个是其中一个重要的方法,在最开始已经演示过使用方式了。
onGenericMotionEvent 这个是在 API 23 之后才添加的内容,主要是为 OnContextClickListener 服务的,暂时不用关注。
setContextClickListener 设置 ContextClickListener 。
setOnDoubleTapListener 设置 OnDoubleTapListener 。

缩放手势检测

Android 缩放手势检测,ScaleGestureDetector
一般来讲,缩放手势都不是单独存在的。
如果用在自定义控件上,则配合 Matrix 相关内容使用更好。
使用方式与 GestureDetector 类似,也是通过 Listener 进行监听用户的操作手势,它是对缩放手势进行了一次封装。

ScaleGestureDetector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package android.view;

import android.content.Context;
import android.os.Handler;

public class ScaleGestureDetector {

/**
* 有两个构造函数
**/
public ScaleGestureDetector(Context context, ScaleGestureDetector.OnScaleGestureListener listener) {
throw new RuntimeException("Stub!");
}

public ScaleGestureDetector(Context context, ScaleGestureDetector.OnScaleGestureListener listener, Handler handler) {
throw new RuntimeException("Stub!");
}

public boolean onTouchEvent(MotionEvent event) {
throw new RuntimeException("Stub!");
}

public void setQuickScaleEnabled(boolean scales) {
throw new RuntimeException("Stub!");
}

public boolean isQuickScaleEnabled() {
throw new RuntimeException("Stub!");
}

public void setStylusScaleEnabled(boolean scales) {
throw new RuntimeException("Stub!");
}

public boolean isStylusScaleEnabled() {
throw new RuntimeException("Stub!");
}

public boolean isInProgress() {
throw new RuntimeException("Stub!");
}

public float getFocusX() {
throw new RuntimeException("Stub!");
}

public float getFocusY() {
throw new RuntimeException("Stub!");
}

public float getCurrentSpan() {
throw new RuntimeException("Stub!");
}

public float getCurrentSpanX() {
throw new RuntimeException("Stub!");
}

public float getCurrentSpanY() {
throw new RuntimeException("Stub!");
}

public float getPreviousSpan() {
throw new RuntimeException("Stub!");
}

public float getPreviousSpanX() {
throw new RuntimeException("Stub!");
}

public float getPreviousSpanY() {
throw new RuntimeException("Stub!");
}

public float getScaleFactor() {
throw new RuntimeException("Stub!");
}

public long getTimeDelta() {
throw new RuntimeException("Stub!");
}

public long getEventTime() {
throw new RuntimeException("Stub!");
}

/**
* 两个手势监听器
* SimpleOnScaleGestureListener:缩放手势检测器的空实现
* OnScaleGestureListener:缩放手势检测器
**/

public static class SimpleOnScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
public SimpleOnScaleGestureListener() {
throw new RuntimeException("Stub!");
}

public boolean onScale(ScaleGestureDetector detector) {
throw new RuntimeException("Stub!");
}

public boolean onScaleBegin(ScaleGestureDetector detector) {
throw new RuntimeException("Stub!");
}

public void onScaleEnd(ScaleGestureDetector detector) {
throw new RuntimeException("Stub!");
}
}

public interface OnScaleGestureListener {

// 缩放被触发(会调用0次或者多次),如果返回 true 则表示当前缩放事件已经被处理,检测器会重新积累缩放因子,返回 false 则会继续积累缩放因子。
boolean onScale(ScaleGestureDetector var1);

// 缩放手势开始,当两个手指放在屏幕上的时候会调用该方法(只调用一次)。如果返回 false 则表示不使用当前这次缩放手势。
boolean onScaleBegin(ScaleGestureDetector var1);

// 缩放手势结束
void onScaleEnd(ScaleGestureDetector var1);
}
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class ScaleGestureDemoView extends View{

private static final String TAG="ScaleGestureDemoView";

private ScaleGestureDetector scaleGestureDetector;

public ScaleGestureDemoView(Context context) {
super(context);
init();
}

public ScaleGestureDemoView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}

private void init() {
scaleGestureDetector=new ScaleGestureDetector(getContext(),new ScaleGestureDetector.SimpleOnScaleGestureListener(){
@Override
public boolean onScale(ScaleGestureDetector detector) {
// 缩放手势触发
// 主要关心这两个值,缩放中心点和缩放因子。
// 中心点:将所有的坐标都加起来,然后除以数量。
// 缩放因子:计算各个手指到焦点的平均距离,在用户手指移动后用新的平均距离除以旧的平均距离,并以此计算得出缩放比例。
Log.e(TAG, "focusX = " + detector.getFocusX()); // 缩放中心,x坐标
Log.e(TAG, "focusY = " + detector.getFocusY()); // 缩放中心y坐标
Log.e(TAG, "scale = " + detector.getScaleFactor()); // 缩放因子
return true;
}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
// 缩放手势开始
Log.e(TAG,"缩放手势开始");
return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// super.onScaleEnd(detector);
// 缩放手势结束
Log.e(TAG,"缩放手势结束");
}
});
}

@Override
public boolean onTouchEvent(MotionEvent event) {
scaleGestureDetector.onTouchEvent(event);
return true;
}
}

综合示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
public class GestureDemoView extends View {

GestureDetector mGestureDetector;
ScaleGestureDetector mScaleGestureDetector;

// 画布当前的 Matrix, 用于获取当前画布的一些状态信息,例如缩放大小,平移距离等
private Matrix mCanvasMatrix = new Matrix();

// 将用户触摸的坐标转换为画布上坐标所需的 Matrix, 以便找到正确的缩放中心位置
private Matrix mInvertMatrix = new Matrix();

// 所有用户触发的缩放、平移等操作都通过下面的 Matrix 直接作用于画布上,
// 将系统计算的一些初始缩放平移信息与用户操作的信息进行隔离,让操作更加直观
private Matrix mUserMatrix = new Matrix();

private Bitmap mBitmap;

// 基础的缩放和平移信息,该信息与用户的手势操作无关
private float mBaseScale;
private float mBaseTranslateX;
private float mBaseTranslateY;

private Paint mPaint;

public GestureDemoView(Context context) {
super(context);
}

public GestureDemoView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
initGesture(context);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);

if (mBitmap.getWidth() * 1.0f / mBitmap.getHeight() > w * 1.0f / h) {
mBaseScale = w * 1.0f / mBitmap.getWidth();
mBaseTranslateX = 0;
mBaseTranslateY = (h - mBitmap.getHeight() * mBaseScale) / 2;
} else {
mBaseScale = h * 1.0f / mBitmap.getHeight() * 1.0f;
mBaseTranslateX = (w - mBitmap.getWidth() * mBaseScale) / 2;
mBaseTranslateY = 0;
}

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(6);
canvas.translate(mBaseTranslateX, mBaseTranslateY);
canvas.scale(mBaseScale, mBaseScale);

canvas.save();
canvas.concat(mUserMatrix);

mCanvasMatrix = canvas.getMatrix();
mCanvasMatrix.invert(mInvertMatrix);

canvas.drawBitmap(mBitmap, 0, 0, mPaint);
canvas.restore();
}


//--- 手势处理 ----------------------------------------------------------------------------------

private void initGesture(Context context) {
mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
float scale = getMatrixValue(MSCALE_X, mCanvasMatrix);
mUserMatrix.preTranslate(-distanceX / scale, -distanceY / scale);
//fixTranslate(); // 在用户滚动时不进行修正,保证用户滚动时也有响应, 在用户抬起手指后进行修正
invalidate();
return true;
}

@Override
public boolean onDoubleTap(MotionEvent e) {
if (!mUserMatrix.isIdentity()) {
mUserMatrix.reset();
} else {
float[] points = mapPoint(e.getX(), e.getY(), mInvertMatrix);
mUserMatrix.postScale(MAX_SCALE, MAX_SCALE, points[0], points[1]);
}
fixTranslate();
invalidate();
return true;
}
});

mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
float fx = detector.getFocusX();
float fy = detector.getFocusY();
float[] points = mapPoint(fx, fy, mInvertMatrix);
scaleFactor = getRealScaleFactor(scaleFactor);
mUserMatrix.preScale(scaleFactor, scaleFactor, points[0], points[1]);
fixTranslate();
invalidate();
return true;
}

});
}

// 修正缩放
private void fixTranslate() {
// 对 Matrix 进行预计算,并根据计算结果进行修正
Matrix viewMatrix = getMatrix(); // 获取当前控件的Matrix
viewMatrix.preTranslate(mBaseTranslateX, mBaseTranslateY);
viewMatrix.preScale(mBaseScale, mBaseScale);
viewMatrix.preConcat(mUserMatrix);
Matrix invert = new Matrix();
viewMatrix.invert(invert);
Rect rect = new Rect();
getGlobalVisibleRect(rect);

float userScale = getMatrixValue(MSCALE_X, mUserMatrix);
float scale = getMatrixValue(MSCALE_X, viewMatrix);

float[] center = mapPoint(mBitmap.getWidth() / 2.0f, mBitmap.getHeight() / 2.0f, viewMatrix);
float distanceX = center[0] - getWidth() / 2.0f;
float distanceY = center[1] - getHeight() / 2.0f;
float[] wh = mapVectors(mBitmap.getWidth(), mBitmap.getHeight(), viewMatrix);

if (userScale <= 1.0f) {
mUserMatrix.preTranslate(-distanceX / scale, -distanceY / scale);
} else {
float[] lefttop = mapPoint(0, 0, viewMatrix);
float[] rightbottom = mapPoint(mBitmap.getWidth(), mBitmap.getHeight(), viewMatrix);

// 如果宽度小于总宽度,则水平居中
if (wh[0] < getWidth()) {
mUserMatrix.preTranslate(distanceX / scale, 0);
} else {
if (lefttop[0] > 0) {
mUserMatrix.preTranslate(-lefttop[0] / scale, 0);
} else if (rightbottom[0] < getWidth()) {
mUserMatrix.preTranslate((getWidth() - rightbottom[0]) / scale, 0);
}

}
// 如果高度小于总高度,则垂直居中
if (wh[1] < getHeight()) {
mUserMatrix.preTranslate(0, -distanceY / scale);
} else {
if (lefttop[1] > 0) {
mUserMatrix.preTranslate(0, -lefttop[1] / scale);
} else if (rightbottom[1] < getHeight()) {
mUserMatrix.preTranslate(0, (getHeight() - rightbottom[1]) / scale);
}
}
}
invalidate();
}

@Override
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
mScaleGestureDetector.onTouchEvent(event);
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
fixTranslate();
}
return true;
}


//--- Tools ------------------------------------------------------------------------------------

//--- 将坐标转换为画布坐标 ---
private float[] mapPoint(float x, float y, Matrix matrix) {
float[] temp = new float[2];
temp[0] = x;
temp[1] = y;
matrix.mapPoints(temp);
return temp;
}

private float[] mapVectors(float x, float y, Matrix matrix) {
float[] temp = new float[2];
temp[0] = x;
temp[1] = y;
matrix.mapVectors(temp);
return temp;
}


//--- 获取 Matrix 中的属性 ---
private float[] matrixValues = new float[9];
private static final int MSCALE_X = 0, MSKEW_X = 1, MTRANS_X = 2;
private static final int MSKEW_Y = 3, MSCALE_Y = 4, MTRANS_Y = 5;
private static final int MPERSP_0 = 6, MPERSP_1 = 7, MPERSP_2 = 8;

@IntDef({MSCALE_X, MSKEW_X, MTRANS_X, MSKEW_Y, MSCALE_Y, MTRANS_Y, MPERSP_0, MPERSP_1, MPERSP_2})
@Retention(RetentionPolicy.SOURCE)
private @interface MatrixName {}

private float getMatrixValue(@MatrixName int name, Matrix matrix) {
matrix.getValues(matrixValues);
return matrixValues[name];
}

//--- 限制缩放比例 ---
private static final float MAX_SCALE = 4.0f; //最大缩放比例
private static final float MIN_SCALE = 0.5f; // 最小缩放比例

private float getRealScaleFactor(float currentScaleFactor) {
float realScale = 1.0f;
float userScale = getMatrixValue(MSCALE_X, mUserMatrix); // 用户当前的缩放比例
float theoryScale = userScale * currentScaleFactor; // 理论缩放数值

// 如果用户在执行放大操作并且理论缩放数据大于4.0
if (currentScaleFactor > 1.0f && theoryScale > MAX_SCALE) {
realScale = MAX_SCALE / userScale;
} else if (currentScaleFactor < 1.0f && theoryScale < MIN_SCALE) {
realScale = MIN_SCALE / userScale;
} else {
realScale = currentScaleFactor;
}
return realScale;
}
}


备注

参考资料:

手势检测

缩放手势检测

传送门:GitHub

欢迎关注微信公众号:非也缘也