在 Android,有什么方法可以禁用 Spinner 的长按行为吗?

On Android, is there any way to disable a Spinner's long-press behavior?

默认的微调器行为是,当它处于 "closed" 时,长按它会 "open" 它并显示下拉视图。我发现这种行为对用户来说可能是非常有问题的。例如,如果他们试图在屏幕上滚动某些东西,并且碰巧 "grab" 一个有微调器的地方,那么它不会滚动,而是在一秒钟左右后打开下拉视图,然后用户基本上只剩下手指放在其中一个下拉选项上(他们现在可能会不小心按下)。

所以,我想禁用长按行为,并在仅单击而不是长按时使用微调器 "open"。这可能吗?

你在 xml 中尝试过 android:longClickable="false" 了吗?

或者尝试这样的事情:

spinner.setOnTouchListener(new View.OnTouchListener() {
        private static final int MAX_CLICK_DISTANCE = 15;
        private static final int MAX_CLICK_DURATION = 1000;
        private long pressStartTime;
        private float pressedX;
        private float pressedY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    pressStartTime = System.currentTimeMillis();
                    pressedX = event.getX();
                    pressedY = event.getY();
                    break;
                }
                case MotionEvent.ACTION_UP: {
                long pressDuration = System.currentTimeMillis() - pressStartTime;
                if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, event.getX(), event.getY()) < MAX_CLICK_DISTANCE) {
                     //when long clicked.
                }
                break;
            }
        }
        return false;
}

所以,我想出了一个相对简单的方法来做到这一点,尽管它不是很优雅。基本上,我在 Spinner 的顶部创建了一个透明的覆盖视图,并将其设置为具有仅触发 Spinner 的点击的 OnClickListener。

XML:

    <Spinner 
        android:id="@+id/spinner"
        android:layout_width="match_parent" 
        android:layout_height="40dip" />

    <View android:id="@+id/spinner_overlay"
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:layout_alignLeft="@id/spinner" 
        android:layout_alignRight="@id/spinner"
        android:layout_alignTop="@id/spinner" 
        android:layout_alignBottom="@id/spinner"  />

Java:

        View spinnerOverlay = findViewById(R.id.spinner_overlay);
        spinnerOverlay.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                spinner.performClick();
            }

        });

如果您只需要恢复标准的点击行为,这是的更简单、更紧凑的版本:

spinner.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                v.performClick();
            }
            return true;
        }
    });

进一步思考

这种行为也让我很恼火。查了一下,好像有个名字:拖动打开。您可以在 AppCompatSpinner#onTouchEvent() 方法的源代码中查看它是如何定义的。

我看到这种强制行为的一些问题(以及一些人想要禁用它的原因):

  1. 它允许用户在微调器中 select 禁用值。通过长按、拖动和释放,您可以获得 select 值,这甚至无法通过正常交互 select 实现(点击打开 + 点击-select)
  2. 作为#1 的结果,它也很容易使 Espresso 测试失败。事实上,在 Espresso 中,点击的持续时间相当不稳定,一次点击可以很快变成长按和 select 交互。
  3. 最后,这里最大的问题是没有方法/XML 属性来禁用 "drag to open" 行为...

让我们修复它!

我在 AOSP 问题跟踪器上开了一张与此相关的工单:#228953。欢迎关注,如有遗漏欢迎评论。

有点晚了,但可能对某人仍然有用:

onLongClick 中设置 returns trueOnLongClickListener 可防止(无意中)打开 Spinner,因为它就像回调一样工作如 documentation.

中所述消耗长按
spinner.setOnLongClickListener(new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View view) {
        return true;
    }
});

唯一的缺点是,微调器在长按时仍然会有 onTouch 动画。