双击锁定。自行车?

怎么开始的



我再次挖掘了Legacy代码并努力解决了上下文泄漏问题,打破了应用程序按钮的双击锁定。我必须寻找我到底破坏了什么以及如何实现它。考虑到实际上对于这种锁定,建议在短时间内禁用UI元素本身或简单地忽略随后的单击,因此在我看来,现有解决方案颇为有趣,因为链接代码。但是仍然需要创建按钮列表并编写大量支持代码。创建该类的实例,该实例将存储元素列表,填充它并在每个单击处理程序中调用三个方法。通常,您可以在许多地方忘记或混淆某些事物。而且我不喜欢记住任何事情。每当我似乎记得某件事时,结果我可能记错了,或者有人已经重做了一次。



我们省略了执行此操作是否正确,还是仅需要有效地将可交互处理程序转移到后台流的问题。我们将再制造一辆自行车,也许会更舒适一些。



总的来说,自然懒惰使我想到,如果没有所有这些,是否有可能做?好吧,阻止按钮并忘记。在那里,它将继续按预期工作。最初,人们想到可能已经存在某种可以连接的库,并且只需要调用一种类型的方法-sdelayMneHorosho()。



但同样,我是一个从某种程度上讲是老派的人,因此不喜欢任何不必要的瘾。图书馆和代码生成的动物园使我对人性感到失望和失望。好吧,肤浅的谷歌搜索只发现了带有计时器或其变型的典型选项。



例如:



一个







等等...



不过,您可能只需在处理程序的第一行关闭元素,然后再将其打开。唯一的问题是,以后执行此操作并不总是一件容易的事,在这种情况下,有必要在按下按钮可能导致的所有执行变体的结尾添加对“开机”代码的调用。毫不奇怪,我没有即时搜索此类解决方案。它们非常复杂,维护起来极为困难。



我想使它更简单,更通用,并记住它必须尽可能少。



项目解决方案



就像我说的那样,尽管存在现有解决方案的所有缺点,但现有解决方案的安排很有趣。至少这是一个单独的简单类。它也允许禁用不同的项目列表,尽管我不确定这是否有意义。



原始双击锁定类
public class MultiClickFilter {
    private static final long TEST_CLICK_WAIT = 500;
    private ArrayList<View> buttonList = new ArrayList<>();
    private long lastClickMillis = -1;

    // User is responsible for setting up this list before using
    public ArrayList<View> getButtonList() {
        return buttonList;
    }

    public void lockButtons() {
        lastClickMillis = System.currentTimeMillis();
        for (View b : buttonList) {
            disableButton(b);
        }
    }

    public void unlockButtons() {
        for (View b : buttonList) {
            enableButton(b);
        }
    }

    // function to help prevent execution of rapid multiple clicks on drive buttons
    //
    public boolean isClickedLately() {
        return (System.currentTimeMillis() - lastClickMillis) < TEST_CLICK_WAIT;  // true will block execution of button function.
    }

    private void enableButton(View button) {
        button.setClickable(true);
        button.setEnabled(true);
    }

    private void disableButton(View button) {
        button.setClickable(false);
        button.setEnabled(false);
    }
}


用法示例:



public class TestFragment extends Fragment {

	<=======  ========>

	private MultiClickFilter testMultiClickFilter = new MultiClickFilter();

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		<=======  ========>

		testMultiClickFilter.getButtonList().add(testButton);
		testMultiClickFilter.getButtonList().add(test2Button);

		<=======  ========>

		testButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				if (testMultiClickFilter.isClickedLately()) {
					return;
				}

				testMultiClickFilter.lockButtons();
				startTestPlayback(v);
				testMultiClickFilter.unlockButtons();
			}
		});

		test2Button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				if (testMultiClickFilter.isClickedLately()) {
					return;
				}

				testMultiClickFilter.lockButtons();
				loadTestProperties(v);
				testMultiClickFilter.unlockButtons();
			}
		});

		<=======  ========>
	}
	
	<=======  ========>
}




该类很小,原则上讲它是做什么的。简而言之,为了阻止某个活动或片段上的按钮,您需要创建MultiClickFilter类的实例,并在其列表中填充需要阻止的UI元素。您可以列出多个列表,但是在这种情况下,每个元素的处理程序必须“知道”要提取的“单击过滤器”的哪个实例。



另外,它不能让您仅忽略点击。为此,您必须阻止整个元素列表,然后必须将其解锁。这导致将其他代码添加到每个处理程序。是的,在示例中,我将unlockButtons方法放在finally块中,但您永远不会知道...通常,此决定会引起疑问。



新解决方案



总的来说,意识到可能不会有什么灵丹妙药,因此被认为是最初的前提:



  1. 建议不要将阻止按钮的列表分开。好吧,我无法提出任何需要这种分离的例子。
  2. 不要禁用元素(已启用/可点击),以保留元素的动画和通常的生动性
  3. 在为此目的设计的任何处理程序中阻止点击 假定适当的用户不会像从机关枪中那样在任何地方单击,并且为了防止意外的“反弹”,只需简单地将“每个人”的单击处理关闭几百毫秒就足够了。


因此,理想情况下,我们应该在代码中有一点进行所有处理,并且应该有一种方法可以在项目中的任何处理程序中从任何地方发生,并且可以阻止重复单击的处理。假设我们的UI并不意味着用户每秒单击两次以上。不需要,如果有必要,那么显然您将不得不单独关注性能,但是我们有一个简单的案例,因此,用颤抖的喜悦之声,不可能将应用程序置于无趣的功能上。而且,您不必每次都花很多时间来优化从一个活动到另一个活动的简单过渡的性能,也不必每次都闪烁进度对话框。



所有这些都将在主线程中为我们工作,因此我们不必担心同步。同样,实际上,我们可以使用这种方法将检查转移到是否需要处理单击或忽略它。好吧,如果可行,那么我们可以使阻塞间隔可自定义。因此,在非常糟糕的情况下,您可以增加特定处理程序的间隔。



可能吗?



事实证明,该实现非常简单和简洁。
package com.ai.android.common;

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.MainThread;

public abstract class MultiClickFilter {
    private static final int DEFAULT_LOCK_TIME_MS = 500;
    private static final Handler uiHandler = new Handler(Looper.getMainLooper());

    private static boolean locked = false;

    @MainThread
    public static boolean clickIsLocked(int lockTimeMs) {
        if (locked)
            return true;

        locked = true;

        uiHandler.postDelayed(() -> locked = false, lockTimeMs);

        return false;
    }

    @MainThread
    public static boolean clickIsLocked() {
        return clickIsLocked(DEFAULT_LOCK_TIME_MS);
    }
}


用法示例:



public class TestFragment {

	<=======  ========>

	private ListView devicePropertiesListView;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		devicePropertiesListView = view.findViewById(R.id.list_view);
		devicePropertiesListView.setOnItemClickListener(this::doOnItemClick);

		<=======  ========>

		return view;
	}

	private void doOnItemClick(AdapterView<?> adapterView, View view, int position, long id) {
		if (MultiClickFilter.clickIsLocked(1000 * 2))
			return;

		<=======  ========>
	}

	<=======  ========>
}




总的来说,现在您只需要将MultiClickFilter类添加到项目中,并检查它是否在每个单击处理程序的开始处被阻止:



        if (MultiClickFilter.clickIsLocked())
            return;


如果要处理单击,则会在指定的时间(默认情况下)设置一个阻止。该方法将使您不必考虑元素列表,不进行复杂的检查以及不手动管理UI元素的可用性。我建议在评论中讨论此实现,也许有更好的选择?



All Articles