引言
Android事件构成
在Android中,事件主要包含点按、长按、拖拽、滑动等,点按又包含单击和双击,另外还包含单指操作和多指操作。全部这些都构成了Android中的事件响应。总的来说。全部的事件都由例如以下三个部分作为基础:
- 按下(ACTION_DOWN)
- 移动(ACTION_MOVE)
- 抬起(ACTION_UP)
全部的操作事件首先必须运行的是按下操作(ACTION_DOWN)。之后全部的操作都是以按下操作作为前提,当按下操作完毕后。接下来可能是一段移动(ACTION_MOVE)然后抬起(ACTION_UP)。或者是按下操作运行完毕后没有移动就直接抬起。这一系列的动作在Android中都能够进行控制。
这些操作事件都发生在我们手机的触摸屏上面,而我们手机上响应我们各种操作事件的就是各种各样的视图组件也就是View
,在Android中,全部的视图都继承于View
,另外通过各种布局组件(ViewGroup
)来对View
进行布局,ViewGroup
也继承于View
。全部的UI控件比如Button、TextView都是继承于View
,而全部的布局控件比如RelativeLayout、容器控件比如ListView都是继承于ViewGroup。
所以。我们的事件操作主要就是发生在View
和ViewGroup
之间。
事件分发的概念
所谓点击事件的事件分发,就是当一个MotionEvent
产生了以后,系统须要把这个事件传递给一个详细的View
(ViewGroup
也继承于View
),这个传递的过程就叫做分发过程,这个点击事件的分发过程须要三个非常重要的方法来共同完毕:disPatchTouchEvent、onInterceptTouchEvent、
和onTouchEvent
。
public boolean disPatchTouchEvent(MotionEvent ev)
用来进行事件的分发,假设事件能够传递给当前的View
,那么此方法一定会被调用,Android中全部的点击事件都必须经过这种方法的分发。然后决定是自身消费当前事件还是继续往下分发给子控件处理。返回true
表示不继续分发。事件被消费了,返回false
则继续往下分发,假设是ViewGroup
则分发给onInterceptTouchEvent
进行推断是否拦截该事件,这种方法的返回结果受到当前View
的onTouchEvent
和下级View
的disPatchTouchEvent
方法的影响,返回结果表示是否消耗当前事件。
public boolean onTouchEvent(MotionEvent ev)
在diaPatchTouchEvent
方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,假设不消耗,则在同一个事件序列其中,当前View
无法再次接收到事件。
public boolean onInterceptTouchEvent(MotionEvent ev)
是ViewGroup中才有的方法,View中没有,它的作用是负责事件的拦截,返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent
进行处理。返回false则不拦截,继续往下传。这是
ViewGroup
特有的方法。由于ViewGroup中可能还有子View,而在Android中View中是不能再包含子View
的(IOS能够),在上述方法内部被调用,假设当前View
拦截了某个事件,那么同一个事件序列中(指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,中间含有不定的ACTION_MOVE
事件,终于以ACTION_UP
事件结束)此方法不会被再次调用,返回结果表示是否拦截当前事件。
这三个方法能够用例如以下伪代码表示:
public boolean disPathchTouchEvent(MotionEvent ev){
//consume指代点击事件是否被消耗
boolean consume=false;
//表示当前父布局要拦截该事件
if(onInterceptTouchEvent(MotionEvent ev)){
consume=onTouchEvent(ev);
}else{
//传递给子元素去处理
child.disPatchTouchEvent(ev);
}
return consume;
}
分析View的事件分发机制
为了简单起见我们先从View
的事件分发机制開始分析。然后在分析ViewGroup
的,首先我们建一个简单的项目。这个项目里仅仅有一个Button,而且我们给这个Button设置点击事件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="100dp"
android:text="Click me" />
</RelativeLayout>
package com.example.testbtn;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener,OnTouchListener{
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn=(Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
btn.setOnTouchListener(this);
}
@Override
public void onClick(View v) {
Log.d("TAG", "OnClick");
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch"+event.getAction());
return false;
}
}
界面是这样的:
运行这个程序。点击Button。查看Log打印输出的信息:
(这里onTouch0代表的是ACTION_DOWN,onTouch1代表的是ACTION_UP,onTouch2表示ACTION_MOVE,由于我们仅仅是稳稳的点击了一下Button所以不会有ACTION_MOVE的Log信息出现)
这样我们能够得到一个初步的结论:onTouch()
方法是优先于onClick()
运行的。然后我们会发现onTouch()
方法有一个非常明显的和onClick()
方法不同的地方的,那就是它有一个Boolean
类型的返回值,假设我们把这个默觉得False的返回值改为True会怎么样呢:
发现了什么:onClick()
方法没有被运行,这里我们把这样的现象叫做点击事件被onTouch()
消费掉了。事件不会在继续向onClick()
方法传递了,那么事件分发机制最基本的几条我们已经了解了,以下我们来分析产生这样的机制的根本原因。
View对点击事件的处理过程
首先我们给出一个结论:Android中全部的事件都必须经过disPatchTouchEvent(MotionEvent ev
)这种方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理,那么我们就来看看这个disPatchTouchEvent(MotionEvent ev)
究竟干了什么。
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
代码有点多。我们一步步来看:
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
最前面这一段就是推断当前事件能否获得焦点,假设不能获得焦点或者不存在一个View
那我们就直接返回False跳出循环。接下来:
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
设置一些标记和处理input与手势等传递。不用管,到这里:
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
这里if (onFilterTouchEventForSecurity(event))
是用来推断View
是否被遮住等。ListenerInfo
是View
的静态内部类,专门用来定义一些XXXListener等方法的,到了重点:
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
非常长的一个推断。一个个来解释:第一个li
肯定不为空。由于在这个If推断语句之前就new了一个li
,第二个条件li.mOnTouchListener != null
,怎么确定这个mOnTouchListener
不为空呢?我们在View类里面发现了例如以下方法:
/**
* 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) {
getListenerInfo().mOnTouchListener = l;
}
意味着仅仅要给控件注冊了onTouch
事件这个mOnTouchListener
就一定会被赋值,接下来(mViewFlags & ENABLED_MASK) == ENABLED
是通过位与运算来推断这个View
是否是ENABLED
的,我们默认控件都是ENABLED
的所以这一条也成立,最后一条li.mOnTouchListener.onTouch(this, event)
是推断onTouch()
的返回值是否为True,我们后面把默觉得False的返回值改成了True,所以这一整系列的推断都是True。那么这个disPatchTouchEvent(MotionEvent ev)
方法直接就返回了True,那么接下来的代码都不会被运行,我们以下有这么一段代码:
if (!result && onTouchEvent(event)) {
result = true;
}
最開始我们onTouch()
方法的返回值是False的。那么
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
这里面的推断就不成立。result最開始的默认值也是false,那么此时假设
onTouchEvent(event)
返回值也是True,那么if (!result && onTouchEvent(event))
这种方法推断条件成立,disPatchTouchEvent(MotionEvent ev)
返回True,否则返回False。
这里我们得到两个结论:
-
OnTouchListener
的优先级比onTouchEvent
要高,联想到刚才的小Demo也能够得出onTouch
方法优先于onClick()
方法运行(onClick()
是在onTouchEvent(event)
方法中被运行的这个待会会说到) - 假设控件(View)的
onTouch
返回False或者mOnTouchListener
为null(控件没有设置setOnTouchListener
方法)或者控件不是ENABLE
的情况下会调用onTouchEvent
方法,此时dispatchTouchEvent
方法的返回值与onTouchEvent
的返回值一样。
那么接下来我们就分析dispatchTouchEvent
方法里面onTouchEvent
的实现,给出onTouchEvent
的源代码:
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 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)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_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 (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
代码还是非常多,我们依旧一段一段来分析,最前面的一段代码:
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 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)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
依据前面的分析我们知道这一段代码是对当前View
处于不可用状态的情况下的分析。通过凝视我们知道即使是一个不可用状态下的View
依旧会消耗点击事件。仅仅是不会对这个点击事件作出响应罢了,另外通过观察这个return返回值,仅仅要这个View
的CLICKABLE
和LONG_CLICKABLE
或者CONTEXT_CLICKABLE
有一个为True。那么返回值就是True。onTouchEvent
方法会消耗当前事件。
看下一段代码:
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
这段代码的意思是假设
View
设置有代理。那么还会运行TouchDelegate
的onTouchEvent(event)
方法,这个onTouchEvent(event)
的工作机制看起来和OnTouchListener
相似,这里不深入研究。
–《Android开发艺术探索》
以下看一下onTouchEvent
中对点击事件的详细处理流程:
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_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 (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
我们还是一行行来分解:
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
//省略
}
return false;
}
这个推断之前描写叙述过不再赘述,假设这个推断不成立直接跳到方法尾部返回False,假设推断成立则继续进入方法内部进行一个switch(event)的推断,这里ACTION_DOWN和ACTION_MOVE都仅仅是进行一些必要的设置与置位,我们主要看ACTION_UP:
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_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 (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 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)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
首先推断了是否被按下 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
接下来推断是不是能够获得焦点,同一时候尝试去获取焦点:
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
经过种种推断后我们看到这一行:
if (!post(mPerformClick)) {
performClick();
}
这是推断假设不是longPressed则通过post在UI Thread中运行一个PerformClick的Runnable。也就是performClick方法,这种方法的源代码例如以下:
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
我们发现了什么。那就是当ACTION_UP事件发生时,会触发performClick()
方法,假设这个View
设置了OnClickListener
那么终于会运行到OnClickListener
的回调方法onClick()
,这也就验证了刚才所说的:onClick()
方法是在onTouchEvent
内部被调用的。
同我们前面找到mOnTouchListener
在哪里赋值的一样。我们也能够找到mOnClickListener
在哪里赋值的:
/**
* 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(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
我们知道View
的LONG_CLICKABLE
属性默认是False的。须要的话我们能够自己在xml或者java文件里去设置。可是CLICKABLE
的False与True是和详细的View
有关的,比方我们知道Button是可点击的,可是TextView默认是不可点击的。可是假设给TextView设置了点击事件,那么依据
if (!isClickable()) {
setClickable(true);
}
这几行代码TextView也会被设置为可点击的,同理还有setOnLongClickListener
也有这样的作用:
/**
* Register a callback to be invoked when this view is clicked and held. If this view is not
* long clickable, it becomes long clickable.
*
* @param l The callback that will run
*
* @see #setLongClickable(boolean)
*/
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
总结
到此。View的事件分发机制已经分析完了。整个过程查了很多资料,最基本的是跟着任玉刚老师的《Android开发艺术探索》学习,最后把这个学习的过程记录下来就是这篇博客了,等有时间的时候把ViewGroup的事件分发机制也分析一遍。
主要參考
Android触摸屏事件派发机制详细解释与源代码分析一(View篇)
Android事件传递机制