说明:本博客为原创,转载请注明出处 http://blog.csdn.net/gucun4848
由于作者水平有限,错误在所难免,请见谅,可以留言,本人会及时改正
索引
基于上篇Android Touch事件总结一,本篇介绍Touch事件和Click、LongClick、Touch、TouchDelegate的关系。
OnClick
public void setOnClickListener(OnClickListener l){
//注册点击事件
}
用法很简单这里只介绍下View什么时候触发这个回调事件,源码如下:
public boolean onTouchEvent(MotionEvent event)
...
switch (action) {
case MotionEvent.ACTION_UP:
...
// 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();
}
//触发OnClick事件
if (!post(mPerformClick)) {
performClick();
}
}
}
...
// 可以看到,在OnTouchEvent方法中,接收到ACTION_UP事件后,判断如果View当前不是FocusableInTouchMode状态,没有LongPressed状态,触发OnClick事件。
OnLongClick
public void setOnLongClickListener(OnLongClickListener l){
//注册长按事件
}
用法很简单这里只介绍下View什么时候触发这个回调事件,源码如下:
public boolean onTouchEvent(MotionEvent event)
...
//还是在OnTouchEvent方法,接收到DOWN事件后,通过PostDelayed()实现
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
// 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();
// mPendingCheckForTap中触发事件
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
// 触发事件
checkForLongClick(0, x, y);
}
break;
OnTouchListener
public void setOnTouchListener(OnTouchListener l){
//注册触摸事件,这个回调会拦截View自身的OnTouchEvent方法。
}
用法很简单这里只介绍下View什么时候触发这个回调事件,源码如下:
public boolean dispatchTouchEvent(MotionEvent event) {
...
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;
}
}
}
// 在分发事件中先触发OnTouch回调,如果OnTouch回调中不消费事件,才会触发View自身的OnTouchEvent方法。
setTouchDelegate
public void setTouchDelegate(TouchDelegate l){
//注册触摸委派事件,这个回调常用于增加自身的可触摸面积。
//实例TouchDelegate对象需要两个参数Rect bounds, View delegateView
//bounds 触摸区域大小
//delegateView 委派事件的View
//这里需要**注意**的是需要delegateView的ParentView调用setTouchDelegate()方法!
}
这里看下View的源码:
public boolean onTouchEvent(MotionEvent event) {
...
//在View的OnTouchEvent方法中,优先调用的委派事件
//这里需要注意的是委派事件的View的ParentView必须保证自身的onTouchEvnet方法会被调用!
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
...
}
//这里是TouchDelegate中的onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
int x = (int)event.getX();
int y = (int)event.getY();
boolean sendToDelegate = false;
boolean hit = true;
boolean handled = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Rect bounds = mBounds;
//这里判断当前的触摸位置是不是在委派的边界内
if (bounds.contains(x, y)) {
mDelegateTargeted = true;
sendToDelegate = true;
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_MOVE:
sendToDelegate = mDelegateTargeted;
if (sendToDelegate) {
Rect slopBounds = mSlopBounds;
if (!slopBounds.contains(x, y)) {
hit = false;
}
}
break;
case MotionEvent.ACTION_CANCEL:
sendToDelegate = mDelegateTargeted;
mDelegateTargeted = false;
break;
}
//触摸位置在边界内
if (sendToDelegate) {
final View delegateView = mDelegateView;
if (hit) {
// Offset event coordinates to be inside the target view
event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
} else {
// Offset event coordinates to be outside the target view (in case it does
// something like tracking pressed state)
int slop = mSlop;
event.setLocation(-(slop * 2), -(slop * 2));
}
//调用了委派事件的View的dispatchTouchEvent方法.
handled = delegateView.dispatchTouchEvent(event);
}
return handled;
}
Demo
GitHub地址: GitHub
环境: Windows7+JAVA8
IDE: AndroidStdio2.2.2
compileSdkVersion:24
测试设备:Nexus5(6.0.1)
运行Demo的时候需要在Manifest.xml中启动项设置成MainViewTouchClickActivity
点击灰色区域,可以触发黄色View的OnClick事件