码迷,mamicode.com
首页 > 移动开发 > 详细

Android 触摸事件 点击事件的分发机制 详解

时间:2014-10-31 13:24:14      阅读:314      评论:0      收藏:0      [点我收藏+]

标签:android   style   blog   io   color   os   ar   for   sp   

最近发现团队里有些员工在做一些自定义控件的时候感觉比较吃力。尤其是做触摸事件这种东西的时候。很多人对机制并不理解。因为百度出来的东西都太理论化了。确实不好理解。

今天带大家坐几个小demo。帮助理解一下。

先从简单的view 的事件分发机制开始解释。

我们首先自定义一个工程

package com.example.testtouch;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.TextView;

public class MainActivity extends Activity {

    private TextView tv;
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedsInstanceState) {
        super.onCreate(savedsInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) this.findViewById(R.id.tv);
        iv = (ImageView) this.findViewById(R.id.iv);
        iv.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    Log.v("test", "iv event down ==" + MotionEvent.ACTION_DOWN);
                }
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    Log.v("test", "iv event up ==" + MotionEvent.ACTION_UP);
                }
                return false;
            }
        });
        tv.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    Log.v("test", "tv event down ==" + MotionEvent.ACTION_DOWN);
                }
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    Log.v("test", "tv event up ==" + MotionEvent.ACTION_UP);
                }
                return false;
            }
        });

        tv.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Log.v("test", "on click");
            }
        });
    }
}

然后运行起来以后 点击一下那个textview。

 

输出日志如下:

 

10-31 02:54:40.456: V/test(1430): tv event down ==0
10-31 02:54:40.523: V/test(1430): tv event up ==1
10-31 02:54:40.526: V/test(1430): on click

 

可以看出来 ontouch事件是在onclick事件以前被调用的。

如果我们把 ontouch事件的返回值 更改为true的话。那么就会发现 onclick 事件就不会执行了。

 

可以理解成 如果ontouch事件 返回的值为true的话 那么剩下的触摸操作全部都被拦截了。

 

换句话说 只要这个方法返回值 为true 那么剩下的操作就全部都没有了。

 

我们可以看源代码 去验证一下。首先我们得知道一个前提。 那就是view的事件分发都是由 dispatchTouchEvent

这个方法来控制的,这个方法 在view的类里面定义 我们去看一下源代码。

 

/**
     * 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 (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                    mOnTouchListener.onTouch(this, event)) {
                return true;
            }

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

主要看这个部分

if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}

if (onTouchEvent(event)) {
return true;
}

 

如果mOnTouchListener  这个不为空 并且 (mViewFlags & ENABLED_MASK) == ENABLED(代表这个控件可以被点击)

并且mOnTouchListener.onTouch(this, event) 这个也返回true  那么dispatchTouchEvent就返回true了。

否则就执行 onTouchEvent(event) 这个方法 并且返回true。

 

分析完这个 就比较好理解了,这边是先调用的OnTouchListener.onTouch(this, event)  这个事件,所以我们能看到log输出 ontouch事件

 

是在onclick时间之前的。并且这个方法 如果返回false  才会走到onTouchEvent 这个事件里面去。如果返回true 那么dispatchTouchEvent

 

这个函数就直接返回了。onTouchEvent  就永远不会得到执行。这也就解释了为什么我们如果把onTouch事件的值设置成true ,onClick方法

就不会得到执行。

当然也可以看出来 oncLIClick事件是在onTouchEvent里执行的。哪我们继续看源代码onTouchEvent 是怎么做的。

 

  1 /**
  2      * Implement this method to handle touch screen motion events.
  3      *
  4      * @param event The motion event.
  5      * @return True if the event was handled, false otherwise.
  6      */
  7     public boolean onTouchEvent(MotionEvent event) {
  8         final int viewFlags = mViewFlags;
  9 
 10         if ((viewFlags & ENABLED_MASK) == DISABLED) {
 11             if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
 12                 mPrivateFlags &= ~PRESSED;
 13                 refreshDrawableState();
 14             }
 15             // A disabled view that is clickable still consumes the touch
 16             // events, it just doesn‘t respond to them.
 17             return (((viewFlags & CLICKABLE) == CLICKABLE ||
 18                     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
 19         }
 20 
 21         if (mTouchDelegate != null) {
 22             if (mTouchDelegate.onTouchEvent(event)) {
 23                 return true;
 24             }
 25         }
 26 
 27         if (((viewFlags & CLICKABLE) == CLICKABLE ||
 28                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
 29             switch (event.getAction()) {
 30                 case MotionEvent.ACTION_UP:
 31                     boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
 32                     if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
 33                         // take focus if we don‘t have it already and we should in
 34                         // touch mode.
 35                         boolean focusTaken = false;
 36                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
 37                             focusTaken = requestFocus();
 38                         }
 39 
 40                         if (prepressed) {
 41                             // The button is being released before we actually
 42                             // showed it as pressed.  Make it show the pressed
 43                             // state now (before scheduling the click) to ensure
 44                             // the user sees it.
 45                             mPrivateFlags |= PRESSED;
 46                             refreshDrawableState();
 47                        }
 48 
 49                         if (!mHasPerformedLongPress) {
 50                             // This is a tap, so remove the longpress check
 51                             removeLongPressCallback();
 52 
 53                             // Only perform take click actions if we were in the pressed state
 54                             if (!focusTaken) {
 55                                 // Use a Runnable and post this rather than calling
 56                                 // performClick directly. This lets other visual state
 57                                 // of the view update before click actions start.
 58                                 if (mPerformClick == null) {
 59                                     mPerformClick = new PerformClick();
 60                                 }
 61                                 if (!post(mPerformClick)) {
 62                                     performClick();
 63                                 }
 64                             }
 65                         }
 66 
 67                         if (mUnsetPressedState == null) {
 68                             mUnsetPressedState = new UnsetPressedState();
 69                         }
 70 
 71                         if (prepressed) {
 72                             postDelayed(mUnsetPressedState,
 73                                     ViewConfiguration.getPressedStateDuration());
 74                         } else if (!post(mUnsetPressedState)) {
 75                             // If the post failed, unpress right now
 76                             mUnsetPressedState.run();
 77                         }
 78                         removeTapCallback();
 79                     }
 80                     break;
 81 
 82                 case MotionEvent.ACTION_DOWN:
 83                     mHasPerformedLongPress = false;
 84 
 85                     if (performButtonActionOnTouchDown(event)) {
 86                         break;
 87                     }
 88 
 89                     // Walk up the hierarchy to determine if we‘re inside a scrolling container.
 90                     boolean isInScrollingContainer = isInScrollingContainer();
 91 
 92                     // For views inside a scrolling container, delay the pressed feedback for
 93                     // a short period in case this is a scroll.
 94                     if (isInScrollingContainer) {
 95                         mPrivateFlags |= PREPRESSED;
 96                         if (mPendingCheckForTap == null) {
 97                             mPendingCheckForTap = new CheckForTap();
 98                         }
 99                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
100                     } else {
101                         // Not inside a scrolling container, so show the feedback right away
102                         mPrivateFlags |= PRESSED;
103                         refreshDrawableState();
104                         checkForLongClick(0);
105                     }
106                     break;
107 
108                 case MotionEvent.ACTION_CANCEL:
109                     mPrivateFlags &= ~PRESSED;
110                     refreshDrawableState();
111                     removeTapCallback();
112                     break;
113 
114                 case MotionEvent.ACTION_MOVE:
115                     final int x = (int) event.getX();
116                     final int y = (int) event.getY();
117 
118                     // Be lenient about moving outside of buttons
119                     if (!pointInView(x, y, mTouchSlop)) {
120                         // Outside button
121                         removeTapCallback();
122                         if ((mPrivateFlags & PRESSED) != 0) {
123                             // Remove any future long press/tap checks
124                             removeLongPressCallback();
125 
126                             // Need to switch from pressed to not pressed
127                             mPrivateFlags &= ~PRESSED;
128                             refreshDrawableState();
129                         }
130                     }
131                     break;
132             }
133             return true;
134         }
135 
136         return false;
137     }

 

 

看62行 有一个   performClick() 我们去看看这个方法。

 /**
     * 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;
    }

 

 看到没,我们的点击事件回调就是在这里做的!

 

回过头来 我们再看看 onTouchEvent 那个switch语句,我们会发现 最终 他们的返回值 都是true!!! 无论是什么事件 都是return true。

然后回过头来 看  dispatchTouchEvent 这个方法。

前面两行代码。

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}

大家都知道 触摸事件是由dispatchTouchEvent  这个函数去进行分发的。无论是什么触摸事件,都是手指先点下去 也就是 action down事件。action down的值是0.

 

所以你看 这边的代码意思就是如果action down的返回事件是true 那就分发。如果action down的返回onTouchEvent都是false的话 哪后面的事件就全部拦截了。

 

可以理解成。对于dispatchTouchEvent 来说 如果ACTION_DOWN事件返回true,就说明它需要处理这个事件,就让它接收所有的触屏事件,否则,说明它不用处理,也就不让它接收后续的触屏事件了。

 

Android 触摸事件 点击事件的分发机制 详解

标签:android   style   blog   io   color   os   ar   for   sp   

原文地址:http://www.cnblogs.com/punkisnotdead/p/4064623.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!