一、多点触控
当多点同时触摸屏幕时,系统将会产生如下的触摸事件:
1.ACTION_DOWN:触摸屏幕的第一个点。此时手势开始。该点的数据通常在MotionEvent事件队列索引位置0处。
2.ACTION_POINTER_DOWN:除了第一个点的其他触摸点数据。该点的数据的索引位置由getActionIndex()方法返回。
3.ACTION_MOVE:在手势过程中发生的一次变化。
4.ACTION_POINTER_UP:当不是第一个点的其他点UP后触发。
5.ACTION_UP:当手势中的最后一个点离开屏幕。
简单测试范例
布局xml代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<RelativeLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools=
"http://schemas.android.com/tools"
android:id=
"@+id/layout1"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:tag=
"true布局"
tools:context=
".MainActivity"
>
<TextView
android:id=
"@+id/message"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:tag=
"true控件"
android:text=
"@string/hello_world"
/>
</RelativeLayout>
|
java代码如下:
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
|
package
com.lonshine.d_touchduodian;
import
android.os.Bundle;
import
android.app.Activity;
import
android.util.Log;
import
android.view.MotionEvent;
import
android.view.View;
import
android.view.View.OnTouchListener;
import
android.widget.RelativeLayout;
public
class
MainActivity
extends
Activity
implements
OnTouchListener
{
@Override
protected
void
onCreate(Bundle savedInstanceState)
{
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RelativeLayout layout = (RelativeLayout) findViewById(R.id.layout1);
layout.setOnTouchListener(
this
);
}
@Override
public
boolean
onTouch(View v, MotionEvent event)
{
String tag = v.getTag().toString();
Log.e(tag,
"**********************************************"
);
Log.e(tag,
"**********************************************"
);
Log.e(tag,
"触摸的是:"
+ tag);
Log.e(tag, describeEvent(event));
logAction(event);
Log.e(tag,
"**********************************************"
);
Log.e(tag,
"**********************************************"
);
Log.e(
""
,
" "
);
Log.e(
""
,
" "
);
if
(
"true"
.equals(tag.substring(
0
,
4
)))
{
return
true
;
}
else
{
return
false
;
}
}
protected
static
String describeEvent(MotionEvent event)
{
StringBuilder sb =
new
StringBuilder(
500
);
sb.append(
"Action: "
).append(event.getAction()).append(
"\n"
);
// 获取触控动作比如ACTION_DOWN
int
count = event.getPointerCount();
sb.append(
"触点个数: "
).append(count).append(
"\n"
);
int
i =
0
;
while
(i < count)
{
sb.append(
"-------------------------"
).append(
"\n"
);
int
pointerId = event.getPointerId(i);
sb.append(
"触点序号: "
).append(i).append(
"\n"
);
sb.append(
"触点ID : "
).append(pointerId).append(
"\n"
);
sb.append(
"相对坐标: "
).append(event.getX(i)).append(
" * "
).append(event.getY(i)).append(
"\n"
);
sb.append(
"压力 : "
).append(event.getPressure(i)).append(
"\n"
);
// 压力值,0-1之间,看情况,很可能始终返回1
sb.append(
"范围大小: "
).append(event.getSize(i)).append(
"\n"
);
// 指压范围
i++;
sb.append(
"-------------------------"
).append(
"\n"
);
}
sb.append(
"按下时间: "
).append(event.getDownTime()).append(
"ms "
);
sb.append(
"结束时间: "
).append(event.getEventTime()).append(
"ms "
);
sb.append(
"运行时间: "
).append(event.getEventTime() - event.getDownTime()).append(
"ms\n"
);
return
sb.toString();
}
private
void
logAction(MotionEvent event)
{
int
masked = event.getActionMasked();
int
index = event.getActionIndex();
int
pointerId = event.getPointerId(index);
if
(masked ==
5
|| masked ==
6
)
{
masked = masked -
5
;
}
Log.e(
"Action"
,
"触点序号: "
+ index);
Log.e(
"Action"
,
"触点ID : "
+ pointerId);
Log.e(
"Action"
,
"ActionMasked :"
+ masked);
}
}
|
多点触控的日志打印出来太多,此处仅针对日志输出作简单分析:
A.按下第一根手指时,获得一个索引为0且指针ID为0的指针(ACTION_DOWN = 0);
B.接下去action输出为2(ACTION_MOVE),此时仍然仅有一个指针,并且索引和ID都为0;
C.然后按下第二根手指,action值输出为261。此值由两部分组成:一个是指针索引,一个是指针正在执行何种操作;
D.将十进制261转换为十六进制数为0x00000105。其中01代表指针索引,05代表操作值(即ACTION_POINTER_DOWN的值,用于多点触摸场景);
E.依此类推,当按下第三根手指时action输出值为0x00000205(十进制517),第四根则为0x000305(十进制773);
F.当第一根手指离开屏幕时,action值为0x00000006(十进制6),即索引为0,操作值为6(即ACTION_POINTER_UP);
G.如果此时第二根手指离开时,action值应该为0x00000106(十进制262),因为此时仍然拥有两根手指的信息;
H.而实际结果并没有得到262的action值,这是因为当第一根手指离开屏幕后,第二根手指的索引从1更改成了0,但是指针ID仍然为1.
I.对于每一次移动,action值将始终为2,因为ACTION_MOVE事件并没有包含哪根手指在移动的信息。
附:依次按下四根手指的日志
其他小结:
(1)getPointerCount() 获得触屏的点数,
getActionIndex()获得触点的指针索引,
getPointerId(index)获得指针索引对应的触点ID;
(2)getActionMasked()表示用于多点触控检测点。而在1.6和2.1中并没有event.getActionMasked()这个方法,其实他就是把event.getAction()& MotionEvent.ACTION_MASK封装了一下。
二、其他参考范例:
(一)单点触摸拖动图片与多点触摸缩放图片
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
|
package
com.lonshine.touchdemo;
import
android.app.Activity;
import
android.content.Context;
import
android.graphics.*;
import
android.graphics.drawable.BitmapDrawable;
import
android.os.Bundle;
import
android.view.MotionEvent;
import
android.widget.ImageView;
import
android.widget.ImageView.ScaleType;
/**
* 多点触控来控制ImageView中图像的放大与缩小, 单点触控则用来拖动图片
*
* @author zeng
*
*/
public
class
MainActivity
extends
Activity
{
private
MyImageView imageView;
private
Bitmap bitmap;
// 两点触屏后之间的长度
private
float
beforeLenght;
private
float
afterLenght;
// 单点移动的前后坐标值
private
float
afterX, afterY;
private
float
beforeX, beforeY;
/** Called when the activity is first created. */
@Override
public
void
onCreate(Bundle savedInstanceState)
{
super
.onCreate(savedInstanceState);
findView();
setContentView(imageView);
config();
}
private
void
findView()
{
imageView =
new
MyImageView(
this
);
// 获得图片
bitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.xing)).getBitmap();
}
private
void
config()
{
// 设置imageView的显示图片
imageView.setImageBitmap(bitmap);
// 设置图片填充ImageView
imageView.setScaleType(ScaleType.FIT_XY);
}
// 创建一个自己的ImageView类
class
MyImageView
extends
ImageView
{
private
float
scale =
0
.1f;
public
MyImageView(Context context)
{
super
(context);
}
// 用来设置ImageView的位置
private
void
setLocation(
int
x,
int
y)
{
this
.setFrame(
this
.getLeft() + x,
this
.getTop() + y,
this
.getRight() + x,
this
.getBottom() + y);
}
/**
* 用来放大缩小ImageView 因为图片是填充ImageView的,所以也就有放大缩小图片的效果 flag为0是放大图片,为1是缩小图片
*/
private
void
setScale(
float
temp,
int
flag)
{
if
(flag ==
0
)
{
this
.setFrame(
this
.getLeft() - (
int
) (temp *
this
.getWidth()),
this
.getTop() - (
int
) (temp *
this
.getHeight()),
this
.getRight()
+ (
int
) (temp *
this
.getWidth()),
this
.getBottom() + (
int
) (temp *
this
.getHeight()));
}
else
{
this
.setFrame(
this
.getLeft() + (
int
) (temp *
this
.getWidth()),
this
.getTop() + (
int
) (temp *
this
.getHeight()),
this
.getRight()
- (
int
) (temp *
this
.getWidth()),
this
.getBottom() - (
int
) (temp *
this
.getHeight()));
}
}
// 绘制边框
@Override
protected
void
onDraw(Canvas canvas)
{
super
.onDraw(canvas);
Rect rec = canvas.getClipBounds();
rec.bottom--;
rec.right--;
Paint paint =
new
Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(rec, paint);
}
/**
* 让图片跟随手指触屏的位置移动 beforeX、Y是用来保存前一位置的坐标 afterX、Y是用来保存当前位置的坐标
* 它们的差值就是ImageView各坐标的增加或减少值
*/
public
void
moveWithFinger(MotionEvent event)
{
switch
(event.getAction())
{
case
MotionEvent.ACTION_DOWN:
beforeX = event.getX();
beforeY = event.getY();
break
;
case
MotionEvent.ACTION_MOVE:
afterX = event.getX();
afterY = event.getY();
this
.setLocation((
int
) (afterX - beforeX), (
int
) (afterY - beforeY));
beforeX = afterX;
beforeY = afterY;
break
;
case
MotionEvent.ACTION_UP:
break
;
}
}
/**
* 通过多点触屏放大或缩小图像 beforeLenght用来保存前一时间两点之间的距离 afterLenght用来保存当前时间两点之间的距离
*/
public
void
scaleWithFinger(MotionEvent event)
{
float
moveX = event.getX(
1
) - event.getX(
0
);
float
moveY = event.getY(
1
) - event.getY(
0
);
switch
(event.getAction())
{
case
MotionEvent.ACTION_DOWN:
beforeLenght = (
float
) Math.sqrt((moveX * moveX) + (moveY * moveY));
break
;
case
MotionEvent.ACTION_MOVE:
// 得到两个点之间的长度
afterLenght = (
float
) Math.sqrt((moveX * moveX) + (moveY * moveY));
float
gapLenght = afterLenght - beforeLenght;
if
(gapLenght ==
0
)
{
break
;
}
// 如果当前时间两点距离大于前一时间两点距离,则传0,否则传1
if
(gapLenght >
0
)
{
this
.setScale(scale,
0
);
}
else
{
this
.setScale(scale,
1
);
}
beforeLenght = afterLenght;
break
;
}
}
}
// 这里来监听屏幕触控时间
@Override
public
boolean
onTouchEvent(MotionEvent event)
{
/**
* 判定用户是否触摸到了图片 如果是单点触摸则调用控制图片移动的方法 如果是2点触控则调用控制图片大小的方法
*/
if
(event.getY() > imageView.getTop() && event.getY() < imageView.getBottom() && event.getX() > imageView.getLeft()
&& event.getX() < imageView.getRight())
{
if
(event.getPointerCount() ==
2
)
{
imageView.scaleWithFinger(event);
}
else
if
(event.getPointerCount() ==
1
)
{
imageView.moveWithFinger(event);
}
}
return
true
;
}
}
|
范例参考自:http://blog.csdn.net/ldj299/article/details/6422547
(二)实现多点消息拦截,用于多个物体拖动等效果
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
|
package
com.lonshine.d_touchdemo;
import
java.util.ArrayList;
import
java.util.List;
import
android.content.Context;
import
android.os.Handler;
import
android.os.Message;
import
android.view.MotionEvent;
import
android.view.VelocityTracker;
import
android.view.ViewConfiguration;
/**
* 实现多点消息拦截,用于多个物体拖动等效果
* @author zeng
*/
public
class
MultiTouchGestureDetector
{
@SuppressWarnings
(
"unused"
)
private
static
final
String MYTAG =
"Alan"
;
public
static
final
String CLASS_NAME =
"MultiTouchGestureDetector"
;
/**
* 事件信息类 <br/>
* 用来记录一个手势
*/
private
class
EventInfo
{
private
MultiMotionEvent mCurrentDownEvent;
// 当前的down事件
private
MultiMotionEvent mPreviousUpEvent;
// 上一次up事件
private
boolean
mStillDown;
// 当前手指是否还在屏幕上
private
boolean
mInLongPress;
// 当前事件是否属于长按手势
private
boolean
mAlwaysInTapRegion;
// 是否当前手指仅在小范围内移动,当手指仅在小范围内移动时,视为手指未曾移动过,不会触发onScroll手势
private
boolean
mAlwaysInBiggerTapRegion;
// 是否当前手指在较大范围内移动,仅当此值为true时,双击手势才能成立
private
boolean
mIsDoubleTapping;
// 当前手势,是否为双击手势
private
float
mLastMotionY;
// 最后一次事件的X坐标
private
float
mLastMotionX;
// 最后一次事件的Y坐标
private
EventInfo(MotionEvent e)
{
this
(
new
MultiMotionEvent(e));
}
private
EventInfo(MultiMotionEvent me)
{
mCurrentDownEvent = me;
mStillDown =
true
;
mInLongPress =
false
;
mAlwaysInTapRegion =
true
;
mAlwaysInBiggerTapRegion =
true
;
mIsDoubleTapping =
false
;
}
// 释放MotionEven对象,使系统能够继续使用它们
public
void
recycle()
{
if
(mCurrentDownEvent !=
null
)
{
mCurrentDownEvent.recycle();
mCurrentDownEvent =
null
;
}
if
(mPreviousUpEvent !=
null
)
{
mPreviousUpEvent.recycle();
mPreviousUpEvent =
null
;
}
}
@Override
public
void
finalize()
{
this
.recycle();
}
}
/**
* 多点事件类 <br/>
* 将一个多点事件拆分为多个单点事件,并方便获得事件的绝对坐标 <br/>
* 绝对坐标用以在界面中找到触点所在的控件
*
* @author ray-ni
*/
public
class
MultiMotionEvent
{
private
MotionEvent mEvent;
private
int
mIndex;
private
MultiMotionEvent(MotionEvent e)
{
mEvent = e;
mIndex = (e.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
// 等效于
// mEvent.getActionIndex();
}
private
MultiMotionEvent(MotionEvent e,
int
idx)
{
mEvent = e;
mIndex = idx;
}
// 行为
public
int
getAction()
{
int
action = mEvent.getAction() & MotionEvent.ACTION_MASK;
// 等效于
// mEvent.getActionMasked();
switch
(action)
{
case
MotionEvent.ACTION_POINTER_DOWN:
action = MotionEvent.ACTION_DOWN;
break
;
case
MotionEvent.ACTION_POINTER_UP:
action = MotionEvent.ACTION_UP;
break
;
}
return
action;
}
// 返回X的绝对坐标
public
float
getX()
{
return
mEvent.getX(mIndex) + mEvent.getRawX() - mEvent.getX();
}
// 返回Y的绝对坐标
public
float
getY()
{
return
mEvent.getY(mIndex) + mEvent.getRawY() - mEvent.getY();
}
// 事件发生的时间
public
long
getEventTime()
{
return
mEvent.getEventTime();
}
// 事件序号
public
int
getIndex()
{
return
mIndex;
}
// 事件ID
public
int
getId()
{
return
mEvent.getPointerId(mIndex);
}
// 释放事件对象,使系统能够继续使用
public
void
recycle()
{
if
(mEvent !=
null
)
{
mEvent.recycle();
mEvent =
null
;
}
}
}
// 多点手势监听器
public
interface
MultiTouchGestureListener
{
// 手指触碰到屏幕,由一个 ACTION_DOWN触发
boolean
onDown(MultiMotionEvent e);
// 确定一个press事件,强调手指按下的一段时间(TAP_TIMEOUT)内,手指未曾移动或抬起
void
onShowPress(MultiMotionEvent e);
// 手指点击屏幕后离开,由 ACTION_UP引发,可以简单的理解为单击事件,即手指点击时间不长(未构成长按事件),也不曾移动过
boolean
onSingleTapUp(MultiMotionEvent e);
// 长按,手指点下后一段时间(DOUBLE_TAP_TIMEOUT)内,不曾抬起或移动
void
onLongPress(MultiMotionEvent e);
// 拖动,由ACTION_MOVE触发,手指地按下后,在屏幕上移动
boolean
onScroll(MultiMotionEvent e1, MultiMotionEvent e2,
float
distanceX,
float
distanceY);
// 滑动,由ACTION_UP触发,手指按下并移动一段距离后,抬起时触发
boolean
onFling(MultiMotionEvent e1, MultiMotionEvent e2,
float
velocityX,
float
velocityY);
}
// 多点双击监听器
public
interface
MultiTouchDoubleTapListener
{
// 单击事件确认,强调第一个单击事件发生后,一段时间内,未发生第二次单击事件,即确定不会触发双击事件
boolean
onSingleTapConfirmed(MultiMotionEvent e);
// 双击事件,
// 由ACTION_DOWN触发,从第一次单击事件的DOWN事件开始的一段时间(DOUBLE_TAP_TIMEOUT)内结束(即手指),
// 并且在第一次单击事件的UP时间开始后的一段时间内(DOUBLE_TAP_TIMEOUT)发生第二次单击事件,
// 除此之外两者坐标间距小于定值(DOUBLE_TAP_SLAP)时,则触发双击事件
boolean
onDoubleTap(MultiMotionEvent e);
// 双击事件,与onDoubleTap事件不同之处在于,构成双击的第二次点击的ACTION_DOWN,ACTION_MOVE和ACTION_UP都会触发该事件
boolean
onDoubleTapEvent(MultiMotionEvent e);
}
// 事件信息队列,队列的下标与MotionEvent的pointId对应
private
static
List<EventInfo> sEventInfos =
new
ArrayList<EventInfo>(
10
);
// 双击判断队列,这个队列中的元素等待双击超时的判断结果
private
static
List<EventInfo> sEventForDoubleTap =
new
ArrayList<EventInfo>(
5
);
// 指定大点击区域的大小(这个比较拗口),这个值主要用于帮助判断双击是否成立
private
int
mBiggerTouchSlopSquare =
20
*
20
;
// 判断是否构成onScroll手势,当手指在这个范围内移动时,不触发onScroll手势
private
int
mTouchSlopSquare;
// 判断是否构成双击,只有两次点击的距离小于该值,才能构成双击手势
private
int
mDoubleTapSlopSquare;
// 最小滑动速度
private
int
mMinimumFlingVelocity;
// 最大滑动速度
private
int
mMaximumFlingVelocity;
// 长按阀值,当手指按下后,在该阀值的时间内,未移动超过mTouchSlopSquare的距离并未抬起,则长按手势触发
private
static
final
int
LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
// showPress手势的触发阀值,当手指按下后,在该阀值的时间内,未移动超过mTouchSlopSquare的距离并未抬起,则showPress手势触发
private
static
final
int
TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
// 双击超时阀值,仅在两次双击事件的间隔(第一次单击的UP事件和第二次单击的DOWN事件)小于此阀值,双击事件才能成立
private
static
final
int
DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
// 双击区域阀值,仅在两次双击事件的距离小于此阀值,双击事件才能成立
private
static
final
int
DOUBLE_TAP_SLAP =
64
;
// GestureHandler所处理的Message的what属性可能为以下 常量:
// showPress手势
private
static
final
int
SHOW_PRESS =
1
;
// 长按手势
private
static
final
int
LONG_PRESS =
2
;
// SingleTapConfirmed手势
private
static
final
int
TAP_SINGLE =
3
;
// 双击手势
private
static
final
int
TAP_DOUBLE =
4
;
// 手势处理器
private
final
GestureHandler mHandler;
// 手势监听器
private
final
MultiTouchGestureListener mListener;
// 双击监听器
private
MultiTouchDoubleTapListener mDoubleTapListener;
// 长按允许阀值
private
boolean
mIsLongpressEnabled;
// 速度追踪器
private
VelocityTracker mVelocityTracker;
private
class
GestureHandler
extends
Handler
{
GestureHandler()
{
super
();
}
GestureHandler(Handler handler)
{
super
(handler.getLooper());
}
@Override
public
void
handleMessage(Message msg)
{
int
idx = (Integer) msg.obj;
switch
(msg.what)
{
case
SHOW_PRESS:
{
if
(idx >= sEventInfos.size())
{
// Log.w(MYTAG, CLASS_NAME +
// ":handleMessage, msg.what = SHOW_PRESS, idx=" + idx +
// ", while sEventInfos.size()="
// + sEventInfos.size());
break
;
}
EventInfo info = sEventInfos.get(idx);
if
(info ==
null
)
{
// Log.e(MYTAG, CLASS_NAME +
// ":handleMessage, msg.what = SHOW_PRESS, idx=" + idx +
// ", Info = null");
break
;
}
// 触发手势监听器的onShowPress事件
mListener.onShowPress(info.mCurrentDownEvent);
break
;
}
case
LONG_PRESS:
{
// Log.d(MYTAG, CLASS_NAME + ":trigger LONG_PRESS");
if
(idx >= sEventInfos.size())
{
// Log.w(MYTAG, CLASS_NAME +
// ":handleMessage, msg.what = LONG_PRESS, idx=" + idx +
// ", while sEventInfos.size()="
// + sEventInfos.size());
break
;
}
EventInfo info = sEventInfos.get(idx);
if
(info ==
null
)
{
// Log.e(MYTAG, CLASS_NAME +
// ":handleMessage, msg.what = LONG_PRESS, idx=" + idx +
// ", Info = null");
break
;
}
dispatchLongPress(info, idx);
break
;
}
case
TAP_SINGLE:
{
// Log.d(MYTAG, CLASS_NAME + ":trriger TAP_SINGLE");
// If the user's finger is still down, do not count it as a
// tap
if
(idx >= sEventInfos.size())
{
// Log.e(MYTAG, CLASS_NAME +
// ":handleMessage, msg.what = TAP_SINGLE, idx=" + idx +
// ", while sEventInfos.size()="
// + sEventInfos.size());
break
;
}
EventInfo info = sEventInfos.get(idx);
if
(info ==
null
)
{
// Log.e(MYTAG, CLASS_NAME +
// ":handleMessage, msg.what = TAP_SINGLE, idx=" + idx +
// ", Info = null");
break
;
}
if
(mDoubleTapListener !=
null
&& !info.mStillDown)
{
// 手指在双击超时的阀值内未离开屏幕进行第二次单击事件,则确定单击事件成立(不再触发双击事件)
mDoubleTapListener.onSingleTapConfirmed(info.mCurrentDownEvent);
}
break
;
}
case
TAP_DOUBLE:
{
if
(idx >= sEventForDoubleTap.size())
{
// Log.w(MYTAG, CLASS_NAME +
// ":handleMessage, msg.what = TAP_DOUBLE, idx=" + idx +
// ", while sEventForDoubleTap.size()="
// + sEventForDoubleTap.size());
break
;
}
EventInfo info = sEventForDoubleTap.get(idx);
if
(info ==
null
)
{
// Log.w(MYTAG, CLASS_NAME +
// ":handleMessage, msg.what = TAP_DOUBLE, idx=" + idx +
// ", Info = null");
break
;
}
sEventForDoubleTap.set(idx,
null
);
// 这个没什么好做的,就是把队列中对应的元素清除而已
break
;
}
default
:
throw
new
RuntimeException(
"Unknown message "
+ msg);
// never
}
}
}
/**
* 触发长按事件
*
* @param info
* @param idx
*/
private
void
dispatchLongPress(EventInfo info,
int
idx)
{
mHandler.removeMessages(TAP_SINGLE, idx);
// 移除单击事件确认
info.mInLongPress =
true
;
mListener.onLongPress(info.mCurrentDownEvent);
}
/**
* 构造器1
*
* @param context
* @param listener
*/
public
MultiTouchGestureDetector(Context context, MultiTouchGestureListener listener)
{
this
(context, listener,
null
);
}
/**
* 构造器2
*
* @param context
* @param listener
* @param handler
*/
public
MultiTouchGestureDetector(Context context, MultiTouchGestureListener listener, Handler handler)
{
if
(handler !=
null
)
{
mHandler =
new
GestureHandler(handler);
}
else
{
mHandler =
new
GestureHandler();
}
mListener = listener;
if
(listener
instanceof
MultiTouchDoubleTapListener)
{
setOnDoubleTapListener((MultiTouchDoubleTapListener) listener);
}
init(context);
}
/**
* 初始化识别器
*
* @param context
*/
private
void
init(Context context)
{
if
(mListener ==
null
)
{
throw
new
NullPointerException(
"OnGestureListener must not be null"
);
}
mIsLongpressEnabled =
true
;
int
touchSlop, doubleTapSlop;
if
(context ==
null
)
{
touchSlop = ViewConfiguration.getTouchSlop();
doubleTapSlop = DOUBLE_TAP_SLAP;
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
}
else
{
// 允许识别器在App中,使用偏好的设定
final
ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
doubleTapSlop = configuration.getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
}
mTouchSlopSquare = touchSlop * touchSlop /
16
;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}
/**
* 设置双击监听器
*
* @param onDoubleTapListener
*/
public
void
setOnDoubleTapListener(MultiTouchDoubleTapListener onDoubleTapListener)
{
mDoubleTapListener = onDoubleTapListener;
}
/**
* 设置是否允许长按
*
* @param isLongpressEnabled
*/
public
void
setIsLongpressEnabled(
boolean
isLongpressEnabled)
{
mIsLongpressEnabled = isLongpressEnabled;
}
/**
* 判断是否允许长按
*
* @return
*/
public
boolean
isLongpressEnabled()
{
return
mIsLongpressEnabled;
}
/**
* 判断当前事件是否为双击事件 <br/>
* 通过遍历sEventForDoubleTap来匹配是否存在能够构成双击事件的单击事件
*
* @param e
* @return
*/
private
EventInfo checkForDoubleTap(MultiMotionEvent e)
{
if
(sEventForDoubleTap.isEmpty())
{
// Log.e(MYTAG, CLASS_NAME +
// ":checkForDoubleTap(), sEventForDoubleTap is empty !");
return
null
;
}
for
(
int
i =
0
; i < sEventForDoubleTap.size(); i++)
{
EventInfo info = sEventForDoubleTap.get(i);
if
(info !=
null
&& isConsideredDoubleTap(info, e))
{
sEventForDoubleTap.set(i,
null
);
// 这个单击事件已经被消耗了,所以置为null
mHandler.removeMessages(TAP_DOUBLE, i);
// 移除Handler内的为处理消息
return
info;
}
}
return
null
;
}
/**
* 判断当前按下事件是否能和指定的单击事件构成双击事件
*
* @param info
* @param secondDown
* @return
*/
private
boolean
isConsideredDoubleTap(EventInfo info, MultiMotionEvent secondDown)
{
if
(!info.mAlwaysInBiggerTapRegion)
{
// 如多第一次单击事件有过较大距离的移动,则无法构成双击事件
return
false
;
}
if
(secondDown.getEventTime() - info.mPreviousUpEvent.getEventTime() > DOUBLE_TAP_TIMEOUT)
{
// 如果第一次单击的UP时间和第二次单击的down时间时间间隔大于DOUBLE_TAP_TIMEOUT,也无法构成双击事件
return
false
;
}
int
deltaX = (
int
) info.mCurrentDownEvent.getX() - (
int
) secondDown.getX();
int
deltaY = (
int
) info.mCurrentDownEvent.getY() - (
int
) secondDown.getY();
return
(deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
// 最后判断两次单击事件的距离
}
/**
* 将事件信息放入双击判断队列,并返回序号
*
* @param info
* @return
*/
private
int
addIntoTheMinIndex(EventInfo info)
{
for
(
int
i =
0
; i < sEventForDoubleTap.size(); i++)
{
if
(sEventForDoubleTap.get(i) ==
null
)
{
sEventForDoubleTap.set(i, info);
return
i;
}
}
sEventForDoubleTap.add(info);
return
sEventForDoubleTap.size() -
1
;
}
/**
* 从事件信息队列中移除指定序号的事件
*
* @param idx
*/
private
void
removeEventFromList(
int
id)
{
if
(id > sEventInfos.size() || id <
0
)
{
// Log.e(MYTAG, CLASS_NAME + ".removeEventFromList(), id=" + id +
// ", while sEventInfos.size() =" +
// sEventInfos.size());
return
;
}
sEventInfos.set(id,
null
);
}
/**
* 向事件队列中添加新信息
*
* @param e
*/
private
void
addEventIntoList(EventInfo info)
{
int
id = info.mCurrentDownEvent.getId();
if
(id < sEventInfos.size())
{
// if (sEventInfos.get(id) != null)
// Log.e(MYTAG, CLASS_NAME + ".addEventIntoList, info(" + id +
// ") has not set to null !");
sEventInfos.set(info.mCurrentDownEvent.getId(), info);
}
else
if
(id == sEventInfos.size())
{
sEventInfos.add(info);
}
else
{
// Log.e(MYTAG, CLASS_NAME + ".addEventIntoList, invalidata id !");
}
}
public
boolean
onTouchEvent(MotionEvent ev)
{
if
(mVelocityTracker ==
null
)
{
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
// 把所有事件都添加到速度追踪器,为计算速度做准备
boolean
handled =
false
;
final
int
action = ev.getAction();
// 获取Action
// int idx = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
// MotionEvent.ACTION_POINTER_INDEX_SHIFT;//获取触摸事件的序号
int
idx = ev.getPointerId(ev.getActionIndex());
// 获取触摸事件的id
switch
(action & MotionEvent.ACTION_MASK)
{
case
MotionEvent.ACTION_DOWN:
case
MotionEvent.ACTION_POINTER_DOWN:
{
EventInfo info =
new
EventInfo(MotionEvent.obtain(ev));
this
.addEventIntoList(info);
// 将手势信息保存到队列中
if
(mDoubleTapListener !=
null
)
{
// 如果双击监听器不为null
if
(mHandler.hasMessages(TAP_DOUBLE))
{
MultiMotionEvent e =
new
MultiMotionEvent(ev);
EventInfo origInfo = checkForDoubleTap(e);
// 检查是否构成双击事件
if
(origInfo !=
null
)
{
info.mIsDoubleTapping =
true
;
handled |= mDoubleTapListener.onDoubleTap(origInfo.mCurrentDownEvent);
handled |= mDoubleTapListener.onDoubleTapEvent(e);
}
}
if
(!info.mIsDoubleTapping)
{
// 当前事件不构成双击事件,那么发送延迟消息以判断onSingleTapConfirmed事件
mHandler.sendMessageDelayed(mHandler.obtainMessage(TAP_SINGLE, idx), DOUBLE_TAP_TIMEOUT);
// Log.d(MYTAG, CLASS_NAME + ": add TAP_SINGLE");
}
}
// 记录X坐标和Y坐标
info.mLastMotionX = info.mCurrentDownEvent.getX();
info.mLastMotionY = info.mCurrentDownEvent.getY();
if
(mIsLongpressEnabled)
{
// 允许长按
mHandler.removeMessages(LONG_PRESS, idx);
mHandler.sendMessageAtTime(mHandler.obtainMessage(LONG_PRESS, idx), info.mCurrentDownEvent.getEventTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT);
// 延时消息以触发长按手势
// Log.d(MYTAG, CLASS_NAME +
// ":add LONG_PRESS to handler for idx " + idx);
}
mHandler.sendMessageAtTime(mHandler.obtainMessage(SHOW_PRESS, idx), info.mCurrentDownEvent.getEventTime() + TAP_TIMEOUT);
// 延时消息,触发showPress手势
handled |= mListener.onDown(info.mCurrentDownEvent);
// 触发onDown()
break
;
}
case
MotionEvent.ACTION_UP:
case
MotionEvent.ACTION_POINTER_UP:
{
MultiMotionEvent currentUpEvent =
new
MultiMotionEvent(ev);
if
(idx >= sEventInfos.size())
{
// Log.e(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" +
// idx + ", while sEventInfos.size()=" +
// sEventInfos.size());
break
;
}
EventInfo info = sEventInfos.get(currentUpEvent.getId());
if
(info ==
null
)
{
// Log.e(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" +
// idx + ", Info = null");
break
;
}
info.mStillDown =
false
;
if
(info.mIsDoubleTapping)
{
// 处于双击状态,则触发onDoubleTapEvent事件
handled |= mDoubleTapListener.onDoubleTapEvent(currentUpEvent);
}
else
if
(info.mInLongPress)
{
// 处于长按状态
mHandler.removeMessages(TAP_SINGLE, idx);
// 可以无视这行代码
info.mInLongPress =
false
;
}
else
if
(info.mAlwaysInTapRegion)
{
// 尚未移动过
if
(mHandler.hasMessages(TAP_SINGLE, idx))
{
// 还在双击的时间阀值内,所以要为双击判断做额外处理
mHandler.removeMessages(TAP_SINGLE, idx);
info.mPreviousUpEvent =
new
MultiMotionEvent(MotionEvent.obtain(ev));
int
index =
this
.addIntoTheMinIndex(info);
// 把当前事件放入队列,等待双击的判断
mHandler.sendMessageAtTime(mHandler.obtainMessage(TAP_DOUBLE, index), info.mCurrentDownEvent.getEventTime() + DOUBLE_TAP_TIMEOUT);
// 将双击超时判断添加到Handler
// Log.d(MYTAG, CLASS_NAME + ": add TAP_DOUBLE");
}
handled = mListener.onSingleTapUp(currentUpEvent);
// 触发onSingleTapUp事件
}
else
{
// A fling must travel the minimum tap distance
final
VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(
1000
, mMaximumFlingVelocity);
// 计算1秒钟内的滑动速度
// 获取X和Y方向的速度
final
float
velocityX = velocityTracker.getXVelocity(idx);
final
float
velocityY = velocityTracker.getYVelocity(idx);
// Log.i(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" +
// idx +
// ", vx=" + velocityX + ", vy=" + velocityY);
// 触发滑动事件
if
((Math.abs(velocityY) > mMinimumFlingVelocity) || (Math.abs(velocityX) > mMinimumFlingVelocity))
{
handled = mListener.onFling(info.mCurrentDownEvent, currentUpEvent, velocityX, velocityY);
}
}
// Hold the event we obtained above - listeners may have changed
// the
// original.
if
(action == MotionEvent.ACTION_UP)
{
// 释放速度追踪器
mVelocityTracker.recycle();
mVelocityTracker =
null
;
// Log.w(MYTAG, CLASS_NAME +
// ":ACTION_POINTER_UP, mVelocityTracker.recycle()");
}
info.mIsDoubleTapping =
false
;
// Log.d(MYTAG, CLASS_NAME + "remove LONG_PRESS");
// 移除showPress和长按消息
mHandler.removeMessages(SHOW_PRESS, idx);
mHandler.removeMessages(LONG_PRESS, idx);
removeEventFromList(currentUpEvent.getId());
// 手指离开,则从队列中删除手势信息
break
;
}
case
MotionEvent.ACTION_MOVE:
for
(
int
rIdx =
0
; rIdx < ev.getPointerCount(); rIdx++)
{
// 因为无法确定当前发生移动的是哪个手指,所以遍历处理所有手指
MultiMotionEvent e =
new
MultiMotionEvent(ev, rIdx);
if
(e.getId() >= sEventInfos.size())
{
// Log.e(MYTAG, CLASS_NAME + ":ACTION_MOVE, idx=" + rIdx
// + ", while sEventInfos.size()=" +
// sEventInfos.size());
break
;
}
EventInfo info = sEventInfos.get(e.getId());
if
(info ==
null
)
{
// Log.e(MYTAG, CLASS_NAME + ":ACTION_MOVE, idx=" + rIdx
// + ", Info = null");
break
;
}
if
(info.mInLongPress)
{
// 长按,则不处理move事件
break
;
}
// 当前坐标
float
x = e.getX();
float
y = e.getY();
// 距离上次事件移动的位置
final
float
scrollX = x - info.mLastMotionX;
final
float
scrollY = y - info.mLastMotionY;
if
(info.mIsDoubleTapping)
{
// 双击事件
handled |= mDoubleTapListener.onDoubleTapEvent(e);
}
else
if
(info.mAlwaysInTapRegion)
{
// 该手势尚未移动过(移动的距离小于mTouchSlopSquare,视为未移动过)
// 计算从落下到当前事件,移动的距离
final
int
deltaX = (
int
) (x - info.mCurrentDownEvent.getX());
final
int
deltaY = (
int
) (y - info.mCurrentDownEvent.getY());
// Log.d(MYTAG, CLASS_NAME + "deltaX="+deltaX+";deltaY="
// +
// deltaX +"mTouchSlopSquare=" + mTouchSlopSquare);
int
distance = (deltaX * deltaX) + (deltaY * deltaY);
if
(distance > mTouchSlopSquare)
{
// 移动距离超过mTouchSlopSquare
handled = mListener.onScroll(info.mCurrentDownEvent, e, scrollX, scrollY);
info.mLastMotionX = e.getX();
info.mLastMotionY = e.getY();
info.mAlwaysInTapRegion =
false
;
// Log.d(MYTAG, CLASS_NAME +
// ":remove LONG_PRESS for idx" + rIdx +
// ",mTouchSlopSquare("+mTouchSlopSquare+"), distance("+distance+")");
// 清除onSingleTapConform,showPress,longPress三种消息
int
id = e.getId();
mHandler.removeMessages(TAP_SINGLE, id);
mHandler.removeMessages(SHOW_PRESS, id);
mHandler.removeMessages(LONG_PRESS, id);
}
if
(distance > mBiggerTouchSlopSquare)
{
// 移动距离大于mBiggerTouchSlopSquare,则无法构成双击事件
info.mAlwaysInBiggerTapRegion =
false
;
}
}
else
if
((Math.abs(scrollX) >=
1
) || (Math.abs(scrollY) >=
1
))
{
// 之前已经移动过了
handled = mListener.onScroll(info.mCurrentDownEvent, e, scrollX, scrollY);
info.mLastMotionX = x;
info.mLastMotionY = y;
}
}
break
;
case
MotionEvent.ACTION_CANCEL:
cancel();
// 清理
}
return
handled;
}
// 清理所有队列
private
void
cancel()
{
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP_SINGLE);
mVelocityTracker.recycle();
mVelocityTracker =
null
;
sEventInfos.clear();
sEventForDoubleTap.clear();
}
}
|