天天看点

Android onclick监听 接口回调机制 解析

Java的回调机制之前一直不太明白,只知道(嗯,这个地方好像是采用了回调,balabala),这两天工程不是很忙就抽了些时间从源码上学习巩固一下。

在Android上,最经典的回调应该是监听器Onclick回调了,其实还有Thread啊等等。辣么,重点来瞅瞅这个onClick()具体是怎么实现回调的涅。

1、先分析一下回调实现的理论知识

首先,来回顾下JAVA回调机制的过程

大家都喜欢用打电话来形容回调。不!  我才不会这么俗,我不用打电话来形容。我们来发短信吧。

小鸟:大鸟,有个问题要问你,为什么人们都叫你大鸟呢?

大鸟:Let me see see,我也不晓得,待我分析分析再回你短信

...

...(1天后,期间小鸟自己去泡其他鸟,就叫异步。煞笔的像暗恋一样等着大鸟回短信,就叫同步吧)

...

大鸟:想了很久这个问题我知道答案了,因为我的名字叫大鸟

小鸟:=。= 噢

以上呢就是回调与暗恋的过程。不知道我有没有表达清楚。

听说不爆照都没人看,辣么我补个图

Android onclick监听 接口回调机制 解析

在别处看到对回调的定义值得借鉴理解:回调函数就是预留给系统调用的函数,而且我们往往知道该函数被调用的时机

我们从代码上来看下实现,ClassA是小鸟,ClassB是大鸟,至于这个interface嘛,我觉得可以理解为两只鸟的约定(通过短信来告知答案)

a接口是作为回调的纽带

public interface CompomentCallbacks {

    int TEST_CALLBACK = 1;

    void method(String answer);
}
           
public interface CompomentCallbacks {
    int TEST_CALLBACK = 1;
    void method(String answer);
}
           

b、ClassA实现这个接口,并在这个接口定义的方法里头写上对于答案的反馈(=。= 噢)。即小鸟想知道答案,所以她得实现这个接口。

public class ClassA implements CompomentCallbacks {

    @Override
    public void method(String answer) {
        System.out("I am called back by ClassB, the answer is " + answer);
    }
}
           

c、ClassB需要写个方法来接收这个接口,也就是接收小鸟发给自己的消息,并且通过发过来的接口去回调ClassA中的接口方法,以便去通知小鸟自己知道答案了。即大鸟想和小鸟通信,告诉小鸟自己的答案,必须留个渠道(也就是留个电话号码)给她问。

public class ClassB {

    static void iThinkThink(final CompomentCallbacks callbacks){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100000000000000000);//花了一天的时间
                    callbacks.method("Answer:My name is 大鸟");//不管花了多少天,通过小鸟给的这个接口短信,大鸟都可以告诉她答案了
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
           

d、亲测一下

小鸟想知道大鸟为什么叫大鸟,这个问题可以通过自己来亲自问,也可以托朋友问,答案都会通过回调来反馈给小鸟。

我们原来的设定是自己问:

public void callbacktest(){
        ClassB b = new ClassB();
        b.iThinkThink(ClassA.this);//这个调用如果发生在ClassA里面就是小鸟自己问的,如果发生在别的类里面就是托别人问的
}
           

当然,这个method方法的参数不是CompomentCallbacks接口吗,为什么可以传ClassA进去呢?这个涉及一个转型的问题,具体自行度娘。反正我的粗暴理解是把接口看成一个抽象类,你参数规定是爹CompomentCallbacks,那我传个干儿子(注意不是亲儿子,继承的才是亲儿子)ClassA进去也是可以的。

可以移步 上一篇 

2、辣么,步入正题看看我们是如何实现onclick的

(a)采用匿名内部类实现

这种方式传参是接口。new的是一个接口。

btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
<span>		</span>//BALABALA
            }
        });
           

或者:

(b)类实现OnClickListener接口

该接口呢是View的一个内部接口,只有一个实现方法。这种方式传参是实现该接口的类

//源码
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    /**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }
}
           

我们代码里一般这么用

/**
 * Created by kongtianhao on 2016/3/7.这个类相当于ClassA小鸟,想知道答案,所以和大鸟约定用短信onClick()来回答
 */
public class TestA implements View.OnClickListener {
   
    button.setOnClickListener(this);
    /**
     * Called when a view has been clicked.
     *
     * @param v The view that was clicked.
     */
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.test1:
                //BALBALA
                break;
            case
            ...
        }
    }
}
           

这两种方式是一样一样的,只是前一种采用匿名内部类实现,后一种更适合监听多个view。为方便理解,可以第二种方式作为基础来理解。这个其实没多大关系啦。。。

接下来追踪一下:

setOnClickListener方法相当于ClassA把自己当参数传给ClassB,以便它调用自己

/**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;//一个view组件含多个Listener监听器,这个只赋值click,其他的还有OnFocusChangeListener、OnLongClickListener等

    }
           

从系统的触摸事件触发调用onclick方法

额  本来想只贴重点出来的,发现特么的这个onTouchEvent对于整个事件流程很关键,就全贴了

/**
     * Implement this method to handle touch screen motion events.
     * <p>
     * If this method is used to detect click actions, it is recommended that
     * the actions be performed by implementing and calling
     * {@link #performClick()}. This will ensure consistent system behavior,
     * including:
     * <ul>
     * <li>obeying click sound preferences
     * <li>dispatching OnClickListener calls
     * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
     * accessibility features are enabled
     * </ul>
     *
     * @param event The motion event.
     * @return True if the event was handled, false otherwise.
     */
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // 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 & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_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 (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        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) {
				//这里就是重点了,触摸事件流程走到这里如果这个组件注册了listener,就会回调该组件的onClick方法
                                // 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) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }

            return true;
        }

        return false;
    }
           

辣么我们继续追踪performClick()↓

/**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    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;
    }
           

源码的注释也写得很清楚了,如果view定义了监听器(也就是setOnClickListener(this)这个步骤),和点击事件有关联的action都会触发这个操作,也就是说会触发回调之前传进来的实现了OnClickListener接口的类对应的onClick方法,时序就会又跑到我们自己的代码中去了。

以上为个人总结,如有偏颇谬误敬请你  不要说!

没有啦~  这只是内心独白  要是有意见大家可以讨论啦

另外补几个干货文章:

http://blog.csdn.net/xiaanming/article/details/8703708/

http://blog.csdn.net/bjyfb/article/details/10462555

http://www.2cto.com/kf/201412/365788.html

http://blog.csdn.net/lindir/article/details/7819720

转载请注明出处:http://blog.csdn.net/kong92917