android点击事件(View)

来源:互联网 发布:sql查询所有表 编辑:程序博客网 时间:2024/06/06 09:16

本文主要讲述一个view的点击事件相关知识,事件分发方面的可以去 android事件分发

阅读本文最好对事件有一定了解,可以通过阅读郭神的http://blog.csdn.net/guolin_blog/article/details/9097463

概述

1、onTouch如果返回true,那么 onTouchEvent执行不到,而onClick在onTouchEvent内,所以 onClick也无法执行,这样就屏蔽了 onClick

2、onClick和onTouch是观察者模式,但是onTouchEvent是方法重写,要自定义view

3、从源码中可以看出,onTouchEvent和onTouch这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

4、另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。

5、我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等一组事件。按理说每次事件都会触发dispatchTouchEvent。可是如果一组事件中的某个事件的dispatchTouchEvent返回了false,那表示这组事件已经处理完毕,后面的事件不会触发dispatchTouchEvent。

例如ACTION_DOWN的dispatchTouchEvent返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action的dispatchTouchEvent返回true(这里是指 dispatchTouchEvent返回true而不是onTouch 返回true) ,才会触发后一个action。(这里的action和事件是一个意思)

6 如果一个view是clickable或者longclickable,那么他永远会消费action_up事件,在onTouchEvent里面消费掉,不会传递给他的parent

源码流程

dispatchTouchEvent流程




我们对控件触摸,会产生DOWN,UP,MOVE等很多事件,我们把DOWN,MOVE..MOVE,UP称为一组事件,一组事件以DOWN开始,以UP结束,中间可能有若干个MOVE。每个事件都会调用dispatchTouchEvent(View内)。如果控件为enable的,而且有mOnTouchListener,就会进入onTouch。有没有mOnTouchListener,就看是否调用了setOnTouchListener(...).进入onTouch,如果返回了true那dispatchTouchEvent就直接返回true,如果onTouch返回了false,那进入onTouchEvent。onTouch的返回true一般表示此事件已经被消费,返回false表示此事件未被消费。所以onTouch和onTouchEvent有可能2个都调,也有可能只调其中一个。
public boolean dispatchTouchEvent(MotionEvent event) {    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&            mOnTouchListener.onTouch(this, event)) {        return true;    }    return onTouchEvent(event);}


onClick哪里调用

onClick调用有几个条件,首先onTouchEvent必须被调用,然后 MotionEvent的action是ACTION_UP,也就是说手抬起才可能触发onClick。进入onTouchEvent之后,如果 MotionEvent的action是ACTION_UP,就进入performClick,performClick内部调用onClick,注意下面37行,mPerformClick不是直接调用的,而是post,所以onClick也不是在onTouchEvent内部直接调用的,只是把 mPerformClick这个runnable加到了消息队列中
public boolean onTouchEvent(MotionEvent event) {    final int viewFlags = mViewFlags;    if ((viewFlags & ENABLED_MASK) == DISABLED) {        // 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) {        if (mTouchDelegate.onTouchEvent(event)) {            return true;        }    }    if (((viewFlags & CLICKABLE) == CLICKABLE ||            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {        switch (event.getAction()) {            case MotionEvent.ACTION_UP:                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;                if ((mPrivateFlags & 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 (!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 (!post(mPerformClick)) {                                performClick();                            }                        }                    }                    if (mUnsetPressedState == null) {                        mUnsetPressedState = new UnsetPressedState();                    }                    if (prepressed) {                        mPrivateFlags |= PRESSED;                        refreshDrawableState();                        postDelayed(mUnsetPressedState,                                ViewConfiguration.getPressedStateDuration());                    } else if (!post(mUnsetPressedState)) {                        // If the post failed, unpress right now                        mUnsetPressedState.run();                    }                    removeTapCallback();                }                break;            case MotionEvent.ACTION_DOWN:                if (mPendingCheckForTap == null) {                    mPendingCheckForTap = new CheckForTap();                }                mPrivateFlags |= PREPRESSED;                mHasPerformedLongPress = false;                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());                break;            case MotionEvent.ACTION_CANCEL:                mPrivateFlags &= ~PRESSED;                refreshDrawableState();                removeTapCallback();                break;            case MotionEvent.ACTION_MOVE:                final int x = (int) event.getX();                final int y = (int) event.getY();                // Be lenient about moving outside of buttons                int slop = mTouchSlop;                if ((x < 0 - slop) || (x >= getWidth() + slop) ||                        (y < 0 - slop) || (y >= getHeight() + slop)) {                    // Outside button                    removeTapCallback();                    if ((mPrivateFlags & PRESSED) != 0) {                        // Remove any future long press/tap checks                        removeLongPressCallback();                        // Need to switch from pressed to not pressed                        mPrivateFlags &= ~PRESSED;                        refreshDrawableState();                    }                }                break;        }        return true;    }    return false;}
public boolean performClick() {    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);    if (mOnClickListener != null) {        playSoundEffect(SoundEffectConstants.CLICK);        mOnClickListener.onClick(this);        return true;    }    return false;}

如何禁止GridView的滚动呢?

首先,我们知道GridView为什么会滚动,他和ListView一样继承自AbsListView,而AbsListView在move事件的处理过程中,会照常调用onTouchEvent,在这里实现滚动。我们想要禁止滚动,就得阻止事件传递到这里。

这个事件必然是dispatchTouchEvent先处理,可以在此时判断Action是不是MOVE,如果是就直接返回true,那么事件无法往下传递,就不会滚动
这是一种方法,通过继承GridView,并且重写dispatchTouchEvent实现


还有一种方法,dispatchTouchEvent会传递给onTouch,我们可以在onTouch里拦截,在这里判断Action是不是MOVE,如果是就直接返回true,那么就无法往下传递,也不会滚动

为什么button2次ontouch才有一次onclick

为什么2次ontouch才有一次onclick?
因为只有在event为case MotionEvent.ACTION_UP:才会进入performClick,performClick内部调用onclick


 EditText诡异事件

EditText编写setOnClickListener,发现如果焦点不在此EditText上(比如在其他EditText上),此时点击不会触发onclick,要再点击一次才能触发,相当于第一次点击获得焦点,第二次点击触发onclick。为什么会这样呢?

touch mode

Android有一种操作模式叫touch mode
当用户直接使用keys或trackball与UI进行交互的时候, 必须先使目标控件获取焦点(比如按钮),这样用户才会注意到是什么控件接收输入. 然而如果设备支持触摸手势的话, 用户可能使用触摸屏与UI进行交互, 这个时候就没有必要将目标控件高亮显示了(即,获取焦点). 因此就产生了这样一种交互模式叫"touch mode ."

对于一个拥有触摸屏功能的设备而言, 一旦用户用手点击屏幕, 设备立刻进入touch mode . 这时候被点击的控件只有isFocusableInTouchMode()方法返回true的时候才会 focusable , 比如EditText控件. 其他可以触摸的控件, 比如按钮, 当被点击的时候不会获取焦点; 它们只是简单地执行onClick事件而已.

任何时候只要用户点击key或滚动trackball, 设备就会退出touch mode ,并且找一个view将焦点置于其上. 此时用户可以不使用触摸手势了.

touch mode 在整个系统运行期间都是有效的(在任何activities中). 如果想要查询当前处于何种状态, 你可以调用View#isInTouchMode()来看看当前是否处于touch mode .
因此,对于Button,点击直接触发OnClick,对于EditView,点击先判断是否获得焦点,如果已经获得焦点,就执行onclick,否则就获取焦点。
001 public boolean onTouchEvent(MotionEvent event) {002         final float x = event.getX();003         final float y = event.getY();004         final int viewFlags = mViewFlags;005 006         if ((viewFlags & ENABLED_MASK) == DISABLED) {007             if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {008                 setPressed(false);009             }010             // A disabled view that is clickable still consumes the touch011             // events, it just doesn't respond to them.012             return (((viewFlags & CLICKABLE) == CLICKABLE ||013                     (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));014         }015 016         if (mTouchDelegate != null) {017             if (mTouchDelegate.onTouchEvent(event)) {018                 return true;019             }020         }021 022         if (((viewFlags & CLICKABLE) == CLICKABLE ||023                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {024             switch (event.getAction()) {025                 case MotionEvent.ACTION_UP:026                     boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;027                     if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {028                         // take focus if we don't have it already and we should in029                         // touch mode.030                         boolean focusTaken = false;031                         if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {032                             focusTaken = requestFocus();033                         }034 035                         if (prepressed) {036                             // The button is being released before we actually037                             // showed it as pressed.  Make it show the pressed038                             // state now (before scheduling the click) to ensure039                             // the user sees it.040                             setPressed(true, x, y);041                        }042 043                         if (!mHasPerformedLongPress) {044                             // This is a tap, so remove the longpress check045                             removeLongPressCallback();046 047                             // Only perform take click actions if we were in the pressed state048                             if (!focusTaken) {049                                 // Use a Runnable and post this rather than calling050                                 // performClick directly. This lets other visual state051                                 // of the view update before click actions start.052                                 if (mPerformClick == null) {053                                     mPerformClick = new PerformClick();054                                 }055                                 if (!post(mPerformClick)) {056                                     performClick();057                                 }058                             }059                         }060 061                         if (mUnsetPressedState == null) {062                             mUnsetPressedState = new UnsetPressedState();063                         }064 065                         if (prepressed) {066                             postDelayed(mUnsetPressedState,067                                     ViewConfiguration.getPressedStateDuration());068                         } else if (!post(mUnsetPressedState)) {069                             // If the post failed, unpress right now070                             mUnsetPressedState.run();071                         }072 073                         removeTapCallback();074                     }075                     break;076 077                 case MotionEvent.ACTION_DOWN:078                     mHasPerformedLongPress = false;079 080                     if (performButtonActionOnTouchDown(event)) {081                         break;082                     }083 084                     // Walk up the hierarchy to determine if we're inside a scrolling container.085                     boolean isInScrollingContainer = isInScrollingContainer();086 087                     // For views inside a scrolling container, delay the pressed feedback for088                     // a short period in case this is a scroll.089                     if (isInScrollingContainer) {090                         mPrivateFlags |= PFLAG_PREPRESSED;091                         if (mPendingCheckForTap == null) {092                             mPendingCheckForTap = new CheckForTap();093                         }094                         mPendingCheckForTap.x = event.getX();095                         mPendingCheckForTap.y = event.getY();096                         postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());097                     } else {098                         // Not inside a scrolling container, so show the feedback right away099                         setPressed(true, x, y);100                         checkForLongClick(0);101                     }102                     break;103 104                 case MotionEvent.ACTION_CANCEL:105                     setPressed(false);106                     removeTapCallback();107                     removeLongPressCallback();108                     break;109 110                 case MotionEvent.ACTION_MOVE:111                     drawableHotspotChanged(x, y);112 113                     // Be lenient about moving outside of buttons114                     if (!pointInView(x, y, mTouchSlop)) {115                         // Outside button116                         removeTapCallback();117                         if ((mPrivateFlags & PFLAG_PRESSED) != 0) {118                             // Remove any future long press/tap checks119                             removeLongPressCallback();120 121                             setPressed(false);122                         }123                     }124                     break;125             }126 127             return true;128         }129 130         return false;131     }

上述代码是View内的,可以看到31行有个isFocusableInTouchMode,对于EditText的第一次点击,而且当前焦点不是此EditText,那就会requestFocus,去获取焦点,focusTaken会变true,48行的if快就不执行,所以不调用performClick,也就不会调用OnClick。当第二次点击EditText时,31行isFocused会为true,就不在获取焦点,focusTaken为false,就可以进入48行的if块,能执行performClick,执行OnClick。
所以,要添加EditText的事件,最好写ontouch,保证每次点击都会触发。

EditText如何弹出软键盘的呢?

主要是通过onTouchEvent,我有一次重写onTouchEvent时,没有调用return super.onTouchEvent(event);  [这里的super就是EditText]
就无法弹出键盘

参考文献

http://blog.csdn.net/guolin_blog/article/details/9097463

http://jakend.iteye.com/blog/764521

http://blog.csdn.net/guolin_blog/article/details/9097463

http://blog.csdn.net/guolin_blog/article/details/9153747

 




0 0
原创粉丝点击