在 Android 中实施 View.OnClickListener 的最佳方式

Best way to implement View.OnClickListener in Android

假设我们有一个 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 中创建一个私有 class 并让内部 class 实现 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
            }
        }
    }
}

这样代码看起来更有条理且易于维护。

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

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

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

那么,实现 ClickListener 的最佳方式到底是什么?

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

更新:

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

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

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

我发现使用 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
  }
}

对于这种特殊情况,我认为维护 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
            }
        }
    };

}

你的 ClickListener 是一个内部非静态 class 这个 'has-a' 的耦合与你的 class Activity 实现的 View.OnClickListener。这是因为你的内部 ClickListener 需要一个 ActivityMain 的实例并且真的不能被重用。我会争辩说你过度工程并且实际上没有获得任何东西。

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

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

我在 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;
            ...
        }
    }

    ...

}

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

通过实施接口,您的 class 不会变成那样.. 就像您说的那样:

"Our Activity-Subclass "是一个“ClickListener,这并不完全正确。”

你的 class 只能有 "Is-a" 关系,如果它扩展,在这种情况下是 Activity。实现一个接口意味着它可以表现得像接口设置了它的契约一样。

一个例子:

class Peter extends Human.. 表示 Peter 是人类..

class Peter也可以实现程序员、音乐家、老公等 意味着彼得可以像上面那样表现。

至于最佳实践,您可以制作一个完全独立的 class 来实现 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);
   }

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

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 .
        }
    };

并将点击事件处理到按钮中,任何类型的点击事件如

button_name.setOnClickListener(method_name);

它的工作非常简单 谢谢

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

没有联系

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

在这两种情况下,我都将操作声明为成员,因此我可以快速浏览一下 Android 组件支持的具体操作。

如果有像 "show a Toast if button is pressed" 这样微不足道的东西,我使用匿名内部 classes 作为 UI 回调,因为在可维护性方面你通常不太关心它们.

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

View.OnClickListener 接口实现到 Activity 是可行的方法。 As 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
            }
        }
    }
}

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

所以这只是一个讨论,而不是获得实施 View.OnClickListener 的最佳解决方案,因为如果你从每个人的实际角度出发,你会得到一个简单且内存效率高的解决方案。

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

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

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

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

让我们假设,在我的 activity 中,我有 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
            }

        }

     }

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

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

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

但是如果我们必须实现多个接口,那么使用匿名 classes / lambda

可能是一个很好的解决方案
 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;
            }
        }
}
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;
    }
  }
}
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;

        }
    }

}