android点击事件(View)

本文主要讲述一个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 touch
011             // 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 in
029                         // 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 actually
037                             // showed it as pressed.  Make it show the pressed
038                             // state now (before scheduling the click) to ensure
039                             // the user sees it.
040                             setPressed(true, x, y);
041                        }
042 
043                         if (!mHasPerformedLongPress) {
044                             // This is a tap, so remove the longpress check
045                             removeLongPressCallback();
046 
047                             // Only perform take click actions if we were in the pressed state
048                             if (!focusTaken) {
049                                 // Use a Runnable and post this rather than calling
050                                 // performClick directly. This lets other visual state
051                                 // 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 now
070                             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 for
088                     // 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 away
099                         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 buttons
114                     if (!pointInView(x, y, mTouchSlop)) {
115                         // Outside button
116                         removeTapCallback();
117                         if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
118                             // Remove any future long press/tap checks
119                             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]
就无法弹出键盘

参考文献



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值