V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
wheat7
V2EX  ›  Android

通过点击事件监听 setOnClickListener 彻底理解回调-Android

  •  
  •   wheat7 · 2017-08-02 13:08:13 +08:00 · 10116 次点击
    这是一个创建于 2700 天前的主题,其中的信息可能已经有所发展或是发生改变。

    个人小站

    Github

    简书

    前言

    老司机们对于回调肯定熟悉得不能再熟悉了,但是新司机可能还是一脸懵逼的,我比较笨,当年懵逼了好久,看夏安明的这一篇博客地址,虽然下边的留言都是,写得好!懂了懂了!但是我当时看了三遍还是不懂好吗 - -,现在我站在我的角度,用我理解的方式给大家讲解回调,我这么笨都理解了,聪明的新司机们肯定也是可以的

    setOnClickListener 分析

    setOnCLickLinstener,只要写过 Android 的同学应该都见过,大家都知道是点击事件监听,但是是怎么实现的呢?对,你没有猜错,就是回调
    你在 onClick(View view)中写的方法,就是一个回调方法,你仔细想一想,这个方法是在你传的参数 new View.OnClickListener()中的方法,你再仔细的想一想,为什么你传入了 new View.OnClickListener()这个参数,Android Studio 就会自动补全,让你去实现 onClick(View view)这个方法呢? 一切都在你想象之中,OnClickListener 就是一个接口,new 出一个接口,你就得实现他里边的抽象方法,在 Android 中,大多数回调都是靠接口来进行的
    并且,你实现了 onClick(View view)方法后,这个方法并没有在我们的 Activity 或者 Fragment 中调用,那为什么他生效了呢?这就是回调,你实现了他,而他却是在另一个地方调用的
    那是在什么地方调用的呢?

    我们点进 setOnClickListener 方法中一探虚实

    于是我们跳到了 View.java,原来这个方法是写在 View 中的,这时你想到,第一行代码中说了,我们的控件都继承于 View,原来如此

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

    setOnClickListener 方法就如同我们调用时的那样,传入一个 OnClickListener 对象作为参数,那我们来看一看 OnClickListener 是个啥子

        public interface OnClickListener {
            /**
             * Called when a view has been clicked.
             *
             * @param v The view that was clicked.
             */
            void onClick(View v);
        }
    

    果然不出你所料,就是个 interface

    然后注意这一行

    getListenerInfo().mOnClickListener = l;
    

    把我们传入的 OnClickListener 对象赋值给了 getListenerInfo().mOnClickListener,记住我们传入的 OnClickListener 对象就相当于携带了我们实现的 onClick(View view)方法,进到 View 里边来了

    记好了哦!

    我们来看看 getListenerInfo()方法

     ListenerInfo getListenerInfo() {
            if (mListenerInfo != null) {
                return mListenerInfo;
            }
            mListenerInfo = new ListenerInfo();
            return mListenerInfo;
        }
    
    

    getListenerInfo()返回一个 ListenerInfo,如果 mListenerInfo 已经存在,就返回,如果不存在,就 new 一个返回,也许你已经知道,或许不久后你就知道,这叫单例模式,保证只有一个 ListenerInfo 对象

    然后我们来看看 ListenerInfo 又是个啥子

    static class ListenerInfo {
            protected OnFocusChangeListener mOnFocusChangeListener;
            private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
            protected OnScrollChangeListener mOnScrollChangeListener;
            private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
            public OnClickListener mOnClickListener;
             protected OnLongClickListener mOnLongClickListener;
            protected OnContextClickListener mOnContextClickListener;
            protected OnCreateContextMenuListener mOnCreateContextMenuListener;
            private OnKeyListener mOnKeyListener;
            private OnTouchListener mOnTouchListener;
            private OnHoverListener mOnHoverListener;
            private OnGenericMotionListener mOnGenericMotionListener;
            private OnDragListener mOnDragListener;
            private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
            OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
        }
    

    原来是一个内部静态类,成员包括各种事件的监听接口,其中包括

    public OnClickListener mOnClickListener;
    
    

    诶哟,和我们传入的一样的一个 OnClickListener 接口引用,于是绕了这么一大圈(我们先不管为啥绕),我们传入的持有我们实现的 onClick(View view)方法的 OnClickListener 接口对象(还记得吗?),被赋值到了 View 中的 mListenerInfo 中的 mOnClickListener 对象,也就是,我们实现的 onCLick(View view) 方法,被 mListenerInfo.mOnClickListener 持有了

    这时,你应该想到了,我们实现的 onClick(View view)应该就是在 View 中被调用了,bingo !

        public boolean performClick() {
            final boolean result;
            final ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnClickListener != null) {
                playSoundEffect(SoundEffectConstants.CLICK);
                li.mOnClickListener.onClick(this);
                result = true;
            } else {
                result = false;
            }
    
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
            return result;
        }
    

    从字面意思理解,这个方法就是执行 Click 的方法, 他将 mListenerInfo 对象传给了一个静态的 ListenerInfo 对象, li,后边的故事大家都知道了

    li.mOnClickListener.onClick(this);
    

    这个方法执行了点击事件,并调用了我们实现的 onClick(View view) 方法

    让我们来梳理一遍流程,我们在 Activity 或者 Fragment 中调用

    View.setOnClickListener 方法,传入一个 OnCLickListener 对象,实现了 onCLick(View view)方法,然后在 View 中的某个地方,我们实现的 onCLick(View view)被调用,实现了回调,这就是回调的流程

    异步

    回调有什么用呢,就是异步,想象一下,系统一直在监听着屏幕的点击事件,在我们触摸到屏幕的时候进行响应,这是一个线程操作,因为如果这个放在主线程,那在事件被响应之前,我们的线程都是阻塞的,因为屏幕的资源被占用了,无法进行其他操作,而在子线程中,系统监听着屏幕的活动,然后在我们触摸时,调用 performClick()方法实现了点击,并且调用了 onClick(View view)方法实现了点击事件的回调,我们就可以恰恰刚好在点击时间触发的时候,进行我们想要的操作,也就是我们实现的 on CLick(View view)方法

    半伪代码实现一个回调给你看

    A.class

    //先定义一个接口
    public interface Listener {
        //回调方法
        void 回调方法();
    }
    
    //申明一个接口
    private Listener mLinstener;
    
    //一个 set 接口的方法
    public void setListener(Listener listener) {
        //把传入的 listener 赋值给 mLinstener
        mLinstener = listener
    }
    
    ... 
    //在某个地方,进行某个操作的时候
    
    private void 某个操作() {
        //回调方法执行
        mLinstener.回调方法();
    }
    
    

    另一个类 B.class

    private A a = new A();
    
    a.setListener(new Linstener() {
        public void 回调方法() {
            //我要在 A 中某个操作()执行的时候要搞的事情
            搞事情阿搞事情();
        }
    });
    
    

    然后在某个操作()调用的时候,我们的回调方法()也就被调用开始搞事情了

    你如果看不懂的话,自己写一遍,这就是 Android 中回调的一般写法,你可以在各种自定义 View 中用来了,用着用着就理解了

    为啥要绕那一圈

    那一圈保证了 View 中只有一个 mOnClickListener 对象,保证了我们一次只执行一次 onClick() 方法

    最后

    新司机们如果觉得有帮助,麻烦请给我的 github 项目点一个 star 地址点这里

    4 条回复    2017-08-02 15:15:52 +08:00
    pcatzj
        1
    pcatzj  
       2017-08-02 14:03:58 +08:00
    onClick()是异步的吗
    zysidea
        2
    zysidea  
       2017-08-02 14:14:07 +08:00
    感觉你这么大篇幅总结起来就是:面向对象的继承。。。。
    751762476
        3
    751762476  
       2017-08-02 14:17:04 +08:00
    加油
    wolegequ
        4
    wolegequ  
       2017-08-02 15:15:52 +08:00 via Android
    这么缺 star 吗
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1527 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 16:57 · PVG 00:57 · LAX 08:57 · JFK 11:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.