引言

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。

所以。我们的事件操作主要就是发生在ViewViewGroup之间。

事件分发的概念

所谓点击事件的事件分发,就是当一个MotionEvent产生了以后,系统须要把这个事件传递给一个详细的View(ViewGroup也继承于View),这个传递的过程就叫做分发过程,这个点击事件的分发过程须要三个非常重要的方法来共同完毕:disPatchTouchEvent、onInterceptTouchEvent、onTouchEvent

  • public boolean disPatchTouchEvent(MotionEvent ev)

    用来进行事件的分发,假设事件能够传递给当前的View,那么此方法一定会被调用,Android中全部的点击事件都必须经过这种方法的分发。然后决定是自身消费当前事件还是继续往下分发给子控件处理。返回true表示不继续分发。事件被消费了,返回false则继续往下分发,假设是ViewGroup则分发给onInterceptTouchEvent进行推断是否拦截该事件,这种方法的返回结果受到当前ViewonTouchEvent和下级ViewdisPatchTouchEvent方法的影响,返回结果表示是否消耗当前事件。

  • 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是否被遮住等。ListenerInfoView的静态内部类,专门用来定义一些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返回值,仅仅要这个ViewCLICKABLELONG_CLICKABLE或者CONTEXT_CLICKABLE有一个为True。那么返回值就是True。onTouchEvent方法会消耗当前事件。

看下一段代码:

 if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}

这段代码的意思是假设View设置有代理。那么还会运行TouchDelegateonTouchEvent(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;
}

我们知道ViewLONG_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事件传递机制

请看兴许ViewGroup的事件分发机制

View的事件分发机制解析的更多相关文章

  1. ViewGroup事件分发机制解析

    最近在看View的事件分发机制,感觉比复杂的地方就是ViewGrop的dispatchTouchEvent函数,便对照着源码研究了一下.故名思意这个函数起到的作用就是分发事件,在具体分析之前还要说明几 ...

  2. Android View 的事件分发原理解析

    作为一名 Android 开发者,每天接触最多的就是 View 了.Android View 虽然不是四大组件,但其并不比四大组件的地位低.而 View 的核心知识点事件分发机制则是不少刚入门同学的拦 ...

  3. Android View的事件分发机制

    准备了一阵子,一直想写一篇事件分发的文章总结一下.这个知识点实在是太重要了. 一个应用的布局是丰富的,有TextView,ImageView,Button等.这些子View的外层还有ViewGroup ...

  4. Android View的事件分发机制和滑动冲突解决方案

    这篇文章会先讲Android中View的事件分发机制,然后再介绍Android滑动冲突的形成原因并给出解决方案.因水平有限,讲的不会太过深入,只希望各位看了之后对事件分发机制的流程有个大概的概念,并且 ...

  5. 【Android - 自定义View】之View的事件分发机制

    参考资料: View事件分发:http://blog.csdn.net/pi9nc/article/details/9281829 ViewGroup事件分发:http://blog.csdn.net ...

  6. Android中View的事件分发机制

    简介 事件也称MotionEvent,事件分发机制就是对MotionEvent事件的分发过程,即当一个MotionEvent发生之后,系统需要把这个事件传递给一个具体的View. 点击事件的分发过程由 ...

  7. Android view 的事件分发机制

    1 事件的传递顺序是 Activity -> Window -> 顶层View touch 事件产生后,最先由 activity 的 dispatchTouchEvent 处理 /** * ...

  8. Android View的事件分发机制探索

    概述 Android事件传递机制也是Android系统中比较重要的一块,事件类型有很多种,这里主要讨论TouchEvent的事件在framework层的传递处理机制.因为对于App开发人员来说,理解f ...

  9. Android View体系(五)从源码解析View的事件分发机制

    1.处理点击事件的方法 View的层级 我们知道View的结构是树形的结构,View可以放在ViewGroup中,这个ViewGroup也可以放到另一个ViewGroup中,这样层层的嵌套就组成了Vi ...

随机推荐

  1. 阅读笔记—EL表达式

    表达式语言(EL) 表达式语言是一种在JSP页面中使用的数据访问语言,通过它可以很方便地在JSP页面中访问应用程序数据. 使用EL访问数据 表达式语言的使用形式:              ${exp ...

  2. Leetcode47: Palindrome Linked List

    Given a singly linked list, determine if it is a palindrome. 推断一个链表是不是回文的,一个比較简单的办法是把链表每一个结点的值存在vect ...

  3. Android 给图片加边框

    图片处理时,有时需要为图片加一些边框,下面介绍一种为图片添加简单边框的方法. 基本思路是:将边框图片裁剪成八张小图片(图片大小最好一致,不然后面处理会很麻烦),分别对应左上角,左边,左下角,下边,右下 ...

  4. AngularJsDEMO

    接触AngularJs时间不长,纯粹是学着好玩而已,因此没有深挖原理,针对理论性的知识,园子里面有很多介绍,我就不多介绍了. 这里写了个简单的DEMO,部署起来就可以直接运行了,里面 大概用了最基础的 ...

  5. Linux 进程通信之管道

    管道是单向的.先进先出的,它把一个进程的输出和还有一个进程的输入连接在一起.一个进程(写进程)在管道的尾部写入数据,还有一个进程(读进程)从管道的头部读出数据.数据被一个进程读出后,将被从管道中删除, ...

  6. code-代码平台服务器路径

    下面记录的是各个平台的服务器路径(va使用) ("repo init -u ssh://vanzo/platform_89/manifest.git") ("repo i ...

  7. StringBuilder类的使用总结

    String 对象是不可改变的.每次使用 System.String 类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间.在需要对字符串执行重复修改的情况下,与创建新 ...

  8. JS contcat() 连接数组 函数

    语法: arrayObject.concat(arrayX,arrayX,......,arrayX) 1.把元素添加到数组中 arr.concat(a,b,c);2.把数组连起来 arr.conca ...

  9. python课程:python3的数字与字符串

    一下是基于python2的教程的 python中有 多个数据类型,和,两种字符串类型 他们都是不可变的.

  10. Linux常用命令及解析

    基本日常命令 init 3 (进入命令行页面) steup (设置网络) exit (退出用户) pwd(查看当前所在目录) date(查看当前系统时间) 举例:(date +%Y-%m-%d)以年月 ...