1. 程式人生 > >通過點選事件監聽 setOnClickListener 徹底理解回撥-Android

通過點選事件監聽 setOnClickListener 徹底理解回撥-Android

前言

老司機們對於回撥肯定熟悉得不能再熟悉了,但是新司機可能還是一臉懵逼的,我比較笨,當年懵逼了好久,看夏安明的這一篇部落格地址,雖然下邊的留言都是,寫得好!懂了懂了!但是我當時看了三遍還是不懂好嗎 - -,現在我站在我的角度,用我理解的方式給大家講解回撥,我這麼笨都理解了,聰明的新司機們肯定也是可以的

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() 方法