【问题标题】:Best way to implement View.OnClickListener in Android在 Android 中实现 View.OnClickListener 的最佳方式
【发布时间】:2015-05-06 16:50:40
【问题描述】:

假设我们有一个有很多视图的 Activity,要在其上注册 OnClickListener

最常见的实现方式是让 Activity-Subclass 实现 OnClickListener,如下所示:

public class ActivityMain extends Activity implements View.OnClickListener
{   
    @Override
    public void onClick(View view)
    {
        switch (view.getId())
        {
            //handle multiple view click events
        }
    }
}

我喜欢实现它的方式是在 Activity-Subclass 内部创建一个私有类,并让该内部类实现 OnClickListener:

public class ActivityMain extends Activity implements View.OnClickListener
{
    private class ClickListener implements View.OnClickListener
    {   
        @Override
        public void onClick(View view)
        {
            switch (view.getId())
            {
                //handle multiple view click events
            }
        }
    }
}

这样代码看起来更有条理,也更容易维护。

此外,谈到“Is-a”、“Has-a”关系,后者似乎是一个很好的做法,因为现在 Activity-Subclass 将与 ClickListener 具有“Has-a”关系。 而在前一种方法中,我们会说我们的 Activity-Subclass “Is-a” ClickListener,这并不完全正确。

请注意,我不关心后者会导致的内存开销。

另外,在xml中添加onClick标签是完全没有问题的。

那么,真正实现 ClickListener 的最佳方式是什么?

请不要推荐任何库,如 RoboGuice 或 ButterKnife 等。

更新:

我想分享一下我最终采用的方法。

我直接在Activity/Fragment中实现监听器。

就 OOP 设计而言。 “HAS-A” 方法没有提供任何实际好处,甚至会占用更多内存。考虑到我们将为我们实现的每个类似侦听器创建的嵌套类的数量(以及内存开销),显然应该避免这种方法。

【问题讨论】:

  • 您的问题非常主观,您所说的“最佳”是什么意思。你的目标/目的是什么?
  • 目标很简单,遵循最佳实践:)
  • 你的ClickListener 是一个内部非静态类,这个'has-a' 的耦合与你的类Activity 实现View.OnClickListener 没有什么不同。这是因为您的内部ClickListener 需要ActivityMain 的实例,并且确实无法重用。我会争辩说你已经过度设计并且实际上并没有获得任何东西。
  • @CharlesDurham 这个特殊案例不是关于代码重用,而是关于更好的 OO 和易于维护......
  • 说“has-a”比“is-a”更好并不重要。重要的是为什么软件开发人员更喜欢“有-a”关系而不是“是-a”。在这种情况下,您的抽象不会从使用组合而不是继承中获得任何通常的好处。

标签: java android listener onclicklistener implements


【解决方案1】:

首先,Android 没有定义关于注册点击监听器的最佳实践。这完全取决于您的用例。

实现View.OnClickListener 接口到Activity 是要走的路。由于 Android 强烈建议接口实现一遍又一遍,无论是 Activity 还是 Fragment。

现在就像你描述的那样:

public class ActivityMain extends Activity implements View.OnClickListener
{
    private class ClickListener implements View.OnClickListener
    {   
        @Override
        public void onClick(View view)
        {
            switch (view.getId())
            {
                //handle multiple view click events
            }
        }
    }
}

这是你的方法。现在这是您的实现方式,如果您不关心内存开销,这没有任何问题。但是,如果您可以在主类中简单地实现它,那么创建内部类并实现 View.OnClickListener 有什么好处,这也可以导致您需要的代码清晰和简单。

所以这只是一个讨论,而不是获得实现 View.OnClickListener 的最佳解决方案,因为如果您遵循每个人的实际观点,您将寻求一个简单且内存高效的解决方案.

所以我更喜欢传统的方式。它使事情变得简单而高效。检查下面的代码:

@Override
public void onClick(View view)
{
    switch (view.getId())
    {
        //handle multiple view click events
    }
}

P.S : 你的方法肯定会增加代码行数 :P ;)

【讨论】:

  • LOC 是什么意思?
  • @AliKazi:对不起,我来晚了。 LOC 表示代码行
  • 您不能在开关中使用view.getId(),因为每种情况:都需要一个值或枚举。像 case 1:case ENUM_ITEM:case R.id.myBtn: 是一个变量。所以你需要使用if elseif 块语句。如果你有很多小部件,你最终会得到一个丑陋的if elseif 代码块。
  • 回答OP,最好使用匿名的onclicklistener或者实现onclicklistener的成员实例。在我看来,它使代码更具可读性。筛选 swich 和 if else 块的情况有点难以阅读。
【解决方案2】:

首先让我们在这里弄清楚基础知识..

通过实现一个接口,你的类不会变成那样......就像你说的那样:

“我们的 Activity-Subclass “Is-a” ClickListener,这并不完全正确。”

你的类只有在扩展时才具有“Is-a”关系,在本例中为Activity。实现一个接口意味着它可以像设置它的接口那样运行。

一个例子:

class Peter extends Human .. 意味着 Peter 是人类..

类 Peter 还可以实现程序员、音乐家、丈夫等 意味着彼得可以像上面那样表现。

至于最佳实践,您可以创建一个完全独立的类来实现 OnClickListener,如下所示:

class MyListener implements View.OnClickListener{

  @Override
public void onClick(View view) {
        // do whatever you want here based on the view being passed
    }

}

在您的主 Activity 中,您可以实例化 MyListener 并调用 onClick() 并在其中传递您的视图:

MyListener listener = new MyListener();

Button b = null;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    button = (Button)findViewById(R.id.button);
    listener.onClick(button);
   }

【讨论】:

  • @Sarthak Mittal 您是否尝试过 Sharp Edge 建议的方法,发现任何问题?我还计划将我的应用程序主活动侦听器移动到单独的类,因为我的家庭活动将对 10 个操作(单击、选择和其他)做出反应。课程长度也接近 4K 行,新手很难找到改变的地方。
  • 是的,这就是重点,将侦听器代码分离到另一个类中以便轻松查找和进行更改。
  • 当我添加意图时,在 onclick() 内部,它跳过了我创建 MyListeners 对象的类。如何解决这个问题?
  • @PrajwalW 你是什么意思它跳过了你的课?分享你在onClick()中的sn-p代码
【解决方案3】:

我在Activity implements View.OnClickListener 的位置使用button.setOnClickListener(this);,然后在单独的方法中获取Button 的ID。请参阅下面的示例:

public class MyActivity extends ActionBarActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.YOUR_LAYOUT);

        ...

        Button myFirstButton = (Button) findViewById(R.id.YOUR_FIRST_BUTTON);
        myFirstButton.setOnClickListener(this);

        Button mySecondButton = (Button) findViewById(R.id.YOUR_SECOND_BUTTON);
        mySecondButton.setOnClickListener(this);

        ...

    }

    ...

    @Override
    public void onClick(View v) {
        Button b = (Button) v;
        switch(b.getId()) {
            case R.id.YOUR_FIRST_BUTTON:
                // Do something
                break;
            case R.id.YOUR_SECOND_BUTTON:
                // Do something
                break;
            ...
        }
    }

    ...

}

【讨论】:

    【解决方案4】:

    在这里您可以创建一个 btnClickListner 对象,然后在您想要对按钮执行 onCLieck 操作时调用该 btnCLickLisner 对象..

    让我们假设,在我的活动中,我有 5 到 10 个按钮,并且为每个按钮编写单独的 onclick 列表是个坏主意。所以要克服这个,我们可以像下面这样使用..

    注册您的按钮

    Button button1 = (Button)findViewById(R.id.button1);
    Button button2 = (Button)findViewById(R.id.button2);
    Button button3 = (Button)findViewById(R.id.button3);
    Button button4 = (Button)findViewById(R.id.button4);
    Button button5 = (Button)findViewById(R.id.button5);
    

    在这里,我在单击后将 onclick 列表器设置为我的按钮

    button1.setOnClickListener(btnClickListner);
    button2.setOnClickListener(btnClickListner);
    button3.setOnClickListener(btnClickListner);
    button4.setOnClickListener(btnClickListner);
    button5.setOnClickListener(btnClickListner);
    

    这是 btnClick Listner 的实现

    View.OnClickListener btnClickListner = new OnClickListener()
        {
      @Override
            public void onClick( View v )
            {
                // TODO Auto-generated method stub
                if( button1.getId() == v.getId() )
                {
                    //Do Button1 click operations here
    
                }
                else if( button2.getId() == v.getId() )
                {
    
                   // Do Button2 click operations here
    
                }
                else if( button3.getId() == v.getId() )
                {
                     // Do Button3 click operations here
    
                }
                else if( button4.getId() == v.getId() )
                {
                    // Do Button4 click operations here
    
                }
                else if( button5.getId() == v.getId() )
                {
                    // Do Button5 click operations here
                }
    
            }
    
         }
    

    【讨论】:

      【解决方案5】:

      我发现使用Butterknife 可以生成干净的代码。而且因为它使用代码生成(而不是反射),所以性能开销很小。

      public class ActivityMain extends Activity {
      
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.inject(this);
        }
      
        @OnClick(R.id.button_foo)
        void onFoodClicked() {
          // Do some foo
        }
      
        @OnClick(R.id.button_bar)
        void onBarClicked() {
          // do some bar
        }
      }
      

      【讨论】:

      • 显然他没有要求任何类型的库。
      • 这与从 xml 添加 onClick 相同。
      • @AmmY - 我会礼貌地不同意 xml 声明。 1. 它没有在 xml 中定义,所以视图没有定义动作,2. 这是 Activity 上的很多受限方法。
      【解决方案6】:

      你的ClickListener 是一个内部非静态类,这个“has-a”的耦合与你的类Activity 实现View.OnClickListener 没有什么不同。这是因为您的内部ClickListener 需要ActivityMain 的实例,并且确实无法重用。我会争辩说你已经过度设计并且实际上并没有获得任何东西。

      编辑:为了回答您的问题,我喜欢为每个小部件设置匿名 View.OnClickListener。我认为这创造了最好的逻辑分离。我也有像setupHelloWorldTextView(TextView helloWorldTextView); 这样的方法,我将所有与该小部件相关的逻辑放在其中。

      【讨论】:

      • 为每个小部件设置一个单独的监听器确实是一个可悲的实现,当然这只是我的意见:)
      • @SarthakMittal 是完全合理的,将逻辑解耦到单独的侦听器对象中变得强制性(关于编写干净的代码),特别是如果您希望跨使用相同点击侦听器逻辑的活动开发可重用组件。说一个用于多个活动的简单排序对话框。
      【解决方案7】:

      第一种方法比另一种更好,因为这就是为什么View.OnClickListenerInterface 而不是abstract class。此外,由于您使用的是非静态内部类,因此后者可能会在各种情况下泄漏。

      【讨论】:

      【解决方案8】:

      对此的一个小评论,也许还有一点点话题。

      什么,如果我们不只是实现 OnClickListener 并且我们还有一堆其他的 Listeners / Callback 来实现。在我看来,在类中实现所有这些而不是使用匿名类/ lambda 会变得很混乱。很难记住哪个方法属于哪个接口。

      因此,如果我们必须多次实现一个接口(在本例中为 OnClickListener),那么在类基础上实现并使用 switch/case 是一个很好的解决方案。

      但如果我们必须实现多个接口,使用匿名类/lambda 可能是一个很好的解决方案

      【讨论】:

        【解决方案9】:

        对于这种特殊情况,我会说维护 OnClickListener 的单个实例是最适合您的方法。您将拥有“Has-a”关系并且不需要创建多个实例,因为您正在使用 onClick(View view) 回调中的视图 ID 处理行为。

        public class ActivityMain extends Activity implements View.OnClickListener {
        
            private View.OnClickListener mClickListener = new View.OnClickListener() {   
                @Override
                public void onClick(View view) {
                    switch (view.getId()) {
                        //handle multiple view click events
                    }
                }
            };
        
        }
        

        【讨论】:

          【解决方案10】:

          只是你使用like不实现子类或不处理点击事件就这样做。

          android.view.View.OnClickListener method_name = new android.view.View.OnClickListener() {
          
                  @Override
                  public void onClick(View v) {
                      // TODO Auto-generated method stub
                      // put your code .
                  }
              };
          

          并将单击事件处理为按钮 ya 任何类型的单击事件,例如

          button_name.setOnClickListener(method_name);
          

          它的工作非常简单 谢谢

          【讨论】:

          • 是的,我知道,它是这样工作的,但问题是,最好的方法是什么?为什么? :)
          • @SarthakMittal ok Fine.. 最好的方法是实现 onclicklistener 并使用 switch case 和每个按钮在 switch 语句中执行方法和方法调用。它非常简单和好,我在我的所有应用程序中都遵循这种方式。谢谢
          • 我们都这样做,这是最常见的实现,请正确阅读问题然后回答。
          【解决方案11】:
           public class ProfileDetail extends AppCompatActivity implements View.OnClickListener {
          
                    TextView tv_address, tv_plan;
          
                  @Override
                  protected void onCreate(Bundle savedInstanceState) {
                      super.onCreate(savedInstanceState);
                      setContentView(R.layout.activity_profile_detail);
          
          
          
                      tv_address = findViewById(R.id.tv_address);
                      tv_plan = findViewById(R.id.tv_plan);
          
                      tv_address.setOnClickListener(this);
                      tv_plan.setOnClickListener(this);
              }
           @Override
              public void onClick(View view) {
                      switch (view.getId()) {
                          case R.id.tv_plan:
                              startActivity(new Intent(getApplicationContext(),PlanActivity.class));
                              break;
                          case R.id.tv_address:
                               startActivity(new Intent(getApplicationContext(),AddressActivity.class));
                              break;
                      }
                  }
          }
          

          【讨论】:

            【解决方案12】:

            这真的取决于你想要实现什么。如果你有例如一个具有线程、依赖项等的复杂功能,我个人喜欢将它与 Activity 完全解耦到一个单独的类 XyzAction 中,该类执行繁重的工作,了解某些 Invokers 并在需要时返回结果。我的Invokers 基本上是对象,实现OnClick/OnTouch/etc.Listeners 并将自己绑定到所需的操作。例如。可能有一个LoginInvokerButton 和一个ImageView 实现OnClickListener,还有一个通用的ActionListener,它在单击MenuItem 时被调用。 Invoker 具有用于向用户显示进度和绑定操作结果的更新方法。该操作将更新发布到其Invokers,并且可以被垃圾收集,如果它们都死了,因为它与 UI 没有连接。

            对于不太复杂的操作,我将它们直接耦合到 Android 组件(即Activity/Feagment/View)并称它们为Actions,它们之间的很大区别是直接实现 UI 回调。

            在这两种情况下,我都将操作声明为成员,因此我可以快速查看 Android 组件支持哪些特定操作。

            如果有一些琐碎的事情,比如“按下按钮时显示 Toast”,我会使用匿名内部类进行 UI 回调,因为您通常不会太在意它们的可维护性。

            【讨论】:

              【解决方案13】:
              public class MainActivity extends AppCompatActivity implements View.OnClickListener {
              Button north,south,east,west;
              @Override
              protected void onCreate(Bundle savedInstanceState) {
                  super.onCreate(savedInstanceState);
                  setContentView(R.layout.activity_main);
                  init();
                  north.setOnClickListener(this);
                  south.setOnClickListener(this);
                  east.setOnClickListener(this);
                  west.setOnClickListener(this);
              }
              
              private void init(){
                  north = findViewById(R.id.north);
                  south = findViewById(R.id.south);
                  east = findViewById(R.id.east);
                  west = findViewById(R.id.west);
              }
              
              @Override
              public void onClick(View v) {
                  switch (v.getId()) {
                      case R.id.north:
                          Toast.makeText(MainActivity.this,"NORTH",Toast.LENGTH_SHORT).show();
                          break;
                      case R.id.south:
                          Toast.makeText(MainActivity.this,"SOUTH",Toast.LENGTH_SHORT).show();
                          break;
                      case R.id.east:
                          Toast.makeText(MainActivity.this,"EAST",Toast.LENGTH_SHORT).show();
                          break;
                      case R.id.west:
                          Toast.makeText(MainActivity.this,"WEST",Toast.LENGTH_SHORT).show();
                          break;
                  }
                }
              }
              

              【讨论】:

                【解决方案14】:
                public class MainActivity extends AppCompatActivity implements View.OnClickListener {
                
                    private Chronometer chronometer;
                    private Button startButton;
                    private Button stopButton;
                
                    @Override
                    protected void onCreate(Bundle savedInstanceState) {
                
                        super.onCreate(savedInstanceState);
                        setContentView(R.layout.activity_main);
                
                        chronometer = findViewById(R.id.chronometer);
                        startButton =findViewById(R.id.startBtn);
                        stopButton = findViewById(R.id.stopBtn);
                
                        startButton.setOnClickListener(this);
                        stopButton.setOnClickListener(this);
                
                    }
                
                    @Override
                    public void onClick(View v) {
                        switch (v.getId()){
                            case R.id.startBtn:
                                chronometer.start();
                                break;
                            case R.id.stopBtn:`
                                chronometer.stop();
                                break;
                
                        }
                    }
                
                }
                

                【讨论】:

                • 最好在代码中写下它的作用
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2013-05-08
                • 2011-10-04
                • 2016-01-19
                • 1970-01-01
                • 2021-08-02
                • 2016-06-14
                • 1970-01-01
                相关资源
                最近更新 更多