【问题标题】:Rapid click event handling on AndroidAndroid 上的快速点击事件处理
【发布时间】:2014-05-29 08:28:26
【问题描述】:

如果您快速点击 2 个单独的按钮,甚至是同一个按钮,Android 将同时处理这两个点击事件。当它可以启动两次服务、两次显示对话框、或相互启动活动等时,这将成为一个问题。

防止双击单个按钮的修复非常简单,答案在 StackOverflow 上。您可以使用布尔标志简单地检查按钮是否已被单击,并停止处理任何进一步的单击事件。

我的问题是用户可能会快速按下两个(或更多)不同的按钮(或列表项或 ActionBar 项等)。处理“已单击按钮 x -> 更好地禁用按钮 x、y、z”的每个组合将非常乏味且容易出错。

有没有人对此有一个体面的解决方案?

【问题讨论】:

  • one 布尔标志怎么样:carryOn - 指示是继续处理输入事件 - 还是处理掉它。所以,基本上会有两种状态 - 检查内部事件监听器==> if (carryOn) { car​​ryOn = false; ... } 完成后,您还需要释放锁。听起来确实很麻烦。我希望有人发布更好的答案。

标签: android


【解决方案1】:

我会提出我的 .02,尽管这可能不是一个体面的解决方案。这很简单,不考虑例如启用按钮、调度或任何其他复杂的操作。它可能会删除一些样板,例如禁用按钮,但代价是丑陋的 try-finally 语法:

public final class EventLocker {

    private static Object lock;

    public static synchronized boolean lock(final Object object) {
        if (null == object) { throw new IllegalArgumentException("object must not be null"); }
        if (null == lock) {
            lock = object;
            return true;
        }
        return false;
    }

    public static synchronized boolean release(final Object object) {
        if (null != lock && lock.equals(object)) {
            lock = null;
            return true;
        }
        return false;
    }

}

然后,假设您希望在按钮上设置 OnClickListener:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (EventLocker.lock(view)) {
            try {
                doSomething();
            } finally {
                EventLocker.release(view);
            }
        }
    }
});

如果你一直使用 lock-unlock 惯用语,你会得到保证,例如在按钮 A 释放锁定之前,按钮 B 不会做任何事情。从好的方面来说,如果需要,添加自定义要求很容易。


回答下面的第一条评论:

你说得完全正确——我的回答有点草率,因为我没有在回答中特别提到这一点。

例如,每个onClick() 事件都会发布到主线程队列。有趣的是,它提出了一个我不可避免地试图避免的话题​​。如果按钮单击事件是按顺序处理的并且不离开主线程,那么例如双击不是异常,而是在短时间内依次处理了两个有效动作。

这确实没有什么问题 - 150 毫秒的短时间可能没有意义,但 10 秒可能是一个完全可行的选择。在这种情况下,我认为最好确保 onClick 实现足够聪明,可以重用、重建或以任何其他方式处理手头的资源。

【讨论】:

  • 感谢您的回复,它确实给了我一些想法。看起来这个解决方案无济于事,因为所有点击事件都在主线程上处理,所以在按钮 A 完成之前按钮 B 不会做任何事情。如果我错了,请纠正我
  • 嗨@Bradley,我已经编辑了我的答案,因为我的回复太长了,无法放入评论中。 :)
  • 是的 - 目前单独处理每个 onClick 是我考虑过的方法之一,但我认为这种方法似乎有点麻烦。我希望得到一个更加集中的答案,或者其他开发人员无需过多考虑所有其他交互即可轻松实现的答案。
【解决方案2】:

如果您快速点击 2 个单独的按钮,甚至是同一个按钮,Android 将同时处理这两个点击事件。当它可以启动两次服务、两次显示对话框、或相互启动活动等时,这将成为一个问题。

编写良好的 UI 是幂等的,通常通过验证:

  • 如果您已经启动服务,请不要启动该服务
  • 如果对话框正在显示,则不显示该对话框
  • 如果您已经开始并且自己尚未停止,请不要开始其他活动
  • 不要重新删除已删除的项目

在某些情况下,开发人员不必担心完全幂等,因为框架会处理它(例如,给定一个编写良好的 onStartCommand() 方法,两次启动服务应该不是问题;您不能删除相同的 _ID两次)。在某些情况下,开发人员不必担心完全幂等,因为对用户的影响是非破坏性的(例如,启动两次活动)。在某些情况下,开发人员不必担心这个问题,因为触发机制有效地阻止了重复效果(例如,长按)。

在某些情况下,我们只是懒惰。 :-)

但验证应该针对工作,而不是触发器。无论我们是由于单击按钮、单击操作项、单击列表行、摇晃设备、传入 GCM 消息还是其他原因而启动服务,我们都需要确保以幂等方式处理工作。

我的问题是用户可能会快速按下两个(或更多)不同的按钮(或列表项或 ActionBar 项等)。

这是超越幂等性。这也是不太可能的,因为用户只是没有那么快地移动他们的手指——这是测试猴子比普通人类更容易遇到的事情。而且,AFAIK 很少有开发人员担心这种事情。

如果你真的想担心,那就想出尽可能少的“担心”。例如,如果用户连续点击两个操作栏项目,其中一个将当前查看的详细信息标记为收藏,另一个则弹出对话框以允许用户将密码与当前查看的详细信息相关联, 谁在乎?为什么这本质上是无效的?

但除此之外,是的,这会很乏味。您需要跟踪所有可能的“不要快速连续执行此工作”,并确定您是否在各种回调中遇到了“快速连续”情况(onClick()onListItemClick()onOptionsItemSelected() )。很多代码都可以分解成某种EventMonitor,它知道你的业务规则,并且可以报告你是否遇到了快速成功的场景。

【讨论】:

  • View#cancelPendingInputEvents() api 可能有助于解决这个问题。见this commit message
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-07
  • 2011-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-17
  • 1970-01-01
相关资源
最近更新 更多