Android 中多次设置 OnClickListener 只执行一次吗?
Posted 吴豪杰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 中多次设置 OnClickListener 只执行一次吗?相关的知识,希望对你有一定的参考价值。
问题
对于 android 初学者,可能对这个问题会比较疑惑: 对于一个 View
,比如 Button
,如果为其设置多次点击监听 OnClickListener
回调方法,同时还在布局中设置了 onClick
属性,并且也实现了点击回调方法,那么问题来了,哪些回调方法会执行呢?又是以怎样的顺序执行呢?请跟随脚步和我一探究竟…
实验现象
我们先来做个实验,观察一下实验现象。
首先在布局文件中声明一个 Button
,并为其设置好点击属性:
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="click()"
android:text="BUTTON"/>
嗯,对,然后再在 Activity
中实现方法:
void click(View v)
Log.i(TAG, "click: in layout file");
这样第一组测试样例放置好了,第二组和第三组很容易,先后在 onCreate()
中设置两次监听,都记得打上 Log
日志:
mButton.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
Log.i(TAG, "click: in onCreate() first");
);
mButton.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
Log.i(TAG, "click: in onCreate() second");
);
然后,见证奇迹了,运行观察实验结果:
I/MainActivity: click: in onCreate() second
你没有看错,只有一条结果,而且是第二次的结果,说明优先级 java
设置监听的优先级大于布局文件,而且最后一次设置的监听会覆盖前一次设置的监听。
结论
点击监听优先级: 之后设置的优先级大于之前设置的优先级,代码中设置的优先级大于布局中设置的优先级
寻找答案
结果很出乎意料,也许猜的都会执行,也许你猜的布局先执行,却没想到只有最后一次设置的执行,这其中的神秘之处究竟何在呢?让我们 Read The Fuck Source Code.
setOnClickListener
先从 setOnClickListener()
入手:
public void setOnClickListener(@Nullable OnClickListener l)
if (!isClickable())
setClickable(true);
getListenerInfo().mOnClickListener = l;
先是将该 View
设置为可点击状态,所以即便一个 View
是不可点击的,你为其设置了监听,也会将其恢复成可点击状态。再是将 getListenerInfo()
返回的对象中的成员 mOnClickListener
直接复制为参数 l
,还不明白?再看:
ListenerInfo getListenerInfo()
if (mListenerInfo != null)
return mListenerInfo;
// 如果为空,重新创建用于保存所有监听器的容器类
mListenerInfo = new ListenerInfo();
return mListenerInfo;
// 容器类定义
static class ListenerInfo
...
// 是数组 事件发生回调所有监听器
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
// 不是数组 直接对其进行赋值
public OnClickListener mOnClickListener;
protected OnLongClickListener mOnLongClickListener;
...
哈,是不是恍然大悟,所以每一次设置监听都是对这个 mOnClickListener
成员进行替换赋值,所以说最后一次设置监听才是有效的。
布局文件
Java
中的监听确实明白了,那么布局文件中又是怎么回事呢?
通过 setOnClickListener()
可以对 mOnClickListener
进行修改,那么我们找一下这个函数在哪里有调用,果然找到了,
在 View
的构造函数中,有这样两行代码:
// 获取布局 android:onClick="" 属性值
final String handlerName = a.getString(attr);
if (handlerName != null)
setOnClickListener(new DeclaredOnClickListener(this, handlerName));
也就是说,View
默认就设置了一个叫做 DeclaredOnClickListener
的监听器:
private static class DeclaredOnClickListener implements OnClickListener
...
@Override
public void onClick(@NonNull View v)
if (mMethod == null)
mMethod = resolveMethod(mHostView.getContext(), mMethodName);
// 调用方法,并携带参数 v
mMethod.invoke(mHostView.getContext(), v);
...
默认的实现是通过 resolveMethod()
获取到方法,并调用之:
private Method resolveMethod(@Nullable Context context, @NonNull String name)
...
if (!context.isRestricted())
// 使用参数 name 反射取得方法
return context.getClass().getMethod(mMethodName, View.class);
...
是不是很眼熟了,这不就是反射获取到方法吗?!一切都明白了吧,在 View
创建之初就设置了一个默认监听, 默认监听是调用的所在 Context
中的符合布局中定义的方法签名的方法。
结论
所以,总的来说是这样的,View
在构造之初就默认设置好了一个监听器,View
的构造也是在 onCreate()
方法执行前完成的,对于每一次设置监听都会覆盖上一次的监听,所以最后一次设置的监听才会是有效的。
以上是关于Android 中多次设置 OnClickListener 只执行一次吗?的主要内容,如果未能解决你的问题,请参考以下文章
Android 中多次设置 OnClickListener 只执行一次吗?
多次包含时在布局内设置Android RemoteViews的文本
android中,如何让布局文件中定义的一个Layout接收点击事件,并为它添加Listener。或者是类似的功能实现