android:onClick XML属性与setOnClickListener有什么区别?

449

据我所读,您可以以两种方式为按钮分配一个 onClick 处理程序。

第一种方法是使用 android: onClick XML 属性,其中只需使用具有签名 void name(View v) 的公共方法的名称;或者使用 setOnClickListener 方法,其中传递实现 OnClickListener 接口的对象。后者通常需要匿名类,而我个人不喜欢这样做(个人喜好),或者定义一个实现 OnClickListener 的内部类。

通过使用 XML 属性,您只需要定义一个方法而不是一个类,因此我想知道是否可以通过代码而不是在 XML 布局中完成相同的操作。


4
我看了你的问题,觉得你和我一样卡在同一个地方。我之前找到了一段很好的视频,对解决我的问题非常有帮助。你可以在下面的链接中找到这个视频:http://www.youtube.com/watch?v=MtmHURWKCmg&feature=youtu.be 希望这也能对你有所帮助 :) - user2166292
9
如果您想节省观看上面评论中发布的视频的时间,该视频演示了两个按钮如何在布局文件中具有相同的onClick属性方法。这归功于参数View v。您只需检查if(v == findViewById(R.id.button1))等即可。 - CodyBugstein
14
我认为最好使用 v.getId() == R.id.button1,因为您不必寻找实际控件并进行比较。您可以使用 switch 代替很多 if 语句。 - Sami Kuhmonen
使用xml android:onClick 会导致崩溃。 - user9599745
17个回答

642

不,这是不可能通过代码实现的。当你定义android:onClick="someMethod"属性时,Android会为你实现OnClickListener

这两个代码片段是相等的,只是以两种不同的方式实现。

代码实现

Button btn = (Button) findViewById(R.id.mybutton);

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        myFancyMethod(v);
    }
});

// some more code

public void myFancyMethod(View v) {
    // does something very interesting
}

以上是一个OnClickListener的代码实现。这是XML实现方式。

XML实现方式

<?xml version="1.0" encoding="utf-8"?>
<!-- layout elements -->
<Button android:id="@+id/mybutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Click me!"
    android:onClick="myFancyMethod" />
<!-- even more layout elements -->

在后台,Android 实际上做的是执行 Java 代码,在点击事件中调用你的方法。

请注意,使用上述 XML 时,Android 仅会在当前 Activity 中查找名为 myFancyMethod()onClick 方法。如果你正在使用片段(Fragment),这一点尤其重要,因为即使你使用片段添加了上述 XML,Android 也不会在用于添加 XML 的片段的 .java 文件中查找 onClick 方法。

我注意到另一个重要的事情。你提到你不喜欢匿名方法,你指的应该是你不喜欢匿名类。


4
我不是Java大师,但是我的确指的是匿名类。感谢您的回复,非常清晰。 - emitrax
129
请注意,如果您使用XML的onclick属性,必须将myFancyMethod()方法放在当前活动中。这一点非常重要,特别是当您在使用片段时,因为以编程方式设置onclick侦听器通常会使处理点击事件的方法位于片段的onCreateView()方法中....如果从XML引用,它将无法找到。 - Peter Ajtai
12
是的,这个方法必须是公开的。 - Octavian Helm
12
有趣的是,使用代码进行操作可以通过将方法设置为私有来保护方法访问,而使用 XML 方法则会导致方法被公开。 - bgse
5
XML方法中函数(在Activity中)的事实不仅在考虑片段时很重要,而且在考虑自定义视图(包含按钮)时也很重要。当您有一个自定义视图在多个活动中重复使用,但想要为所有情况使用相同的onClick方法时,XML方法不是最方便的方法。您需要将此onClick方法(具有相同的主体)放置在使用自定义视图的每个活动中。 - Bartek Lipinski
显示剩余10条评论

93
当我看到最佳答案时,我意识到我的问题是没有在fancy方法上放置参数(View v)。
public void myFancyMethod(View v) {}

当尝试从 XML 访问它时,应使用:

当尝试从xml访问时,应该使用

android:onClick="myFancyMethod"/>

这是最好的答案 - 我曾经遇到过同样的问题,即在方法中没有写(View v)... - Ola Ström

76

android:onClick 属性适用于 API 级别 4 及以上的版本,因此如果您的目标版本小于 1.6,则无法使用该属性。


34

检查您是否忘记了将方法声明为public!


1
为什么必须将其公开? - eRaisedToX
4
我认为这很明显:如果不是公共的,就不能从Android框架中调用它。 - m0skit0

32

指定android:onClick属性会导致Button实例在内部调用setOnClickListener。因此,没有任何区别。

为了更清楚地理解,让我们看看框架如何处理XML onClick属性。

当布局文件被展开时,其中指定的所有视图都将被实例化。在这种特定情况下,Button实例是使用public Button(Context context,AttributeSet attrs,int defStyle)构造函数创建的。从资源束读取XML标记中的所有属性,并将其作为AttributeSet传递给构造函数。

Button类继承自View类,这导致调用View构造函数,该函数通过setOnClickListener设置单击事件回调处理程序。

attrs.xml中定义的onClick属性,在View.java中称为R.styleable.View_onClick

以下是View.java的代码,它会通过调用setOnClickListener自行完成大部分工作。

 case R.styleable.View_onClick:
            if (context.isRestricted()) {
                throw new IllegalStateException("The android:onClick attribute cannot "
                        + "be used within a restricted context");
            }

            final String handlerName = a.getString(attr);
            if (handlerName != null) {
                setOnClickListener(new OnClickListener() {
                    private Method mHandler;

                    public void onClick(View v) {
                        if (mHandler == null) {
                            try {
                                mHandler = getContext().getClass().getMethod(handlerName,
                                        View.class);
                            } catch (NoSuchMethodException e) {
                                int id = getId();
                                String idText = id == NO_ID ? "" : " with id '"
                                        + getContext().getResources().getResourceEntryName(
                                            id) + "'";
                                throw new IllegalStateException("Could not find a method " +
                                        handlerName + "(View) in the activity "
                                        + getContext().getClass() + " for onClick handler"
                                        + " on view " + View.this.getClass() + idText, e);
                            }
                        }

                        try {
                            mHandler.invoke(getContext(), View.this);
                        } catch (IllegalAccessException e) {
                            throw new IllegalStateException("Could not execute non "
                                    + "public method of the activity", e);
                        } catch (InvocationTargetException e) {
                            throw new IllegalStateException("Could not execute "
                                    + "method of the activity", e);
                        }
                    }
                });
            }
            break;

正如您所看到的,setOnClickListener 被调用来注册回调,就像我们在代码中所做的那样。唯一的区别是它使用 Java Reflection 来调用我们 Activity 中定义的回调方法。

以下是其他答案中提到的问题的原因:

  • 回调方法应该是公共的:由于使用了 Java Class getMethod,只有具有公共访问修饰符的函数才会被搜索。否则,请准备好处理 IllegalAccessException 异常。
  • 在 Fragment 中使用 Button 与 onClick 时,回调应该在 Activity 中定义getContext().getClass().getMethod() 调用将方法搜索限制为当前上下文,即在 Fragment 的情况下为 Activity。因此,该方法在 Activity 类中而不是 Fragment 类中进行搜索。
  • 回调方法应该接受 View 参数:由于 Java Class getMethod 搜索接受 View.class 参数的方法。

1
这对我来说是缺失的一部分 - Java使用反射查找以getContext()开头的点击处理程序。对我来说,从片段到活动如何传播点击有点神秘。 - Andrew Queisser

17

这里已经有非常好的答案了,但是我想要添加一句话:

在XML中的android:onclick中,Android在幕后使用Java反射来处理它。

而且,正如这里所解释的那样,反射总是会降低性能。(特别是在Dalvik VM上)。注册onClickListener是更好的方法。


6
它会使应用程序变慢多少? :) 大约半毫秒左右,甚至不到?与实际膨胀布局相比,它就像一根羽毛和一条鲸鱼。 - Konrad Morawski

15
请注意,如果要使用onClick XML特性,则相应的方法应该有一个参数,其类型应与XML对象匹配。
例如,一个将通过其名称字符串连接到您的方法:android:onClick="MyFancyMethod"但方法声明应显示:...MyFancyMethod(View v) {... 如果您尝试将此功能添加到菜单项中,则XML文件中将具有完全相同的语法,但是您的方法将被声明为:...MyFancyMethod(MenuItem mi) {...

6

另一种设置点击监听器的方法是使用XML。只需将android:onClick属性添加到您的<Button>标签中即可。

尽可能使用xml属性“onClick”而不是匿名Java类是一个好习惯。

首先,让我们看看代码的区别:

XML属性 / onClick属性

XML部分

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button1" 
    android:onClick="showToast"/>

Java部分

public void showToast(View v) {
    //Add some logic
}

匿名Java类/ setOnClickListener

XML部分

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

Java 部分

findViewById(R.id.button1).setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //Add some logic
        }
});

以下是使用XML属性而不是匿名Java类的好处:
  • 使用匿名Java类时,我们总是需要为元素指定ID,但使用XML属性可以省略ID。
  • 使用匿名Java类时,我们必须在视图中主动搜索元素(findViewById部分),但使用XML属性时,Android会替我们完成。
  • 匿名Java类需要至少5行代码,如上所示,但使用XML属性只需要3行。
  • 使用匿名Java类时,我们必须将方法命名为“onClick”,但使用XML属性时,我们可以添加任何名称,这将极大地提高代码的可读性。
  • Google在API级别4发布期间添加了Xml“onClick”属性,这意味着它是一种更现代的语法,而现代语法几乎总是更好的。
当然,并不总是能够使用XML属性,以下是我们不选择它的原因:
  • 如果我们正在使用片段,则onClick属性只能添加到活动中,因此如果我们有一个片段,我们必须使用匿名类。
  • 如果我们想将onClick监听器移动到单独的类中(也许它非常复杂和/或我们想在应用程序的不同部分重用它),那么我们也不想使用xml属性。

请注意,使用 XML 属性调用的函数应始终为公共函数,如果声明为私有函数,则会导致异常。 - Bestin John

5

使用Java 8,您可能可以使用方法引用来实现您想要的功能。

假设这是按钮的onClick事件处理程序。

private void onMyButtonClicked(View v) {
    if (v.getId() == R.id.myButton) {
        // Do something when myButton was clicked
    }
}

然后,您可以通过以下方式在 setOnClickListener() 调用中传递一个 onMyButtonClicked 实例方法引用。

Button myButton = (Button) findViewById(R.id.myButton);
myButton.setOnClickListener(this::onMyButtonClicked);

这将使您避免自己明确定义一个匿名类。但是,我必须强调Java 8的方法引用实际上只是一种语法糖。它实际上为您创建了一个匿名类的实例(就像lambda表达式一样),因此在取消事件处理程序时需要注意类似于lambda表达式样式的事件处理程序。这篇文章解释得非常好:article link
PS. 对于那些好奇如何在Android中真正使用Java 8语言功能的人,要感谢retrolambda库的礼貌。

5
通过使用XML属性,您只需要定义一个方法而不是类,因此我想知道是否可以通过代码而不是在XML布局中执行相同的操作。 是的,您可以使您的fragment或activity实现View.OnClickListener,当您在代码中初始化新视图对象时,您只需执行mView.setOnClickListener(this)即可,这会自动设置所有代码中使用的视图对象以使用您的fragment或activity等具有的onClick(View v)方法。为了区分哪个视图调用了onClick方法,您可以在v.getId()方法上使用switch语句。 请注意,这个答案与那个说“不可能通过代码实现”的答案不同。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接