本文以实例形式讲述了Android Touch事件分发过程,对于深入理解与掌握Android程序设计有很大的帮助作用。具体分析如下:
首先,从一个简单示例入手:
先看一个示例如下图所示:
布局文件 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
android:id
=
"@+id/container"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:layout_gravity
=
"center"
tools:context
=
"com.example.touch_event.MainActivity"
tools:ignore
=
"MergeRootFrame"
>
<
Button
android:id
=
"@+id/my_button"
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:text
=
"@string/hello_world"
/>
</
FrameLayout
>
|
MainActivity文件:
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
|
public
class
MainActivity
extends
Activity {
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mBtn = (Button) findViewById(R.id.my_button);
mBtn.setOnTouchListener(
new
OnTouchListener() {
@Override
public
boolean
onTouch(View v, MotionEvent event) {
Log.d(
""
,
"### onTouch : "
+ event.getAction());
return
false
;
}
});
mBtn.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
Log.d(
""
,
"### onClick : "
+ v);
}
});
}
@Override
public
boolean
dispatchTouchEvent(MotionEvent ev) {
Log.d(
""
,
"### activity dispatchTouchEvent"
);
return
super
.dispatchTouchEvent(ev);
}
}
|
当用户点击按钮时会输出如下Log:
1
2
3
4
5
|
08-31 03:03:56.116: D/(1560): ### activity dispatchTouchEvent
08-31 03:03:56.116: D/(1560): ### onTouch : 0
08-31 03:03:56.196: D/(1560): ### activity dispatchTouchEvent
08-31 03:03:56.196: D/(1560): ### onTouch : 1
08-31 03:03:56.196: D/(1560): ### onClick : android.widget.Button{52860d98 VFED..C. ...PH... 0,0-1080,144 #7f05003d app:id/my_button}
|
我们可以看到首先执行了Activity中的dispatchTouchEvent方法,然后执行了onTouch方法,然后再是dispatchTouchEvent --> onTouch, 最后才是执行按钮的点击事件。这里我们可能有个疑问,为什么dispatchTouchEvent和onTouch都执行了两次,而onClick才执行了一次 ? 为什么两次的Touch事件的action不一样,action 0 和 action 1到底代表了什么 ?
覆写过onTouchEvent的朋友知道,一般来说我们在该方法体内都会处理集中touch类型的事件,有ACTION_DOWN、ACTION_MOVE、ACTION_UP等,不过上面我们的例子中并没有移动,只是单纯的按下、抬起。因此,我们的触摸事件也只有按下、抬起,因此有2次touch事件,而action分别为0和1。我们看看MotionEvent中的一些变量定义吧:
1
2
3
4
5
6
7
8
|
public
final
class
MotionEvent
extends
InputEvent
implements
Parcelable {
// 代码省略
public
static
final
int
ACTION_DOWN =
0
;
// 按下事件
public
static
final
int
ACTION_UP =
1
;
// 抬起事件
public
static
final
int
ACTION_MOVE =
2
;
// 手势移动事件
public
static
final
int
ACTION_CANCEL =
3
;
// 取消
// 代码省略
}
|
可以看到,代表按下的事件为0,抬起事件为1,也证实了我们上面所说的。
在看另外两个场景:
1、我们点击按钮外的区域,输出Log如下 :
1
2
|
08-31 03:04:45.408: D/(1560): ### activity dispatchTouchEvent08-31
03:04:45.512: D/(1560): ### activity dispatchTouchEvent
|
2、我们在onTouch函数中返回true, 输出Log如下 :
1
2
3
4
|
08-31 03:06:04.764: D/(1612): ### activity dispatchTouchEvent
08-31 03:06:04.764: D/(1612): ### onTouch : 0
08-31 03:06:04.868: D/(1612): ### activity dispatchTouchEvent
08-31 03:06:04.868: D/(1612): ### onTouch : 1
|
以上两个场景为什么会这样呢 ? 我们继续往下看吧。
Android Touch事件分发
那么整个事件分发的流程是怎样的呢 ?
简单来说就是用户触摸手机屏幕会产生一个触摸消息,最终这个触摸消息会被传送到ViewRoot ( 看4.2的源码时这个类改成了ViewRootImpl )的InputHandler,ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁,根据ViewRoot的定义,发现它并不是一个View类型,而是一个Handler。InputHandler是一个接口类型,用于处理KeyEvent和TouchEvent类型的事件,我们看看源码 :
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
|
public
final
class
ViewRoot
extends
Handler
implements
ViewParent,
View.AttachInfo.Callbacks {
// 代码省略
private
final
InputHandler mInputHandler =
new
InputHandler() {
public
void
handleKey(KeyEvent event, Runnable finishedCallback) {
startInputEvent(finishedCallback);
dispatchKey(event,
true
);
}
public
void
handleMotion(MotionEvent event, Runnable finishedCallback) {
startInputEvent(finishedCallback);
dispatchMotion(event,
true
);
// 1、handle 触摸消息
}
};
// 代码省略
// 2、分发触摸消息
private
void
dispatchMotion(MotionEvent event,
boolean
sendDone) {
int
source = event.getSource();
if
((source & InputDevice.SOURCE_CLASS_POINTER) !=
0
) {
dispatchPointer(event, sendDone);
// 分发触摸消息
}
else
if
((source & InputDevice.SOURCE_CLASS_TRACKBALL) !=
0
) {
dispatchTrackball(event, sendDone);
}
else
{
// TODO
Log.v(TAG,
"Dropping unsupported motion event (unimplemented): "
+ event);
if
(sendDone) {
finishInputEvent();
}
}
}
// 3、通过Handler投递消息
private
void
dispatchPointer(MotionEvent event,
boolean
sendDone) {
Message msg = obtainMessage(DISPATCH_POINTER);
msg.obj = event;
msg.arg1 = sendDone ?
1
:
0
;
sendMessageAtTime(msg, event.getEventTime());
}
@Override
public
void
handleMessage(Message msg) {
// ViewRoot覆写handlerMessage来处理各种消息
switch
(msg.what) {
// 代码省略
case
DO_TRAVERSAL:
if
(mProfile) {
Debug.startMethodTracing(
"ViewRoot"
);
}
performTraversals();
if
(mProfile) {
Debug.stopMethodTracing();
mProfile =
false
;
}
break
;
case
DISPATCH_POINTER: {
// 4、处理DISPATCH_POINTER类型的消息,即触摸屏幕的消息
MotionEvent event = (MotionEvent) msg.obj;
try
{
deliverPointerEvent(event);
// 5、处理触摸消息
}
finally
{
event.recycle();
if
(msg.arg1 !=
0
) {
finishInputEvent();
}
if
(LOCAL_LOGV || WATCH_POINTER) Log.i(TAG,
"Done dispatching!"
);
}
}
break
;
// 代码省略
}
// 6、真正的处理事件
private
void
deliverPointerEvent(MotionEvent event) {
if
(mTranslator !=
null
) {
mTranslator.translateEventInScreenToAppWindow(event);
}
boolean
handled;
if
(mView !=
null
&& mAdded) {
// enter touch mode on the down
boolean
isDown = event.getAction() == MotionEvent.ACTION_DOWN;
if
(isDown) {
ensureTouchMode(
true
);
// 如果是ACTION_DOWN事件则进入触摸模式,否则为按键模式。
}
if
(Config.LOGV) {
captureMotionLog(
"captureDispatchPointer"
, event);
}
if
(mCurScrollY !=
0
) {
event.offsetLocation(
0
, mCurScrollY);
// 物理坐标向逻辑坐标的转换
}
if
(MEASURE_LATENCY) {
lt.sample(
"A Dispatching TouchEvents"
, System.nanoTime() - event.getEventTimeNano());
}
// 7、分发事件,如果是窗口类型,则这里的mView对应的就是PhonwWindow中的DecorView,否则为根视图的ViewGroup。
handled = mView.dispatchTouchEvent(event);
// 代码省略
}
}
// 代码省略
}
|
经过层层迷雾,不管代码7处的mView是DecorView还是非窗口界面的根视图,其本质都是ViewGroup,即触摸事件最终被根视图ViewGroup进行分发!!!
我们就以Activity为例来分析这个过程,我们知道显示出来的Activity有一个顶层窗口,这个窗口的实现类是PhoneWindow, PhoneWindow中的内容区域是一个DecorView类型的View,这个View这就是我们在手机上看到的内容,这个DecorView是FrameLayout的子类,Activity的的dispatchTouchEvent实际上就是调用PhoneWindow的dispatchTouchEvent,我们看看源代码吧,进入Activity的dispatchTouchEvent函数 :
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
boolean
dispatchTouchEvent(MotionEvent ev) {
if
(ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if
(getWindow().superDispatchTouchEvent(ev)) {
// 1、调用的是PhoneWindow的superDispatchTouchEvent(ev)
return
true
;
}
return
onTouchEvent(ev);
}
public
void
onUserInteraction() {
}
|
可以看到,如果事件为按下事件,则会进入到onUserInteraction()这个函数,该函数为空实现,我们暂且不管它。继续看,发现touch事件的分发调用了getWindow().superDispatchTouchEvent(ev)函数,getWindow()获取到的实例的类型为PhoneWindow类型,你可以在你的Activity类中使用如下方式查看getWindow()获取到的类型:
1
|
Log.d(
""
,
"### Activiti中getWindow()获取的类型是 : "
+
this
.getWindow()) ;
|
输出:
1
|
08
-
31
03
:
40
:
17.036
: D/(
1688
): ### Activiti中getWindow()获取的类型是 : com.android.internal.policy.impl.PhoneWindow
@5287fe38
|
OK,废话不多说,我们还是继续看PhoneWindow中的superDispatchTouchEvent函数吧。
1
2
3
4
|
@Override
public
boolean
superDispatchTouchEvent(MotionEvent event) {
return
mDecor.superDispatchTouchEvent(event);
}
|
恩,调用的是mDecor的superDispatchTouchEvent(event)函数,这个mDecor就是我们上面所说的DecorView类型,也就是我们看到的Activity上的所有内容的一个顶层ViewGroup,即整个ViewTree的根节点。看看它的声明吧。
1
2
|
// This is the top-level view of the window, containing the window decor.
private
DecorView mDecor;
|
DecorView
那么我继续看看DecorView到底是个什么玩意儿吧。
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
|
private
final
class
DecorView
extends
FrameLayout
implements
RootViewSurfaceTaker {
/* package */int mDefaultOpacity = PixelFormat.OPAQUE;
/** The feature ID of the panel, or -1 if this is the application's DecorView */
private
final
int
mFeatureId;
private
final
Rect mDrawingBounds =
new
Rect();
private
final
Rect mBackgroundPadding =
new
Rect();
private
final
Rect mFramePadding =
new
Rect();
private
final
Rect mFrameOffsets =
new
Rect();
private
boolean
mChanging;
private
Drawable mMenuBackground;
private
boolean
mWatchingForMenu;
private
int
mDownY;
public
DecorView(Context context,
int
featureId) {
super
(context);
mFeatureId = featureId;
}
@Override
public
boolean
dispatchKeyEvent(KeyEvent event) {
final
int
keyCode = event.getKeyCode();
// 代码省略
return
isDown ? PhoneWindow.
this
.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.
this
.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
@Override
public
boolean
dispatchTouchEvent(MotionEvent ev) {
final
Callback cb = getCallback();
return
cb !=
null
&& mFeatureId <
0
? cb.dispatchTouchEvent(ev) :
super
.dispatchTouchEvent(ev);
}
@Override
public
boolean
dispatchTrackballEvent(MotionEvent ev) {
final
Callback cb = getCallback();
return
cb !=
null
&& mFeatureId <
0
? cb.dispatchTrackballEvent(ev) :
super
.dispatchTrackballEvent(ev);
}
public
boolean
superDispatchKeyEvent(KeyEvent event) {
return
super
.dispatchKeyEvent(event);
}
public
boolean
superDispatchTouchEvent(MotionEvent event) {
return
super
.dispatchTouchEvent(event);
}
public
boolean
superDispatchTrackballEvent(MotionEvent event) {
return
super
.dispatchTrackballEvent(event);
}
@Override
public
boolean
onTouchEvent(MotionEvent event) {
return
onInterceptTouchEvent(event);
}
// 代码省略
}
|
可以看到,DecorView继承自FrameLayout, 它对于touch事件的分发( dispatchTouchEvent )、处理都是交给super类来处理,也就是FrameLayout来处理,我们在FrameLayout中没有看到相应的实现,那继续跟踪到FrameLayout的父类,即ViewGroup,我们看到了dispatchTouchEvent的实现,那我们就先看ViewGroup (Android 2.3 源码)是如何进行事件分发的吧。
ViewGroup的Touch事件分发
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
|
/**
* {@inheritDoc}
*/
@Override
public
boolean
dispatchTouchEvent(MotionEvent ev) {
if
(!onFilterTouchEventForSecurity(ev)) {
return
false
;
}
final
int
action = ev.getAction();
final
float
xf = ev.getX();
final
float
yf = ev.getY();
final
float
scrolledXFloat = xf + mScrollX;
final
float
scrolledYFloat = yf + mScrollY;
final
Rect frame = mTempRect;
boolean
disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) !=
0
;
if
(action == MotionEvent.ACTION_DOWN) {
if
(mMotionTarget !=
null
) {
// this is weird, we got a pen down, but we thought it was
// already down!
// XXX: We should probably send an ACTION_UP to the current
// target.
mMotionTarget =
null
;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
if
(disallowIntercept || !onInterceptTouchEvent(ev))
// 1、是否禁用拦截、是否拦截事件
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final
int
scrolledXInt = (
int
) scrolledXFloat;
final
int
scrolledYInt = (
int
) scrolledYFloat;
final
View[] children = mChildren;
final
int
count = mChildrenCount;
for
(
int
i = count -
1
; i >=
0
; i--)
// 2、迭代所有子view,查找触摸事件在哪个子view的坐标范围内
final
View child = children[i];
if
((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() !=
null
) {
child.getHitRect(frame);
// 3、获取child的坐标范围
if
(frame.contains(scrolledXInt, scrolledYInt))
// 4、判断发生该事件坐标是否在该child坐标范围内
// offset the event to the view's coordinate system
final
float
xc = scrolledXFloat - child.mLeft;
final
float
yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if
(child.dispatchTouchEvent(ev))
// 5、child处理该事件
// Event handled, we have a target now.
mMotionTarget = child;
return
true
;
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
boolean
isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if
(isUpOrCancel) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final
View target = mMotionTarget;
if
(target ==
null
) {
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
if
((mPrivateFlags & CANCEL_NEXT_UP_EVENT) !=
0
) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return
super
.dispatchTouchEvent(ev);
}
// if have a target, see if we're allowed to and want to intercept its
// events
if
(!disallowIntercept && onInterceptTouchEvent(ev)) {
final
float
xc = scrolledXFloat - (
float
) target.mLeft;
final
float
yc = scrolledYFloat - (
float
) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if
(!target.dispatchTouchEvent(ev)) {
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
// clear the target
mMotionTarget =
null
;
// Don't dispatch this event to our own view, because we already
// saw it when intercepting; we just want to give the following
// event to the normal onTouchEvent().
return
true
;
}
if
(isUpOrCancel) {
mMotionTarget =
null
;
}
// finally offset the event to the target's coordinate system and
// dispatch the event.
final
float
xc = scrolledXFloat - (
float
) target.mLeft;
final
float
yc = scrolledYFloat - (
float
) target.mTop;
ev.setLocation(xc, yc);
if
((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) !=
0
) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget =
null
;
}
return
target.dispatchTouchEvent(ev);
}
|
这个函数代码比较长,我们只看上文中标注的几个关键点。首先在代码1处可以看到一个条件判断,如果disallowIntercept和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?onInterceptTouchEvent就是ViewGroup对事件进行拦截的一个函数,返回该函数返回false则表示不拦截事件,反之则表示拦截。第二个条件是是对onInterceptTouchEvent方法的返回值取反,也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值的整体变为false,从而跳出了这个条件判断。例如我们需要实现ListView滑动删除某一项的功能,那么可以通过在onInterceptTouchEvent返回true,并且在onTouchEvent中实现相关的判断逻辑,从而实现该功能。
进入代码1内部的if后,有一个for循环,遍历了当前ViewGroup下的所有子child view,如果触摸该事件的坐标在某个child view的坐标范围内,那么该child view来处理这个触摸事件,即调用该child view的dispatchTouchEvent。如果该child view是ViewGroup类型,那么继续执行上面的判断,并且遍历子view;如果该child view不是ViewGroup类型,那么直接调用的是View中的dispatchTouchEvent方法,除非这个child view的类型覆写了该方法。我们看看View中的dispatchTouchEvent函数:
View的Touch事件分发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public
boolean
dispatchTouchEvent(MotionEvent event) {
if
(!onFilterTouchEventForSecurity(event)) {
return
false
;
}
if
(mOnTouchListener !=
null
&& (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(
this
, event)) {
return
true
;
}
return
onTouchEvent(event);
}
|
该函数中,首先判断该事件是否符合安全策略,然后判断该view是否是enable的 ,以及是否设置了Touch Listener,mOnTouchListener即我们通过setOnTouchListener设置的。
1
2
3
4
5
6
7
|
/**
* Register a callback to be invoked when a touch event is sent to this view.
* @param l the touch listener to attach to this view
*/
public
void
setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
|
如果mOnTouchListener.onTouch(this, event)返回false则继续执行onTouchEvent(event);如果mOnTouchListener.onTouch(this, event)返回true,则表示该事件被消费了,不再传递,因此也不会执行onTouchEvent(event)。这也验证了我们上文中留下的场景2,当onTouch函数返回true时,点击按钮,但我们的点击事件没有执行。那么我们还是先来看看onTouchEvent(event)函数到底做了什么吧。
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
|
/**
* Implement this method to handle touch screen motion events.
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public
boolean
onTouchEvent(MotionEvent event) {
final
int
viewFlags = mViewFlags;
if
((viewFlags & ENABLED_MASK) == DISABLED)
// 1、判断该view是否enable
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return
(((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if
(mTouchDelegate !=
null
) {
if
(mTouchDelegate.onTouchEvent(event)) {
return
true
;
}
}
if
(((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))
// 2、是否是clickable或者long clickable
switch
(event.getAction()) {
case
MotionEvent.ACTION_UP:
// 抬起事件
boolean
prepressed = (mPrivateFlags & PREPRESSED) !=
0
;
if
((mPrivateFlags & PRESSED) !=
0
|| prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean
focusTaken =
false
;
if
(isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
// 获取焦点
}
if
(!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if
(!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if
(mPerformClick ==
null
) {
mPerformClick =
new
PerformClick();
}
if
(!post(mPerformClick))
// post
performClick();
// 3、点击事件处理
}
}
}
if
(mUnsetPressedState ==
null
) {
mUnsetPressedState =
new
UnsetPressedState();
}
if
(prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
}
else
if
(!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break
;
case
MotionEvent.ACTION_DOWN:
if
(mPendingCheckForTap ==
null
) {
mPendingCheckForTap =
new
CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress =
false
;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break
;
case
MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break
;
case
MotionEvent.ACTION_MOVE:
final
int
x = (
int
) event.getX();
final
int
y = (
int
) event.getY();
// Be lenient about moving outside of buttons
int
slop = mTouchSlop;
if
((x <
0
- slop) || (x >= getWidth() + slop) ||
(y <
0
- slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if
((mPrivateFlags & PRESSED) !=
0
) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break
;
}
return
true
;
}
return
false
;
}
|
我们看到,在onTouchEvent函数中就是对ACTION_UP、ACTION_DOWN、ACTION_MOVE等几个事件进行处理,而最重要的就是UP事件了,因为这个里面包含了对用户点击事件的处理,或者是说对于用户而言相对重要一点,因此放在了第一个case中。在ACTION_UP事件中会判断该view是否enable、是否clickable、是否获取到了焦点,然后我们看到会通过post方法将一个PerformClick对象投递给UI线程,如果投递失败则直接调用performClick函数执行点击事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/**
* Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public
boolean
post(Runnable action) {
Handler handler;
if
(mAttachInfo !=
null
) {
handler = mAttachInfo.mHandler;
}
else
{
// Assume that post will succeed later
ViewRoot.getRunQueue().post(action);
return
true
;
}
return
handler.post(action);
}
|
我们看看PerformClick类吧。
1
2
3
4
5
|
private
final
class
PerformClick
implements
Runnable {
public
void
run() {
performClick();
}
}
|
可以看到,其内部就是包装了View类中的performClick()方法。再看performClick()方法:
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
|
/**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/
public
void
setOnClickListener(OnClickListener l) {
if
(!isClickable()) {
setClickable(
true
);
}
mOnClickListener = l;
}
/**
* Call this view's OnClickListener, if it is defined.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public
boolean
performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if
(mOnClickListener !=
null
) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(
this
);
return
true
;
}
return
false
;
}
|
代码很简单,主要就是调用了mOnClickListener.onClick(this);方法,即执行用户通过setOnClickListener设置进来的点击事件处理Listener。
总结
用户触摸屏幕产生一个触摸消息,系统底层将该消息转发给ViewRoot ( ViewRootImpl ),ViewRoot产生一个DISPATCHE_POINTER的消息,并且在handleMessage中处理该消息,最终会通过deliverPointerEvent(MotionEvent event)来处理该消息。在该函数中会调用mView.dispatchTouchEvent(event)来分发消息,该mView是一个ViewGroup类型,因此是ViewGroup的dispatchTouchEvent(event),在该函数中会遍历所有的child view,找到该事件的触发的左边与每个child view的坐标进行对比,如果触摸的坐标在该child view的范围内,则由该child view进行处理。如果该child view是ViewGroup类型,则继续上一步的查找过程;否则执行View中的dispatchTouchEvent(event)函数。在View的dispatchTouchEvent(event)中首先判断该控件是否enale以及mOnTouchListent是否为空,如果mOnTouchListener不为空则执行mOnTouchListener.onTouch(event)方法,如果该方法返回false则再执行View中的onTouchEvent(event)方法,并且在该方法中执行mOnClickListener.onClick(this, event) ;方法; 如果mOnTouchListener.onTouch(event)返回true则不会执行onTouchEvent方法,因此点击事件也不会被执行。
粗略的流程图如下 :
相信本文所述对大家进一步深入掌握Android程序设计有一定的借鉴价值。