Android View 触摸屏事件派发机制和源码分析
来源:互联网 发布:linux配置web服务器 编辑:程序博客网 时间:2024/06/04 18:41
Android View 触摸屏事件派发机制和源码分析
最近参考Android SystemUI 的源码其中涉及到很多事件派发和处理相关的问题.早期感觉很复杂,后面有时间了通过结合log来分析渐渐有一定的理解.现在参考网络上面的文章自己也将这一方面的源码分析一下,同时写下来,以便然后回忆和加深理解.(基于mtk 的Android 5.1 系统)
Android 的触摸事件都是从dispatchTouchEvent()开始的,View 和ViewGroup 都是一样的.暂时只分析View,不分析ViewGroup.
1.View 的dispatchTouchEvent()开始
其实View的dispatchTouchEvent也是有父布局(某个ViewGroup,可以理解为LinerLayout)调用的.直接上源码.
在判断是ACTION_DOWN之前dispatchTouchEvent 做了处理手势相关的工作(mInputEventConsistencyVerifier),这个具体还没有研究,以后再研究,在笔记的后面有一个"Android的用户输入处理"可以参考.目前只是分析我们需要关注的重点.
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);
}
if (DBG_MOTION || DEBUG_DEFAULT) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
Xlog.i(VIEW_LOG_TAG, "Touch down dispatch to " + this + ", event = " + event);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
Xlog.i(VIEW_LOG_TAG, "Touch up dispatch to " + this + ", event = " + event);
}
}
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)dispatchTouchEvent: event = " + event + ",this = " + this);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {//判断当前View 没有被遮蔽
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//1.设置touchListener,2.是enable状态,3.touchListener的onTouch返回true
/// M : add log to help debugging
if (DBG_TOUCH) {
Xlog.d(VIEW_LOG_TAG, "handle Touch event by listerner, listener = " + li
+ ", event = " + event + ", this = " + this);
}
result = true;
}
if (!result && onTouchEvent(event)) {//touchListener的onTouch没有返回false,或者没有设置clickListener/longClickListener,而且onTouchEvent返回true.而且
onTouchEvent返回什么dispatchTouchEvent也会返回什么/// M : add log to help debugging
if (DBG_TOUCH) {
Xlog.d(VIEW_LOG_TAG, "handle Touch event by onTouchEvent, event = "
+ event + ", this = " + this);
}
result = true;
}
}
/// M : add log to help debugging
if (!result && DBG_TOUCH) {
Xlog.d(VIEW_LOG_TAG, "Do not handle Touch event, event = "
+ event + ", this = " + this);
}
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;
}
在onFilterTouchEventForSecurity里面看注释就知道是判断当前的View 有没有被遮蔽.我们考虑的是没有被遮蔽的情况,了就是onFilterTouchEventForSecurity返回true.
dispatchTouchEvent 在39行有一个if判断,这个判断依次做了3个小判断. if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))
第一步:li != null && li.mOnTouchListener != null,这个ListenerInfo 是不是为null并且mListenerInfo的mOnTouchListener 是不是为null.在View 的源码里面查找mListenerInfo就会找到是在getListenerInfo里面调用的,接着会发现是在setOnClickListener里面调用的.显然也就是我们判断我们也没有给这个View 控件设置View.OnClickListener事件.如果有就已经第2部分的判断
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
第二步: (mViewFlags & ENABLED_MASK) == ENABLED,这个就是判断当前View是不是enable的.这个是通过setEnabel方法来设置的,如果是enable的,就继续第3部的判断
第三步:li.mOnTouchListener.onTouch(this, event),判断View.OnTouchListener的onTouch()返回true.如果是true.就是讲result赋值true.也就是说关系到这个dispatchTouchEvent的返回值了.
上面3个判断是一个接着一个,第一个false了,后面就没有戏了,是不会执行的.
再往下面看if(!result&& onTouchEvent(event)),这个if 通过由result 和onTouchEvent()一起决定.前面已经分析关于result的部分,现在接着分析onTouchEvent()部分.
代码如下,但是有的长,还是一样,看重点就好,放弃不重要的细节.这个方法重点就是38行开始的if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {,这个进行了2个判断,容易一个判断满足了就可执行下面关于ACTION_DWON,ACTION_MOVE,ACTION_UP等事件的处理.同样分布判断这个条件.
第一步:(viewFlags & CLICKABLE) == CLICKABLE,判断是否设置了setClickable(),注意setOnClickListener(OnClickListener l)也能转调这个方法
第二步: (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE,判断是否设置了setLongClickable(boolean longClickable),注意public void setOnLongClickListener(OnLongClickListener l) 也能转调这个方法
上面的判断只要在View源码来搜索CLICKABLE和LONG_CLICKABLE找到对应的方法再找到调用的地方就知道是判断设置相关监听事件View.onClickListener/View.onLongClickListener的.
如果if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {,为true,View 的onTouchEvent()最终就是返回true,相关这个条件没有通过View 的onTouchEvent()最终就是返回false.再回溯到dispatchTouchEvent()就会知道这个onTouchEvent返回true, dispatchTouchEvent就是返回true,dispatchTouchEvent()就会知道这个onTouchEvent返回false, dispatchTouchEvent就是返回false.
12行:disable 的情况下也可以消耗掉点击事件,只是不会执行click等方法.
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)onTouchEvent 1: event = " + event + ",mTouchDelegate = "
+ mTouchDelegate + ",enable = " + isEnabled() + ",clickable = " + isClickable()
+ ",isLongClickable = " + isLongClickable() + ",this = " + this);
}
if ((viewFlags & ENABLED_MASK) == DISABLED) {//disable 的情况下也可以消耗掉点击事件,只是不会执行click等方法.
/// M: we need to reset the pressed state or remove prepressed callback
/// either up or cancel event happens.
final int action = event.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
} else if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
Xlog.d(VIEW_LOG_TAG, "View onTouch event, if a view's DISABLED&PFLAG_PREPRESSED"
+ "equal to TRUE, then remove callback mPrivateFlags = " + mPrivateFlags
+ ", this = " + this);
removeTapCallback();
}
}
// 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) {//设置了代理的情况下调用代理的onTouchEvent
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {//
判断是否设置了setClickable();判断是否设置了setLongClickable()switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)Touch up: prepressed = " + prepressed
+ ",this = " + this);
}
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) {
// 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 (DBG_TOUCH) {
Xlog.d(VIEW_LOG_TAG, "(View)Touch up: post perfomrClick"
+ " runnable, this = " + this);
}
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();
}
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();
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)Touch down: isInScrollingContainer = "
+ isInScrollingContainer + ",this = " + this);
}
// 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:
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)Touch cancel: this = " + this);
}
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)Touch move: x = " + x + ",y = " + y
+ ",mTouchSlop = " + mTouchSlop + ",this = " + this);
}
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;
}
判断了CLICKABLE和LONG_CLICKABLE之后需要分析的就是ACTION_DOWN,ACTION_MOVE,ACTION_UP这几个事件的处理了,ACTION_DOWN,ACTION_MOVE主要还是记录坐标位置等工作,重点还是ACTION_UP事件.
判断是ACTION_UP时间之后会判断之前是不是按下过(prepressed)->能不能获取焦点->获取焦点->没有被长按过->调用View.post()来执行一个实现了Runnable接口的PerformClick类的run方法,其实也就是performClick().
performClick()方法判断我们设置了View.OnClickListener之后就会根据实际情况播放触屏声音,同时执行View.OnClickListener的onClick()方法.如果设置了View.OnClickListener,performClick()就会返回true,相反就是false.当是这个方法的返回值不影响onTouchEvent() 的返回值.
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
if (DBG_TOUCH) {
Xlog.d(VIEW_LOG_TAG, "(View)performClick, listener = " + li.mOnClickListener
+ ",this = " + this);
}
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
到这里View 视图从dispatchTouchEvent()开始的触屏处理的主要流程已经分析完了.
现在总结一下:
1.View 的触摸屏时间是从dispatchTouchEvent() 开始的;
2.View设置相关的Listener 之后,是先处理onTouch,再来处理onClick(),其实onClick是最后才有机会处理.
先后顺序为:onTouch()->onTouchEvent->onClick(),当然这是事件没有被消耗也就是前面的方法没有返回true的情况下.
3.如果onTouch()返回true,将会阻止事件传递,也就是不会执行View的onTouchEvent()和onClick(),如果onTouch()返回false,才会继续走onTouchEvnet()流程.
4.onClick是在onTouchEvent()里面调用执行的,而且是在onTouchEvent()的ACTION_UP事件里面执行的.
5.如果Enable 为true,并且设置了OnTouchListener同时onTouch返回了true,就不会执行onTouchEvent(),onClick()更不会执行
具体看图
6.View 的dispatchTouchEvent()返回值:如果重写了onTouch(),就和onTouch()返回值一致.没有没有重写onTouch(),就和onTouchEvent()返回值一致.
7.View的onTouchEvent()返回值:如果设置View.OnClickListener或者View.OnLongClickListener.就返回true.否则就是false.
参考:
Android触摸屏事件派发机制详解与源码分析一(View篇)
http://blog.csdn.net/yanbober/article/details/45887547
Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
http://blog.csdn.net/yanbober/article/details/45912661
Android触摸屏事件派发机制详解与源码分析三(Activity篇)
http://blog.csdn.net/yanbober/article/details/45932123
Android的用户输入处理
http://www.cnblogs.com/samchen2009/p/3368158.html
0 0
- Android View 触摸屏事件派发机制和源码分析
- Android View触摸屏事件派发机制详解与源码分析
- Android Activity 触摸屏事件派发机制和源码分析
- Android ViewGroup 触摸屏事件派发机制和源码分析
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)onTouch,onClick,ontouchevent
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析
- 仙岛求药(迷宫寻找最短路径)DFS
- 如何快速在ecplise模拟器上安装APK包
- 访问模型一 最简单的访问服务器
- 有关top命令中的%st,sar命令中的%steal
- 常见的四种连接池实现
- Android View 触摸屏事件派发机制和源码分析
- STC单片机知识点
- 图的基本存储的基本方式二 邻接表(附上链式向前星方法)
- python2中为什么在进行类定义时最好要加object,不叫又怎样
- 每日学习英语计划
- 微信公众帐号开发教程第10篇-解析接口中的消息创建时间CreateTime
- LeetCode 365. Water and Jug Problem
- 值得推荐的android开源框架
- tomcat 的几点优化