android:onClick XML属性与setOnClickListener有何不同?

How exactly does the android:onClick XML attribute differ from setOnClickListener?

我已经读过了,您可以通过两种方式将onClick处理程序分配给按钮。

使用android:onClickxml属性,其中只使用带有signaturevoid name(View v)的公共方法的名称,或使用setOnClickListener方法,其中传递实现OnClickListener接口的对象。后者通常需要一个匿名类,我个人不喜欢(个人品味),或者定义一个实现OnClickListener的内部类。

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


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

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

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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实现

1
2
3
4
5
6
7
8
<?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将只在当前活动中查找onClick方法myFancyMethod()。如果您使用片段,这一点很重要,因为即使您使用片段添加上面的XML,Android也不会在用于添加XML的片段的.java文件中查找onClick方法。

我注意到的另一件重要事情。你说过你不喜欢匿名的方法。你想说你不喜欢匿名课程。


当我看到最上面的答案时,我意识到我的问题不是把参数(视图V)放在花式方法上:

1
public void myFancyMethod(View v) {}

当试图从XML访问它时,应该使用

1
android:onClick="myFancyMethod"/>

希望能帮助别人。


android:onClick用于API 4级以后的版本,因此如果您的目标值小于1.6,则不能使用它。


检查是否忘记将方法公开!


指定android:onClick属性会导致Button实例在内部调用setOnClickListener。所以没有什么区别。

为了有清晰的理解,让我们看看这个框架是如何处理XML onClick属性的。

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

Button类继承自View类,导致调用View构造函数,该类负责通过setOnClickListener设置click回调处理程序。

attrs.xml中定义的onclick属性在view.java中称为R.styleable.View_onClick

这里是View.java的代码,它通过自己调用setOnClickListener来完成大部分工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 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来调用在活动中定义的回调方法。

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

  • 回调方法应该是公共的:因为使用了Java Class getMethod,所以只搜索具有公共访问说明符的函数。否则准备好处理IllegalAccessException异常。
  • 当使用带有onclick-in片段的按钮时,回调应该在activity中定义:getContext().getClass().getMethod()调用将方法搜索限制在当前上下文中,即fragment中的activity。因此,方法是在活动类而不是片段类中搜索的。
  • 回调方法应该接受视图参数:因为Java Class getMethod搜索接受View.class作为参数的方法。


注意,如果要使用onclick XML特性,对应的方法应该有一个参数,其类型应该与XML对象匹配。

例如,按钮将通过其名称字符串:android:onClick="MyFancyMethod"链接到方法,但方法声明应显示:...MyFancyMethod(View v) {...

如果您试图将此功能添加到菜单项中,它在XML文件中将具有完全相同的语法,但您的方法将声明为:...MyFancyMethod(MenuItem mi) {...


这里有很好的答案,但我想增加一行:

在EDCOX1×0的XML中,Android使用场景背后的Java反射来处理这个问题。

正如这里所解释的,反射总是减慢性能。(尤其是在达尔维克虚拟机上)。注册onClickListener是更好的方法。


另一种设置点击监听器的方法是使用XML。只需将android:onclick属性添加到标签中。

如果可能的话,在匿名Java类上使用XML属性"OnCalk"是一个很好的实践。

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

xml属性/onclick属性

XML部分

1
2
3
4
5
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button1"
    android:onClick="showToast"/>

爪哇部分

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

匿名Java类/ StutoCnLink监听器

XML部分

1
2
3
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

爪哇部分

1
2
3
4
5
6
7
findViewById(R.id.button1).setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //Add some logic
        }
});

下面是在匿名Java类上使用XML属性的好处:

  • 对于匿名Java类,我们必须为我们指定一个ID。元素,但可以省略WITHXML属性ID。
  • 使用匿名Java类,我们必须主动搜索元素在视图内部(findViewByID部分),但具有XML属性安卓为我们做的。
  • 匿名Java类需要至少5行代码,因为我们可以请参见,但对于XML属性,3行代码就足够了。
  • 使用匿名Java类,我们必须命名我们的方法"OnCalk",但是使用XML属性,我们可以添加任何需要的名称,这将极大地提高了代码的可读性。
  • XML"onclick"属性已由Google在API级别添加4发行版,这意味着它有点现代语法和现代语法几乎总是更好的。

当然,不可能总是使用XML属性,以下是我们不选择它的原因:

  • 如果我们在处理碎片。只能添加onclick属性对于一个活动,所以如果我们有一个片段,我们必须使用匿名类。
  • 如果我们想将onclick侦听器移动到单独的类中(如果非常复杂和/或我们希望在应用程序的不同部分),那么我们不想使用XML属性。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
   Add Button in xml and give onclick attribute name that is the name of Method.
   <!--xml --!>
   <Button
  android:id="@+id/btn_register"
  android:layout_margin="1dp"
  android:onClick="addNumber"
  android:text="Add"
  />


    Button btnAdd = (Button) findViewById(R.id.mybutton); btnAdd.setOnClickListener(new View.OnClickListener() {
   @Override
    public void onClick(View v) {
      addNumber(v);
    }
    });

  Private void addNumber(View v){
  //Logic implement
    switch (v.getId()) {
    case R.id.btnAdd :
        break;
     default:
        break;
    }}

By using the XML attribute you just need to define a method instead of
a class so I was wondering if the same can be done via code and not in
the XML layout.

是的,你可以让你的fragmentactivity实现View.OnClickListener

在代码中初始化新的视图对象时,只需执行mView.setOnClickListener(this);

这会自动将代码中的所有视图对象设置为使用您的fragmentactivity等的onClick(View v)方法。

为了区分哪个视图调用了onClick方法,可以在v.getId()方法上使用switch语句。

这个答案不同于"不,通过代码是不可能的"的答案。


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

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

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

然后,在这样的setOnClickListener()调用中传递onMyButtonClicked实例方法引用。

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

这将允许您避免自己显式定义匿名类。然而,我必须强调Java 8的方法引用实际上只是一个语法糖。它实际上为您创建了一个匿名类的实例(就像lambda表达式一样),因此在注销事件处理程序时应用了类似lambda表达式样式的事件处理程序。这篇文章解释得很好。

对于那些好奇我如何在Android中真正使用Java 8语言特征的人来说,这是RealLAMDA库的礼貌。


支持ruivo的答案,是的,您必须声明方法为"public"才能在Android的XML onclick中使用-我正在开发一个应用程序,目标是从API级别8(minsdk…)到16(targetsdk…)。

我声明我的方法是私有的,它导致了错误,只是声明它是公共工程。


How android:onclick xml attribute works in Android

只需在android:onclick属性的值中使用方法的名称。确保在方法名称之前使用公共关键字。

android onclick XML示例


注意,虽然android:onClickXML似乎是处理单击的一种方便方法,但是setOnClickListener实现除了添加onClickListener之外,还做了一些额外的工作。实际上,它使视图属性clickable为真。

虽然在大多数Android实现中这可能不是问题,但根据Phone构造函数,Button始终默认为clickable=true,但某些手机模型上的其他构造函数在非按钮视图中可能有默认的clickable=false。

所以设置XML是不够的,你必须一直考虑在非按钮上添加android:clickable="true",如果你有一个默认值为clickable=true的设备,你甚至忘记了一次将这个XML属性放在上面,你不会在运行时注意到这个问题,但当它在你的客户手中时,你会得到市场上的反馈!

此外,我们永远无法确定proguard将如何混淆和重命名XML属性和类方法,因此不100%安全,他们将永远不会有一天出现错误。

所以,如果你不想遇到麻烦,也不想去想它,最好使用setOnClickListener或类似butternive的库和注释@OnClick(R.id.button)一起使用。


假设您想添加这样的click事件main.xml

1
2
3
4
5
6
7
8
9
<Button
    android:id="@+id/btn_register"
    android:layout_margin="1dp"
    android:layout_marginLeft="3dp"
    android:layout_marginTop="10dp"
    android:layout_weight="2"
    android:onClick="register"
    android:text="Register"
    android:textColor="#000000"/>

在Java文件中,您必须编写类似此方法的方法。

1
2
public void register(View view) {
}

我用XML文件编写这段代码…

1
2
3
4
5
6
7
8
9
<Button
    android:id="@+id/btn_register"
    android:layout_margin="1dp"
    android:layout_marginLeft="3dp"
    android:layout_marginTop="10dp"
    android:layout_weight="2"
    android:onClick="register"
    android:text="Register"
    android:textColor="#000000"/>

把这段代码写成片段…

1
2
public void register(View view) {
}


最好的方法是使用以下代码:

1
2
3
4
5
6
7
 Button button = (Button)findViewById(R.id.btn_register);
 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //do your fancy method
            }
        });