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

Posted

技术标签:

【中文标题】android:onClick XML 属性与 setOnClickListener 究竟有何不同?【英文标题】:How exactly does the android:onClick XML attribute differ from setOnClickListener? 【发布时间】:2011-05-08 09:35:45 【问题描述】:

据我所知,您可以通过两种方式将 onClick 处理程序分配给按钮。

使用android:onClick XML 属性,您只需将公共方法的名称与签名void name(View v) 一起使用,或者使用setOnClickListener 方法,您传递一个实现OnClickListener 接口的对象。后者通常需要一个我个人不喜欢的匿名类(个人品味)或定义一个实现 OnClickListener 的内部类。

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

【问题讨论】:

我读到了你的问题,我认为你和我一样被困在同一个地方。我遇到了一个非常好的视频,它对解决我的问题很有帮助。在以下链接上找到视频:youtube.com/watch?v=MtmHURWKCmg&feature=youtu.be 我希望这对您也有帮助:) 对于那些想节省时间观看上面评论中发布的视频的人来说,它只是演示了两个按钮如何具有相同的方法,因为它在布局文件中的onClick 属性。这要归功于参数View v。您只需检查if (v == findViewById(R.id.button1)) 等.. @Imray 我认为最好使用v.getId() == R.id.button1,因为您不必找到实际控制并进行比较。而且您可以使用switch 而不是大量的ifs。 使用 xml android:onClick 会导致崩溃。 【参考方案1】:

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

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

代码实现

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_
    android:layout_
    android:text="Click me!"
    android:onClick="myFancyMethod" />
<!-- even more layout elements -->

在后台,Android 只执行 Java 代码,在点击事件上调用您的方法。

请注意,使用上面的 XML,Android 将仅在当前 Activity 中查找 onClick 方法 myFancyMethod()。如果您正在使用片段,请务必记住这一点,因为即使您使用片段添加上述 XML,Android 也不会在用于添加 XML 的片段的.java 文件中查找onClick 方法。

我注意到的另一件重要事情。您提到您不喜欢匿名方法。你的意思是说你不喜欢匿名classes

【讨论】:

我不是 Java 专家,但是是的,我的意思是匿名类。感谢您的回复。很清楚。 注意,如果你使用XML onclick,你必须把onclick方法(myFancyMethod())放在当前Activity中。如果您使用片段,这一点很重要,因为设置 onclick 侦听器的编程方式可能会在片段的 onCreateView()... 中处理点击的方法...如果从XML。 是的,方法必须是公开的。 有趣的是,在代码中这样做确实允许通过将方法设为私有来屏蔽方法访问,而以 xml 方式执行会导致方法暴露。 函数(在 XML 方法中)必须在 Activity 中这一事实不仅在您考虑片段时很重要,而且在自定义视图(包含按钮)时也很重要。当您有一个在多个活动中重用的自定义视图,但您希望在所有情况下都使用相同的 onClick 方法时,XML 方法并不是最方便的方法。您需要将此 onClickMethod (具有相同的主体)放在使用您的自定义视图的每个活动中。【参考方案2】:

当我看到上面的答案时,这让我意识到我的问题不是把参数(View v)放在花哨的方法上:

public void myFancyMethod(View v) 

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

android:onClick="myFancyMethod"/>

希望对某人有所帮助。

【讨论】:

这是最好的答案——我遇到了同样的问题,方法中没有写(View v)...【参考方案3】:

android:onClick 适用于 API 级别 4 及以上,因此如果您的目标是

【讨论】:

【参考方案4】:

检查是否忘记公开方法!

【讨论】:

为什么必须公开? @eRaisedToX 我认为很清楚:如果它不公开,则无法从 Android 框架调用。【参考方案5】:

指定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 中使用带有 onClick 的 Button 时,应在 Activity 中定义回调getContext().getClass().getMethod() 调用将方法搜索限制在当前上下文中,在 Fragment 的情况下为 Activity。因此方法是在 Activity 类而不是 Fragment 类中搜索的。 回调方法应该接受 View 参数:因为 Java Class getMethod 搜索接受 View.class 作为参数的方法。

【讨论】:

这对我来说是缺失的部分——Java 使用反射来查找以 getContext() 开头的点击处理程序。点击如何从片段向上传播到 Activity 对我来说有点神秘。【参考方案6】:

这里有很好的答案,但我想补充一行:

在 XML 中的 android:onclick 中,Android 在后台使用 java reflection 来处理这个问题。

而as explained here, 反射总是会降低性能。 (尤其是在 Dalvik VM 上)。注册onClickListener比较好。

【讨论】:

它可以使应用程序减慢多少? :) 半毫秒;甚至不?与实际膨胀布局相比,它就像羽毛和鲸鱼【参考方案7】:

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

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

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

【讨论】:

【参考方案8】:

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

最好尽可能在匿名 Java 类上使用 xml 属性“onClick”。

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

XML 属性/onClick 属性

XML 部分

<Button
    android:layout_
    android:layout_
    android:id="@+id/button1" 
    android:onClick="showToast"/>

Java 部分

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

匿名 Java 类/setOnClickListener

XML 部分

<Button
    android:layout_
    android:layout_/>

Java 部分

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

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

对于匿名 Java 类,我们总是必须为我们的 元素,但带有 XML 属性的 id 可以省略。 对于匿名 Java 类,我们必须主动搜索元素 在视图内部(findViewById 部分),但具有 XML 属性 Android 为我们做到了。 匿名 Java 类至少需要 5 行代码,因为我们可以 看,但是对于 XML 属性,3 行代码就足够了。 对于匿名 Java 类,我们必须将方法命名为“onClick”, 但是使用 XML 属性,我们可以添加任何我们想要的名称,这将 极大地提高了我们代码的可读性。 Google 在 API 级别添加了 XML“onClick”属性 4 版本,这意味着它是更现代的语法和现代 语法几乎总是更好。

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

如果我们正在处理片段。只能添加 onClick 属性 到一个活动,所以如果我们有一个片段,我们将不得不使用一个 匿名类。 如果我们想将 onClick 监听器移动到一个单独的类 (也许如果它非常复杂和/或我们想在 应用程序的不同部分),那么我们就不想使用 xml 属性。

【讨论】:

请注意,使用 XML 属性调用的函数应该始终是公共的,如果它们被声明为私有,则会导致异常。【参考方案9】:

使用 Java 8,您可能可以使用 Method Reference 来实现您想要的。

假设这是您的 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-expression-style 事件处理程序类似的谨慎。这个article 解释得非常好。

PS。对于那些好奇我如何才能真正在 Android 中使用 Java 8 语言功能的人,这是由retrolambda library 提供的。

【讨论】:

【参考方案10】:

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

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

当你在代码中初始化你的新视图对象时,你可以简单地做mView.setOnClickListener(this);

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

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

这个答案与说“不,这是不可能通过代码”的答案不同

【讨论】:

【参考方案11】:
   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;
    

【讨论】:

【参考方案12】:

支持 Ruivo 的回答,是的,您必须将方法声明为“public”才能在 Android 的 XML onclick 中使用 - 我正在开发一个从 API 级别 8(minSdk...)到 16(targetSdk... )。

我将我的方法声明为私有的,但它导致了错误,只是将其声明为公共的效果很好。

【讨论】:

似乎在宿主Activity的类中声明的变量不能在声明的回调范围内使用;如果在 myFancyMethod() 中使用,Bundle Activity.mBundle 将抛出 IllegalStateException/NullPointerException。【参考方案13】:

请注意,虽然android:onClick XML 似乎是一种处理点击的便捷方式,但setOnClickListener 实现除了添加onClickListener 之外还做了一些额外的事情。事实上,它把视图属性clickable 设置为true。

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

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

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

因此,如果您不想遇到麻烦并且从不考虑它,最好使用 setOnClickListener 或带有注释 @OnClick(R.id.button) 的 ButterKnife 之类的库

【讨论】:

onClick XML 属性也设置了clickable = true,因为它在内部调用View 上的setOnClickListener【参考方案14】:

假设,你想添加这样的点击事件main.xml

<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文件中,你必须写一个类似这个方法的方法。

public void register(View view) 

【讨论】:

【参考方案15】:

我在 xml 文件中编写这段代码...

<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"/>

并在片段中编写此代码...

public void register(View view) 

【讨论】:

这在片段中是可能的。【参考方案16】:

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

 Button button = (Button)findViewById(R.id.btn_register);
 button.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                //do your fancy method
            
        );

【讨论】:

我不明白这如何回答问题 - 提问者希望避免创建匿名类,而是使用类方法。【参考方案17】:

为了让你的生活更轻松并避免 setOnClicklistener() 中的匿名类,请实现一个 View.OnClicklistener 接口,如下所示:

公共类 YourClass 扩展 CommonActivity 实现 View.OnClickListener,...

这样可以避免:

btn.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        yourMethod(v);
    
);

然后直接转到:

@Override
public void onClick(View v) 
  switch (v.getId()) 
    case R.id.your_view:
      yourMethod();
      break;
  

【讨论】:

以上是关于android:onClick XML 属性与 setOnClickListener 究竟有何不同?的主要内容,如果未能解决你的问题,请参考以下文章

如何绑定android点击事件

Android Proguard - 如何保持仅从 XML 布局引用的 onClick 处理程序

xml onClick 中的 Kotlin 不起作用

Android中点击事件功能实现示例

如何告诉 ProGuard 保留用于 onClick 的函数?

Android使用TextView,设置onClick属性无效解决的方法