事件 订阅
事件,读音是shì jiàn,汉语词语,意思是事情、事项。 展开全文
事件,读音是shì jiàn,汉语词语,意思是事情、事项。
信息
外文名
event
拼    音
shì jiàn
中文名
事件
解    释
事情、事项
事件解释
收起全文
精华内容
参与话题
问答
  • 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识。也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何...

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463

     

    其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识。也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?等等……对于这些问题,我并没有给出非常详细的回答,因为我知道如果想要彻底搞明白这些问题,掌握Android事件分发机制是必不可少的,而Android事件分发机制绝对不是三言两语就能说得清的。

     

    在我经过较长时间的筹备之后,终于决定开始写这样一篇文章了。目前虽然网上相关的文章也不少,但我觉得没有哪篇写得特别详细的(也许我还没有找到),多数文章只是讲了讲理论,然后配合demo运行了一下结果。而我准备带着大家从源码的角度进行分析,相信大家可以更加深刻地理解Android事件分发机制。

     

    阅读源码讲究由浅入深,循序渐进,因此我们也从简单的开始,本篇先带大家探究View的事件分发,下篇再去探究难度更高的ViewGroup的事件分发。

     

    那我们现在就开始吧!比如说你当前有一个非常简单的项目,只有一个Activity,并且Activity中只有一个按钮。你可能已经知道,如果想要给这个按钮注册一个点击事件,只需要调用:

    button.setOnClickListener(new OnClickListener() {
    	@Override
    	public void onClick(View v) {
    		Log.d("TAG", "onClick execute");
    	}
    });

    这样在onClick方法里面写实现,就可以在按钮被点击的时候执行。你可能也已经知道,如果想给这个按钮再添加一个touch事件,只需要调用:

    button.setOnTouchListener(new OnTouchListener() {
    	@Override
    	public boolean onTouch(View v, MotionEvent event) {
    		Log.d("TAG", "onTouch execute, action " + event.getAction());
    		return false;
    	}
    });

    onTouch方法里能做的事情比onClick要多一些,比如判断手指按下、抬起、移动等事件。那么如果我两个事件都注册了,哪一个会先执行呢?我们来试一下就知道了,运行程序点击按钮,打印结果如下:

     

     

    可以看到,onTouch是优先于onClick执行的,并且onTouch执行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还可能会有多次ACTION_MOVE的执行,如果你手抖了一下)。因此事件传递的顺序是先经过onTouch,再传递到onClick。

     

    细心的朋友应该可以注意到,onTouch方法是有返回值的,这里我们返回的是false,如果我们尝试把onTouch方法里的返回值改成true,再运行一次,结果如下:

     

     

    我们发现,onClick方法不再执行了!为什么会这样呢?你可以先理解成onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。

     

    如果到现在为止,以上的所有知识点你都是清楚的,那么说明你对Android事件传递的基本用法应该是掌握了。不过别满足于现状,让我们从源码的角度分析一下,出现上述现象的原理是什么。

     

    首先你需要知道一点,只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法。那当我们去点击按钮的时候,就会去调用Button类里的dispatchTouchEvent方法,可是你会发现Button类里并没有这个方法,那么就到它的父类TextView里去找一找,你会发现TextView里也没有这个方法,那没办法了,只好继续在TextView的父类View里找一找,这个时候你终于在View里找到了这个方法,示意图如下:

     

     

    然后我们来看一下View中dispatchTouchEvent方法的源码:

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

    这个方法非常的简洁,只有短短几行代码!我们可以看到,在这个方法内,首先是进行了一个判断,如果mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。

     

    先看一下第一个条件,mOnTouchListener这个变量是在哪里赋值的呢?我们寻找之后在View里发现了如下方法:

    public void setOnTouchListener(OnTouchListener l) {
        mOnTouchListener = l;
    }

    Bingo!找到了,mOnTouchListener正是在setOnTouchListener方法里赋值的,也就是说只要我们给控件注册了touch事件,mOnTouchListener就一定被赋值了。

     

    第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。

     

    第三个条件就比较关键了,mOnTouchListener.onTouch(this, event),其实也就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。

     

    现在我们可以结合前面的例子来分析一下了,首先在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的,也是印证了刚刚的打印结果。而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行。而打印结果也证实了如果onTouch返回true,onClick就不会再执行了。

     

    根据以上源码的分析,从原理上解释了我们前面例子的运行结果。而上面的分析还透漏出了一个重要的信息,那就是onClick的调用肯定是在onTouchEvent(event)方法中的!那我们马上来看下onTouchEvent的源码,如下所示:

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

    相较于刚才的dispatchTouchEvent方法,onTouchEvent方法复杂了很多,不过没关系,我们只挑重点看就可以了。

     

    首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。在经过种种判断之后,会执行到第38行的performClick()方法,那我们进入到这个方法里瞧一瞧:

    public boolean performClick() {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        if (mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            mOnClickListener.onClick(this);
            return true;
        }
        return false;
    }

    可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法,那mOnClickListener又是在哪里赋值的呢?经过寻找后找到如下方法:

    public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        mOnClickListener = l;
    }

    一切都是那么清楚了!当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值。然后每当控件被点击时,都会在performClick()方法里回调被点击控件的onClick方法。

     

    这样View的整个事件分发的流程就让我们搞清楚了!不过别高兴的太早,现在还没结束,还有一个很重要的知识点需要说明,就是touch事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。

     

    说到这里,很多的朋友肯定要有巨大的疑问了。这不是在自相矛盾吗?前面的例子中,明明在onTouch事件里面返回了false,ACTION_DOWN和ACTION_UP不是都得到执行了吗?其实你只是被假象所迷惑了,让我们仔细分析一下,在前面的例子当中,我们到底返回的是什么。

    参考着我们前面分析的源码,首先在onTouch事件里返回了false,就一定会进入到onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。由于我们点击了按钮,就会进入到第14行这个if判断的内部,然后你会发现,不管当前的action是什么,最终都一定会走到第89行,返回一个true。

     

    是不是有一种被欺骗的感觉?明明在onTouch事件里返回了false,系统还是在onTouchEvent方法中帮你返回了true。就因为这个原因,才使得前面的例子中ACTION_UP可以得到执行。

     

    那我们可以换一个控件,将按钮替换成ImageView,然后给它也注册一个touch事件,并返回false。如下所示:

    imageView.setOnTouchListener(new OnTouchListener() {
    	@Override
    	public boolean onTouch(View v, MotionEvent event) {
    		Log.d("TAG", "onTouch execute, action " + event.getAction());
    		return false;
    	}
    });

    运行一下程序,点击ImageView,你会发现结果如下:

     

     

    在ACTION_DOWN执行完后,后面的一系列action都不会得到执行了。这又是为什么呢?因为ImageView和按钮不同,它是默认不可点击的,因此在onTouchEvent的第14行判断时无法进入到if的内部,直接跳到第91行返回了false,也就导致后面其它的action都无法执行了。

     

    好了,关于View的事件分发,我想讲的东西全都在这里了。现在我们再来回顾一下开篇时提到的那三个问题,相信每个人都会有更深一层的理解。

     

    1. onTouch和onTouchEvent有什么区别,又该如何使用?

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

     

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

     

    2. 为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?

    如果你阅读了Android滑动框架完全解析,教你如何一分钟实现滑动菜单特效这篇文章,你应该会知道滑动菜单的功能是通过给ListView注册了一个touch事件来实现的。如果你在onTouch方法里处理完了滑动逻辑后返回true,那么ListView本身的滚动事件就被屏蔽了,自然也就无法滑动(原理同前面例子中按钮不能点击),因此解决办法就是在onTouch方法里返回false。

     

    3. 为什么图片轮播器里的图片使用Button而不用ImageView?

    提这个问题的朋友是看过了Android实现图片滚动控件,含页签功能,让你的应用像淘宝一样炫起来 这篇文章。当时我在图片轮播器里使用Button,主要就是因为Button是可点击的,而ImageView是不可点击的。如果想要使用ImageView,可以有两种改法。第一,在ImageView的onTouch方法里返回true,这样可以保证ACTION_DOWN之后的其它action都能得到执行,才能实现图片滚动的效果。第二,在布局文件里面给ImageView增加一个android:clickable="true"的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的。

     

    今天的讲解就到这里了,相信大家现在对Android事件分发机制又有了进一步的认识,在后面的文章中我会再带大家一起探究Android中ViewGroup的事件分发机制,感兴趣的朋友请继续阅读 Android事件分发机制完全解析,带你从源码的角度彻底理解(下) 。

     

    关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

    微信扫一扫下方二维码即可关注:

            

    展开全文
  • Android事件分发机制 详解攻略,您值得拥有

    万次阅读 多人点赞 2017-01-06 11:43:05
    网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全、思路不清晰、无源码分析、简单问题复杂化等等 今天,我将全面总结Android的事件分发机制,我能保证这是市面上的最全面、最清晰、最易懂的 ...

    前言

    • Android事件分发机制是Android开发者必须了解的基础
    • 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全、思路不清晰、无源码分析、简单问题复杂化等等
    • 今天,我将全面总结Android的事件分发机制,我能保证这是市面上的最全面、最清晰、最易懂的
    1. 本文秉着“结论先行、详细分析在后”的原则,即先让大家感性认识,再通过理性分析从而理解问题;
    2. 所以,请各位读者先记住结论,再往下继续看分析;
    • 文章较长,阅读需要较长时间,建议收藏等充足时间再进行阅读

    目录

    示意图


    1. 基础认知

    1.1 事件分发的对象是谁?

    答:点击事件(Touch事件)

    • 定义
      当用户触摸屏幕时(ViewViewGroup派生的控件),将产生点击事件(Touch事件)

    Touch事件的相关细节(发生触摸的位置、时间等)被封装成MotionEvent对象

    • 事件类型(4种)
    事件类型 具体动作
    MotionEvent.ACTION_DOWN 按下View(所有事件的开始)
    MotionEvent.ACTION_UP 抬起View(与DOWN对应)
    MotionEvent.ACTION_MOVE 滑动View
    MotionEvent.ACTION_CANCEL 结束事件(非人为原因)
    • 特别说明:事件列

    从手指接触屏幕 至 手指离开屏幕,这个过程产生的一系列事件

    注:一般情况下,事件列都是以DOWN事件开始、UP事件结束,中间有无数的MOVE事件,如下图:
    事件列

    即当一个点击事件(MotionEvent )产生后,系统需把这个事件传递给一个具体的 View 去处理。

    1.2 事件分发的本质

    答:将点击事件(MotionEvent)传递到某个具体的View & 处理的整个过程

    即 事件传递的过程 = 分发过程。

    1.3 事件在哪些对象之间进行传递?

    答:Activity、ViewGroup、View

    • AndroidUI界面由ActivityViewGroupView 及其派生类组成
      UI界面

    示意图

    1.4 事件分发的顺序

    即 事件传递的顺序:Activity -> ViewGroup -> View

    即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View

    示意图

    ###1.5 事件分发过程由哪些方法协作完成?
    答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()

    示意图

    下文会对这3个方法进行详细介绍

    ###1.6 总结
    示意图

    • 至此,相信大家已经对 Android的事件分发有了感性的认知
    • 下面,我将详细介绍Android事件分发机制

    2. 事件分发机制 源码分析

    • 请谨记:Android事件分发流程 = Activity -> ViewGroup -> View

    即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View

    示意图

    • 从上可知,要想充分理解Android分发机制,本质上是要理解:
      1. Activity对点击事件的分发机制
      2. ViewGroup对点击事件的分发机制
      3. View对点击事件的分发机制
    • 下面,我将通过源码,全面解析 事件分发机制

    即按顺序讲解:Activity事件分发机制、ViewGroup事件分发机制、View事件分发机制

    2.1 Activity的事件分发机制

    当一个点击事件发生时,事件最先传到ActivitydispatchTouchEvent()进行事件分发

    2.1.1 源码分析

    /**
      * 源码分析:Activity.dispatchTouchEvent()
      */ 
        public boolean dispatchTouchEvent(MotionEvent ev) {
    
                // 一般事件列开始都是DOWN事件 = 按下事件,故此处基本是true
                if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    
                    onUserInteraction();
                    // ->>分析1
    
                }
    
                // ->>分析2
                if (getWindow().superDispatchTouchEvent(ev)) {
    
                    return true;
                    // 若getWindow().superDispatchTouchEvent(ev)的返回true
                    // 则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束
                    // 否则:继续往下调用Activity.onTouchEvent
    
                }
                // ->>分析4
                return onTouchEvent(ev);
            }
    
    
    /**
      * 分析1:onUserInteraction()
      * 作用:实现屏保功能
      * 注:
      *    a. 该方法为空方法
      *    b. 当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
      */
          public void onUserInteraction() { 
    
          }
          // 回到最初的调用原处
    
    /**
      * 分析2:getWindow().superDispatchTouchEvent(ev)
      * 说明:
      *     a. getWindow() = 获取Window类的对象
      *     b. Window类是抽象类,其唯一实现类 = PhoneWindow类;即此处的Window类对象 = PhoneWindow类对象
      *     c. Window类的superDispatchTouchEvent() = 1个抽象方法,由子类PhoneWindow类实现
      */
        @Override
        public boolean superDispatchTouchEvent(MotionEvent event) {
    
            return mDecor.superDispatchTouchEvent(event);
            // mDecor = 顶层View(DecorView)的实例对象
            // ->> 分析3
        }
    
    /**
      * 分析3:mDecor.superDispatchTouchEvent(event)
      * 定义:属于顶层View(DecorView)
      * 说明:
      *     a. DecorView类是PhoneWindow类的一个内部类
      *     b. DecorView继承自FrameLayout,是所有界面的父类
      *     c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
      */
        public boolean superDispatchTouchEvent(MotionEvent event) {
    
            return super.dispatchTouchEvent(event);
            // 调用父类的方法 = ViewGroup的dispatchTouchEvent()
            // 即 将事件传递到ViewGroup去处理,详细请看ViewGroup的事件分发机制
    
        }
        // 回到最初的调用原处
    
    /**
      * 分析4:Activity.onTouchEvent()
      * 定义:属于顶层View(DecorView)
      * 说明:
      *     a. DecorView类是PhoneWindow类的一个内部类
      *     b. DecorView继承自FrameLayout,是所有界面的父类
      *     c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
      */
      public boolean onTouchEvent(MotionEvent event) {
    
            // 当一个点击事件未被Activity下任何一个View接收 / 处理时
            // 应用场景:处理发生在Window边界外的触摸事件
            // ->> 分析5
            if (mWindow.shouldCloseOnTouch(this, event)) {
                finish();
                return true;
            }
            
            return false;
            // 即 只有在点击事件在Window边界外才会返回true,一般情况都返回false,分析完毕
        }
    
    /**
      * 分析5:mWindow.shouldCloseOnTouch(this, event)
      */
        public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        // 主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
        if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
            return true;
        }
        return false;
        // 返回true:说明事件在边界外,即 消费事件
        // 返回false:未消费(默认)
    }
    // 回到分析4调用原处
    

    2.1.2 总结

    • 当一个点击事件发生时,从Activity的事件分发开始(Activity.dispatchTouchEvent()

    示意图

    • 方法总结

    示意图

    那么,ViewGroupdispatchTouchEvent()什么时候返回true / false?请继续往下看ViewGroup事件的分发机制


    2.2 ViewGroup事件的分发机制

    从上面Activity事件分发机制可知,ViewGroup事件分发机制从dispatchTouchEvent()开始

    2.2.1 源码分析

    1. Android 5.0后,ViewGroup.dispatchTouchEvent()的源码发生了变化(更加复杂),但原理相同;
    2. 本文为了让读者容易理解,故采用Android 5.0前的版本
    /**
      * 源码分析:ViewGroup.dispatchTouchEvent()
      */ 
        public boolean dispatchTouchEvent(MotionEvent ev) { 
    
        ... // 仅贴出关键代码
    
            // 重点分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
                if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
    
                // 判断值1:disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
                // 判断值2: !onInterceptTouchEvent(ev) = 对onInterceptTouchEvent()返回值取反
                        // a. 若在onInterceptTouchEvent()中返回false(即不拦截事件),就会让第二个值为true,从而进入到条件判断的内部
                        // b. 若在onInterceptTouchEvent()中返回true(即拦截事件),就会让第二个值为false,从而跳出了这个条件判断
                        // c. 关于onInterceptTouchEvent() ->>分析1
    
                    ev.setAction(MotionEvent.ACTION_DOWN);  
                    final int scrolledXInt = (int) scrolledXFloat;  
                    final int scrolledYInt = (int) scrolledYFloat;  
                    final View[] children = mChildren;  
                    final int count = mChildrenCount;  
    
            // 重点分析2
                // 通过for循环,遍历了当前ViewGroup下的所有子View
                for (int i = count - 1; i >= 0; i--) {  
                    final View child = children[i];  
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                            || child.getAnimation() != null) {  
                        child.getHitRect(frame);  
    
                        // 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
                        // 若是,则进入条件判断内部
                        if (frame.contains(scrolledXInt, scrolledYInt)) {  
                            final float xc = scrolledXFloat - child.mLeft;  
                            final float yc = scrolledYFloat - child.mTop;  
                            ev.setLocation(xc, yc);  
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
    
                            // 条件判断的内部调用了该View的dispatchTouchEvent()
                            // 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面的View事件分发机制)
                            if (child.dispatchTouchEvent(ev))  { 
    
                            mMotionTarget = child;  
                            return true; 
                            // 调用子View的dispatchTouchEvent后是有返回值的
                            // 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
                            // 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                            // 即把ViewGroup的点击事件拦截掉
    
                                    }  
                                }  
                            }  
                        }  
                    }  
                }  
                boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
                        (action == MotionEvent.ACTION_CANCEL);  
                if (isUpOrCancel) {  
                    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
                }  
                final View target = mMotionTarget;  
    
            // 重点分析3
            // 若点击的是空白处(即无任何View接收事件) / 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)
            if (target == null) {  
                ev.setLocation(xf, yf);  
                if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
                    ev.setAction(MotionEvent.ACTION_CANCEL);  
                    mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                }  
                
                return super.dispatchTouchEvent(ev);
                // 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
                // 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
                // 此处需与上面区别:子View的dispatchTouchEvent()
            } 
    
            ... 
    
    }
    /**
      * 分析1:ViewGroup.onInterceptTouchEvent()
      * 作用:是否拦截事件
      * 说明:
      *     a. 返回true = 拦截,即事件停止往下传递(需手动设置,即复写onInterceptTouchEvent(),从而让其返回true)
      *     b. 返回false = 不拦截(默认)
      */
      public boolean onInterceptTouchEvent(MotionEvent ev) {  
        
        return false;
    
      } 
      // 回到调用原处
    

    2.2.2 总结

    • 结论:Android事件分发总是先传递到ViewGroup、再传递到View
    • 过程:当点击了某个控件时

    示意图

    • 核心方法总结

    示意图

    2.2.3 Demo讲解

    • 布局如下
      布局层次

    • 测试代码

    布局文件:activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/my_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:focusableInTouchMode="true"
        android:orientation="vertical">
    
        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="按钮1" />
    
        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="按钮2" />
    
    </LinearLayout>
    
    

    核心代码:MainActivity.java

    /**
      * ViewGroup布局(myLayout)中有2个子View = 2个按钮
      */
    	public class MainActivity extends AppCompatActivity {
    
        Button button1,button2;
        ViewGroup myLayout;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            button1 = (Button)findViewById(R.id.button1);
            button2 = (Button)findViewById(R.id.button2);
            myLayout = (LinearLayout)findViewById(R.id.my_layout);
    
            // 1.为ViewGroup布局设置监听事件
            myLayout.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("TAG", "点击了ViewGroup");
                }
            });
    
            // 2. 为按钮1设置监听事件
            button1.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("TAG", "点击了button1");
                }
            });
    
            // 3. 为按钮2设置监听事件
            button2.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("TAG", "点击了button2");
                }
            });
    
        }
    }
    
    • 结果测试
      示意图

    从上面的测试结果发现:

    • 点击Button时,执行Button.onClick(),但ViewGroupLayout注册的onTouch()不会执行
    • 只有点击空白区域时,才会执行ViewGroupLayoutonTouch()
    • 结论:ButtononClick()将事件消费掉了,因此事件不会再继续向下传递。

    2.3 View事件的分发机制

    从上面ViewGroup事件分发机制知道,View事件分发机制从dispatchTouchEvent()开始

    2.3.1 源码分析

    /**
      * 源码分析:View.dispatchTouchEvent()
      */
      public boolean dispatchTouchEvent(MotionEvent event) {  
    
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                    mOnTouchListener.onTouch(this, event)) {  
                return true;  
            } 
            return onTouchEvent(event);  
      }
      // 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent()
      //     1. mOnTouchListener != null
      //     2. (mViewFlags & ENABLED_MASK) == ENABLED
      //     3. mOnTouchListener.onTouch(this, event)
      // 下面对这3个条件逐个分析
    
    
    /**
      * 条件1:mOnTouchListener != null
      * 说明:mOnTouchListener变量在View.setOnTouchListener()方法里赋值
      */
      public void setOnTouchListener(OnTouchListener l) { 
    
        mOnTouchListener = l;  
        // 即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
            
    } 
    
    /**
      * 条件2:(mViewFlags & ENABLED_MASK) == ENABLED
      * 说明:
      *     a. 该条件是判断当前点击的控件是否enable
      *     b. 由于很多View默认enable,故该条件恒定为true
      */
    
    /**
      * 条件3:mOnTouchListener.onTouch(this, event)
      * 说明:即 回调控件注册Touch事件时的onTouch();需手动复写设置,具体如下(以按钮Button为例)
      */
        button.setOnTouchListener(new OnTouchListener() {  
            @Override  
            public boolean onTouch(View v, MotionEvent event) {  
         
                return false;  
            }  
        });
        // 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
        // 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()中跳出If,执行onTouchEvent(event)
    

    接下来,我们继续看:**onTouchEvent(event)**的源码分析

    1. 详情请看注释
    2. Android 5.0View.onTouchEvent()源码发生了变化(更加复杂),但原理相同;
    3. 本文为了让读者更好理解,所以采用Android 5.0前的版本
    /**
      * 源码分析:View.onTouchEvent()
      */
      public boolean onTouchEvent(MotionEvent event) {  
        final int viewFlags = mViewFlags;  
    
        if ((viewFlags & ENABLED_MASK) == DISABLED) {  
             
            return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
        }  
        if (mTouchDelegate != null) {  
            if (mTouchDelegate.onTouchEvent(event)) {  
                return true;  
            }  
        }  
    
        // 若该控件可点击,则进入switch判断中
        if (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
    
                    switch (event.getAction()) { 
    
                        // a. 若当前的事件 = 抬起View(主要分析)
                        case MotionEvent.ACTION_UP:  
                            boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
    
                                ...// 经过种种判断,此处省略
    
                                // 执行performClick() ->>分析1
                                performClick();  
                                break;  
    
                        // b. 若当前的事件 = 按下View
                        case MotionEvent.ACTION_DOWN:  
                            if (mPendingCheckForTap == null) {  
                                mPendingCheckForTap = new CheckForTap();  
                            }  
                            mPrivateFlags |= PREPRESSED;  
                            mHasPerformedLongPress = false;  
                            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                            break;  
    
                        // c. 若当前的事件 = 结束事件(非人为原因)
                        case MotionEvent.ACTION_CANCEL:  
                            mPrivateFlags &= ~PRESSED;  
                            refreshDrawableState();  
                            removeTapCallback();  
                            break;
    
                        // d. 若当前的事件 = 滑动View
                        case MotionEvent.ACTION_MOVE:  
                            final int x = (int) event.getX();  
                            final int y = (int) event.getY();  
            
                            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;  
                    }  
                    // 若该控件可点击,就一定返回true
                    return true;  
                }  
                 // 若该控件不可点击,就一定返回false
                return false;  
            }
    
    /**
      * 分析1:performClick()
      */  
        public boolean performClick() {  
    
            if (mOnClickListener != null) {  
                playSoundEffect(SoundEffectConstants.CLICK);  
                mOnClickListener.onClick(this);  
                return true;  
                // 只要我们通过setOnClickListener()为控件View注册1个点击事件
                // 那么就会给mOnClickListener变量赋值(即不为空)
                // 则会往下回调onClick() & performClick()返回true
            }  
            return false;  
        }  
    

    2.3.2 总结

    • 每当控件被点击时:

    示意图

    注:onTouch()的执行 先于 onClick()

    • 核心方法总结

    示意图

    2.3.3 Demo讲解

    下面我将用Demo验证上述的结论

    /**
      * 结论验证1:在回调onTouch()里返回false
      */
       // 1. 通过OnTouchListener()复写onTouch(),从而手动设置返回false
       button.setOnTouchListener(new View.OnTouchListener() {
    
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    System.out.println("执行了onTouch(), 动作是:" + event.getAction());
               
                    return false;
                }
            });
    
        // 2. 通过 OnClickListener()为控件设置点击事件,为mOnClickListener变量赋值(即不为空),从而往下回调onClick()
        button.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    System.out.println("执行了onClick()");
                }
    
            });
    
    /**
      * 结论验证2:在回调onTouch()里返回true
      */
       // 1. 通过OnTouchListener()复写onTouch(),从而手动设置返回true
       button.setOnTouchListener(new View.OnTouchListener() {
    
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    System.out.println("执行了onTouch(), 动作是:" + event.getAction());
               
                    return true;
                }
            });
    
        // 2. 通过 OnClickListener()为控件设置点击事件,为mOnClickListener变量赋值(即不为空)
        // 但由于dispatchTouchEvent()返回true,即事件不再向下传递,故不调用onClick())
        button.setOnClickListener(new View.OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    System.out.println("执行了onClick()");
                }
                
            });
    
    
    • 测试结果
      示意图

    2.4 总结

    示意图

    若您已经看到此处,那么恭喜你,你已经能非常熟悉掌握Android的事件分发机制了

    即:ActivityViewGroupView 的事件分发机制


    3. 工作流程 总结

    • 在本节中,我将结合源码,梳理出1个事件分发的工作流程总结,具体如下:

    示意图

    左侧虚线:具备相关性 & 逐层返回

    • 以角色为核心的图解说明

    示意图

    • 以方法为核心的图解说明

    示意图


    4. 核心方法总结

    • 已知事件分发过程的核心方法为:dispatchTouchEvent()onInterceptTouchEvent()onTouchEvent()
      示意图

    • 下面,我将结合总结的工作流程,再次详细讲解该3个方法

    4.1 dispatchTouchEvent()

    • 简介

    示意图

    示意图

    • 返回情况说明

    情况1:默认
    示意图

    示意图

    情况2:返回true
    示意图

    示意图

    情况3:返回false
    示意图

    示意图


    4.2 onInterceptTouchEvent()

    • 简介

    示意图

    注:ActivityView都无该方法

    示意图

    • 返回情况说明

    情况1:true

    示意图

    示意图

    情况2:false(默认)

    示意图

    示意图


    4.3 onTouchEvent()

    • 简介

    示意图

    示意图

    • 返回情况说明

    情况1:返回true

    示意图

    [外链图片转存失败(img-XbbYoeF5-1562040906999)(https://user-gold-cdn.xitu.io/2019/6/11/16b467e6cda884fb?w=1140&h=1296&f=png&s=75290)]

    情况2:返回false(default)
    示意图

    示意图

    4.4 三者关系

    下面,我用一段伪代码来阐述上述3个方法的关系 & 事件传递规则

    /**
      * 点击事件产生后
      */ 
      // 步骤1:调用dispatchTouchEvent()
      public boolean dispatchTouchEvent(MotionEvent ev) {
    
        boolean consume = false; //代表 是否会消费事件
        
        // 步骤2:判断是否拦截事件
        if (onInterceptTouchEvent(ev)) {
          // a. 若拦截,则将该事件交给当前View进行处理
          // 即调用onTouchEvent ()方法去处理点击事件
            consume = onTouchEvent (ev) ;
    
        } else {
    
          // b. 若不拦截,则将该事件传递到下层
          // 即 下层元素的dispatchTouchEvent()就会被调用,重复上述过程
          // 直到点击事件被最终处理为止
          consume = child.dispatchTouchEvent (ev) ;
        }
    
        // 步骤3:最终返回通知 该事件是否被消费(接收 & 处理)
        return consume;
    
       }
    

    5. 常见的事件分发场景

    下面,我将通过实例说明常见的事件传递情况 & 流程

    5.1 背景描述

    • 讨论的布局如下:

    示意图

    • 情景
      1. 用户先触摸到屏幕上View C上的某个点(图中黄区)

      Action_DOWN事件在此处产生

      1. 用户移动手指
      2. 最后离开屏幕

    5.2 一般的事件传递情况

    一般的事件传递场景有:

    • 默认情况
    • 处理事件
    • 拦截DOWN事件
    • 拦截后续事件(MOVEUP

    场景1:默认

    • 即不对控件里的方法(dispatchTouchEvent()onTouchEvent()onInterceptTouchEvent())进行重写 或 更改返回值
    • 那么调用的是这3个方法的默认实现:调用下层的方法 & 逐层返回
    • 事件传递情况:(呈U型)
      1. 从上往下调用dispatchTouchEvent()

      Activity A ->> ViewGroup B ->> View C

      1. 从下往上调用onTouchEvent()

      View C ->> ViewGroup B ->> Activity A

    [外链图片转存失败(img-f2SDJbnD-1562040907003)(https://user-gold-cdn.xitu.io/2019/6/11/16b467e6f22ad97a?w=1140&h=1296&f=png&s=75697)]

    注:虽然ViewGroup BonInterceptTouchEvent()对DOWN事件返回了false,但后续的事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent()
    这一点与onTouchEvent()的行为是不一样的:不再传递 & 接收该事件列的其他事件

    场景2:处理事件

    View C希望处理该点击事件,即:设置View C为可点击的(Clickable) 或 复写其onTouchEvent()返回true

    最常见的:设置Button按钮来响应点击事件

    事件传递情况:(如下图)

    • DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理该事件
    • 因为View C正在处理该事件,那么DOWN事件将不再往上传递给ViewGroup B 和 Activity AonTouchEvent()
    • 该事件列的其他事件(Move、Up)也将传递给View ConTouchEvent()

    示意图

    会逐层往dispatchTouchEvent() 返回,最终事件分发结束

    场景3:拦截DOWN事件

    假设ViewGroup B希望处理该点击事件,即ViewGroup B复写了onInterceptTouchEvent()返回trueonTouchEvent()返回true
    事件传递情况:(如下图)

    • DOWN事件被传递给ViewGroup BonInterceptTouchEvent(),该方法返回true,表示拦截该事件,即自己处理该事件(事件不再往下传递)

    • 调用自身的onTouchEvent()处理事件(DOWN事件将不再往上传递给Activity AonTouchEvent()

    • 该事件列的其他事件(Move、Up)将直接传递给ViewGroup BonTouchEvent()

    注:

    1. 该事件列的其他事件(Move、Up)将不会再传递给ViewGroup BonInterceptTouchEvent();因:该方法一旦返回一次true,就再也不会被调用
    2. 逐层往dispatchTouchEvent() 返回,最终事件分发结束

    示意图

    场景4:拦截DOWN的后续事件

    结论

    • ViewGroup 拦截了一个半路的事件(如MOVE),该事件将会被系统变成一个CANCEL事件 & 传递给之前处理该事件的子View
    • 该事件不会再传递给ViewGrouponTouchEvent()
    • 只有再到来的事件才会传递到ViewGrouponTouchEvent()

    场景描述
    ViewGroup B 无拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件

    DOWN事件传递到View ConTouchEvent(),返回了true

    实例讲解

    • 在后续到来的MOVE事件,ViewGroup BonInterceptTouchEvent()返回true拦截该MOVE事件,但该事件并没有传递给ViewGroup B ;这个MOVE事件将会被系统变成一个CANCEL事件传递给View ConTouchEvent()
    • 后续又来了一个MOVE事件,该MOVE事件才会直接传递给ViewGroup BonTouchEvent()

    后续事件将直接传递给ViewGroup BonTouchEvent()处理,而不会再传递给ViewGroup BonInterceptTouchEvent(),因该方法一旦返回一次true,就再也不会被调用了。

    • View C再也不会收到该事件列产生的后续事件

    [外链图片转存失败(img-nW27KLdP-1562040907005)(https://user-gold-cdn.xitu.io/2019/6/11/16b467e71a4547a1?w=1240&h=1369&f=png&s=154789)]

    至此,关于Android常见的事件传递情况 & 流程已经讲解完毕。


    6. 额外知识

    6.1 Touch事件的后续事件(MOVE、UP)层级传递

    • 若给控件注册了Touch事件,每次点击都会触发一系列action事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP等)
    • dispatchTouchEvent()事件分发时,只有前一个事件(如ACTION_DOWN)返回true,才会收到后一个事件(ACTION_MOVE和ACTION_UP)

    即如果在执行ACTION_DOWN时返回false,后面一系列的ACTION_MOVE、ACTION_UP事件都不会执行

    从上面对事件分发机制分析知:

    • dispatchTouchEvent()、 onTouchEvent() 消费事件、终结事件传递(返回true)
    • 而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,对后续的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用

    请记住:接收了ACTION_DOWN事件的函数不一定能收到后续事件(ACTION_MOVE、ACTION_UP)

    这里给出ACTION_MOVE和ACTION_UP事件的传递结论

    • 结论1
      若对象(Activity、ViewGroup、View)的dispatchTouchEvent()分发事件后消费了事件(返回true),那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP

    黑线:ACTION_DOWN事件传递方向
    红线:ACTION_MOVE 、 ACTION_UP事件传递方向

    流程讲解

    • 结论2
      若对象(Activity、ViewGroup、View)的onTouchEvent()处理了事件(返回true),那么ACTION_MOVE、ACTION_UP的事件从上往下传到该View后就不再往下传递,而是直接传给自己的onTouchEvent()& 结束本次事件传递过程。

    黑线:ACTION_DOWN事件传递方向
    红线:ACTION_MOVE、ACTION_UP事件传递方向

    流程讲解

    6.2 onTouch()和onTouchEvent()的区别

    • 该2个方法都是在View.dispatchTouchEvent()中调用
    • onTouch()优先于onTouchEvent执行;若手动复写在onTouch()中返回true(即 将事件消费掉),将不会再执行onTouchEvent()

    注:若1个控件不可点击(即非enable),那么给它注册onTouch事件将永远得不到执行,具体原因看如下代码

    // &&为短路与,即如果前面条件为false,将不再往下执行
    //  故:onTouch()能够得到执行需2个前提条件:
         // 1. mOnTouchListener的值不能为空
         // 2. 当前点击的控件必须是enable的
    mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)
    
    // 对于该类控件,若需监听它的touch事件,就必须通过在该控件中重写onTouchEvent()来实现
    

    7. 总结


    请帮顶 / 评论点赞!因为你们的赞同/鼓励是我写作的最大动力!


    欢迎关注carson_ho的微信公众号

    • 名称:carson带你解析Android
    • 作用:通过碎片化时间(上下班、饭前饭后等)带你高效、系统地学习Android知识

    具体请看文章:我想给你们介绍一个与众不同的Android微信公众号(福利回赠)

    • 扫一扫关注即可

    示意图

    示意图

    展开全文
  • vue监听input输入事件-oninput

    万次阅读 热门讨论 2017-05-25 19:14:06
    .vue文件其实是一个组件,关于它的说明我之前也写过一篇文章,地址: .vue文件,今天这篇文章要讲的是.vue文件中监听input的输入值变化事件。需求是这页面中,改变input的值,就调用一个事件,第一想到的是oninput。...

    .vue文件其实是一个组件,关于它的说明我之前也写过一篇文章,地址: .vue文件,今天这篇文章要讲的是.vue文件中监听input的输入值变化事件。需求是这页面中,改变input的值,就调用一个事件,第一想到的是oninput。

    oninput 事件在用户输入时触发,菜鸟教程中的用法是:

    这里写图片描述

    但是在.vue中这样写是没有作用的:

    这里写图片描述

    <input type="text" id="cardsNum2"  value="1" @oninput ="inputFunc">
    

    这样写也没有作用:

    这里写图片描述

    <input type="text" id="cardsNum2"  value="1" v-on:oninput ="inputFunc">
    

    最后,这样写才起作用:

    这里写图片描述

    <input type="text" id="cardsNum2"  value="1" v-on:input ="inputFunc">
    

    希望这篇文章对您有所帮助!

    个人微信公众号:侦探小禾子,闲聊育儿生活星座塔罗牌,野生法考通过者免费法律咨询,欢迎关注!
    这里写图片描述

    展开全文
  • Vue教程03(事件修饰符)

    万次阅读 多人点赞 2019-07-06 18:18:05
      本文我们来详细介绍下vue中的事件修饰符 Vue事件修饰符 事件修饰符概览 修饰符 说明 .stop 阻止冒泡 .prevent 阻止默认事件 .capture 添加事件侦听器时使用事件捕获模式 .self 只当事件在该元素...

      本文我们来详细介绍下vue中的事件修饰符

    Vue事件修饰符

    事件修饰符概览

    修饰符 说明
    .stop 阻止冒泡
    .prevent 阻止默认事件
    .capture 添加事件侦听器时使用事件捕获模式
    .self 只当事件在该元素本身(比如不是子元素)触发时触发回调
    .once 事件只触发一次

    事件修饰符具体介绍

    .stop

      .stop用来防止冒泡,我们先来看看冒泡的场景

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
        <script src="./lib/vue-2.4.0.js"></script>
        <style>
                .inner {
                  height: 150px;
                  background-color: gold;
                }
            
                .outer {
                  padding: 40px;
                  background-color: red;
                }
              </style>
    </head>
    <body>
        
        <div id="app">
                <div class="inner" @click="div1Handler">
                        <input type="button" value="点击" @click="btnHandler">
                </div>
        </div>
        <script>
            var vm = new Vue({
                el:"#app",
                data: {
    
                },
                methods: {
                    div1Handler() {
                        console.log('这是触发了 inner div 的点击事件')
                    },
                    btnHandler() {
                        console.log('这是触发了 btn 按钮 的点击事件')
                    }
                }
            })
        </script>
    </body>
    </html>
    

    页面操作效果

    在这里插入图片描述

    我们看到不光点击按钮的点击事件触发了,而且父容器div的点击事件也触发了,这时我们就可以使用.stop来阻止这个冒泡了,如下

    在这里插入图片描述

    在访问测试

    在这里插入图片描述

    通过输出可以看到点击事件没有往上冒泡了!

    .prevent

      阻止默认行为,我们可以通过a标签来演示,先看未阻止的情况

    在这里插入图片描述

    看下演示效果
    在这里插入图片描述

    我们可以看到先触发了我们的弹出框,然后页面跳转了,这时我们可以阻止默认行为

    在这里插入图片描述

    再看效果

    在这里插入图片描述

    从效果中可以看出默认的跳转事件被阻止了!

    .capture

      实现捕获触发事件的机制,即是给元素添加一个监听器,当元素发生冒泡时,先触发带有该修饰符的元素。若有多个该修饰符,则由外而内触发。
    就是谁有该事件修饰符,就先触发谁。 先看没有该修饰符的操作

    在这里插入图片描述

    在这里插入图片描述

    输出我们可以看到先触发的 按钮的点击事件,然后触发的div的点击事件,现在我们绑定 .capture

    在这里插入图片描述

    在这里插入图片描述

    通过输出可以看到是先触发的 绑定的有".capture"的div,然后触发的btn按钮。

    .self

      实现只有点击当前元素时候,才会触发事件处理函数

    在这里插入图片描述

    效果演示:

    在这里插入图片描述

    .once

      只触发一次事件处理函数

    在这里插入图片描述

    在这里插入图片描述

    通过输出效果可以看出阻止默认行为只有效了一次!

    .stop 和 .self 的区别

    先看.stop的行为:

    在这里插入图片描述

    点击btn按钮,阻止冒泡,我们看效果

    在这里插入图片描述

    通过演示可以看到点击按钮,两个div的事件都没有触发。

    再看.self

    在这里插入图片描述

    在这里插入图片描述

    通过演示可以看出self只会阻止自己身上冒泡行为的触发,并不会真正阻止 冒泡的行为。

    展开全文
  • Js-三大事件(鼠标事件、键盘事件、html事件

    万次阅读 多人点赞 2017-08-23 19:01:44
    鼠标事件、键盘事件、html事件
  • 【JavaScript】按钮绑定点击事件-onCliek事件 <button type="submit" id="btn">btn</button> 1. 第一种: $("#btn").click(function(event){} 2. 第二种: document.getElementById('#foo')....
  • nodejs事件事件循环简介

    千次阅读 2020-12-06 21:37:03
    我们在javascript中监听这些事件,从而触发相应的处理。 同样的nodejs中也有事件,并且还有一个专门的events模块来进行专门的处理。 同时事件事件循环也是nodejs构建异步IO的非常重要的概念。 今天我们来详细...
  • JS冒泡、捕获、默认事件那些事W3C规范三个事件阶段阻止冒泡、取消默认行为)(默认事件)防止冒泡和捕获取消默认事件return false兼容的使用方法(代码)事件对象event事件对象event的一些兼容性写法 W3C规范三个事件阶段...
  • 事件捕获指的是从document到触发事件的那个节点,即自上而下的去触发事件事件冒泡刚好相反,是从触发事件的那个节点一直到document,是自下而上的去触发事件事件委托:利用了事件冒泡的原理,在下方通过实例...
  • JavaScript事件代理(事件委托)

    万次阅读 多人点赞 2019-01-11 15:03:44
    顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。 举个通俗的例子 比如一个宿舍的同学同时...
  • DOM事件类型、event事件对象,最详解析。

    万次阅读 多人点赞 2020-05-11 11:02:28
    目录事件对象(一)DOM中的事件对象⑴ 示例⑵ 所有事件都有的event对象的属性和方法① currentTarget | this | targer 的区别 事件对象 在触发DOM上的某个事件时,会产生一个事件对象event,这个事件对象包含着所有...
  • JavaScript基础之事件冒泡、事件捕获、事件委托、事件绑定可以说是JavaScript基础中最为重要的知识点。下面就来介绍一下相关知识。 我们知道,在dom模型中,html是多层次的,当一个html元素上产生事件时,该事件会...
  • jquery双击事件

    万次阅读 2018-03-08 14:47:32
    在开发过程中经常会遇到使用双击事件或者避免短时间内连续触发单击事件的逻辑。这里介绍一下对类似问题的处理方法。 一、jquery的dblclick事件 一个DOM元素,如:div,既绑定了 click 事件,又绑定了 dblclick ...
  • js实现单击事件和双击事件

    万次阅读 2019-08-06 09:28:58
    一下代码可以解决单击事件和双击事件冲突的问题 //单击事件 var timeoutID = null; $("#test).on('click',function(e){ clearTimeout(timeoutID); timeoutID = window.setTimeout(function (){ //写单击事件...
  • android事件拦截处理机制详解

    万次阅读 多人点赞 2014-11-13 19:43:33
    android 事件拦截处理机制详解
  • 记得在前面的文章中,我带大家一起从源码的角度分析了Android中View的事件分发机制,相信阅读过的朋友对View的事件分发已经有比较深刻的理解了。 还未阅读过的朋友,请先参考 Android事件分发机制完全解析,带你从...
  • 新用户关注微信公众平台,将产生一个订阅事件,即subscribe事件,默认代码中没有对这一事件进行相应回复处理。 在新用户关注公众平台后,可能想知道该平台提供了哪些功能,以及怎样使用该平台,通俗一点讲就是该平台...
  • JS事件事件类型[焦点事件]

    千次阅读 2017-06-28 08:05:00
    焦点事件会在页面获得或失去焦点时触发。利用这些事件并与document.hasFocus()方法及document.activeElement属性配合,可以知晓用户在页面上的行踪,有以下6信焦点事件: blur:在元素失去焦点时触发。这个事件不会...
  • 一、阻止事件冒泡方法: 在vue中,一般情况下我们是这样绑定事件方法的: <div class="parent-wrapper" @click="clickWrapper">点这里</div> @click=“事件名”,这是常规方法,但是如果需要阻止...
  • bpmn事件

    千次阅读 2020-07-06 13:27:10
    本文章主要讲解bpmn事件,系列文章包含工作流、工作流系统、工作流引擎的相关讲解,涉及的到Camunda BPM、BPMN规范、activit的基础性知识,对于流程自动化、业务流程等进行了深入研究探讨。
  • radio选择事件 onchange事件 onclick事件

    万次阅读 2015-12-10 15:10:59
    单选框按钮(radio)选择事件怎么设置呢? 既可以在radio标签里设置onclick事件实现,也可以设置它的onchange事件实现,效果一样,代码如下: 那么当点击单选按钮的时候就会触发test函数。
  • uni-app 事件 事件映射表,左侧为 WEB 事件,右侧为 ``uni-app`` 对应事件 { click: 'tap', touchstart: 'touchstart', touchmove: 'touchmove', touchcancel: 'touchcancel', touchend: 'touchend', tap: ...
  • change事件与select事件

    万次阅读 2019-07-26 10:25:57
    change事件是jQuery表单事件。在<input>元素中,<textarea>和<select>元素的值都是可以发生改变的,开发者可以通过change事件去监听这些改变的动作。接下来我先说它的元素,它的元素分为三种:...
  • Echarts设置点击事件

    万次阅读 2018-09-15 11:21:25
    通常在使用Echarts画图之后会碰到一个需求,通过点击生成后图形具体某一项来传递相应的参数然后进行一个页面的跳转,当我遇到这个需求第一就想到了用on绑定点击事件的方法,然后就在代码上进行尝试,果然可以实现这...
  • iOS事件传递机制

    万次阅读 2020-04-03 15:42:41
     事件的产生和传递(事件如何从父控件传递到子控件并寻找到最合适的view、寻找最合适的view的底层实现、拦截事件的处理)->找到最合适的view后事件的处理(touches方法的重写,也就是事件的响应) 重点和难点是...
  • react事件传参

    万次阅读 2019-05-11 22:36:03
    React触发事件的时候如何传参? 第一种方法使用箭头函数将其包裹起来: //在上面自定义的方法 test = (name) => { console.log(name) } //在render里面的代码 <button onClick={() => this.test('...
  • js监听事件的绑定事件与移除事件

    千次阅读 2019-03-06 11:01:26
    监听事件的绑定和移除主要是addEventListener和removeEventListener的应用 addEventListener语法 element.addEventListener(type,handler,false/true) type:事件类型 handler:事件执行触发的函数 false/true:...
  • 代码触发,手动触发touchstart事件,touch事件,click事件,自定义事件
  • js事件节流、防抖、事件委托

    千次阅读 2020-03-23 00:03:56
    js事件节流、事件委托事件节流事件防抖事件委托(事件代理)事件冒泡事件委托的优点代码兼容性写法 事件节流 事件节流和防抖是为了解决开发过程中遇到性能问题,常见于onscroll、onresize,频繁点击button等 原理 设置...
  • 早期的事件,是作为分担服务器运算负载的一种手段,实文档或者浏览器窗口中发生的一些特定的交互瞬间,如点击按钮,拖放文件等。我们可以使用侦听器来预定事件,当事件发布时候就可作出相应的响应,这种模式称为观察...

空空如也

1 2 3 4 5 ... 20
收藏数 680,817
精华内容 272,326
关键字:

事件