使用touchListener和clickListener处于突出显示状态的左侧按钮

时间:2019-11-10 14:50:10

标签: android onclicklistener android-button ontouchlistener

执行以下操作后,我的Button保持在突出显示状态时出现问题:

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        v.performClick();
                        Log.d("Test", "Performing click");
                        return true;
                    }
                }
                return false;
            }
        });

    }
}

关于上面的代码,在使用它时,我希望按钮单击由触摸来处理,并且通过返回“ true”,处理应在touchListener处停止。

但事实并非如此。即使单击了单击,按钮仍保持突出显示状态。

我得到的是:

Test - calling onClick
Test - Performing click

另一方面,如果我使用以下代码,则单击该按钮,并打印相同的内容,但该按钮最终不会停留在突出显示状态:

public class MainActivity extends AppCompatActivity {

    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AppCompatButton button = (AppCompatButton) findViewById(R.id.mybutton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("Test", "calling onClick");
            }
        });
        button.setOnTouchListener(new View.OnTouchListener() {

            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN: {
                        v.getBackground().setColorFilter(0xe0f47521,PorterDuff.Mode.SRC_ATOP);
                        v.invalidate();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        v.getBackground().clearColorFilter();
                        v.invalidate();
                        // v.performClick();
                        Log.d("Test", "Performing click");
                        return false;
                    }
                }
                return false;
            }
        });

    }
}

对于触摸事件的响应者链是什么,我有点困惑。我的猜测是:

1)TouchListener

2)ClickListener

3)ParentViews

有人也可以确认吗?

4 个答案:

答案 0 :(得分:10)

此类自定义无需进行程序修改。您可以简单地在xml文件中进行操作。首先,完全删除您在setOnTouchListener中提供的onCreate方法。接下来,在res/color目录中定义选择器颜色,如下所示。 (如果目录不存在,请创建它)

res / color / button_tint_color.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="#e0f47521" android:state_pressed="true" />
    <item android:color="?attr/colorButtonNormal" android:state_pressed="false" />
</selector>

现在,将其设置为按钮的app:backgroundTint属性:

<androidx.appcompat.widget.AppCompatButton
    android:id="@+id/mybutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    app:backgroundTint="@color/button_tint_color" />


视觉结果:

enter image description here



已编辑:(以解决触摸事件问题)

从总体上看,触摸事件的流程从Activity开始,然后流到布局(从父布局到子布局),再到视图。 (下图为LTR流程)

enter image description here

当触摸事件到达目标视图时,该视图可以处理该事件,然后决定是否将其传递到先前的布局/活动(返回falsetrue的{​​{1}}方法)。 (上图中的RTL流程)

现在,让我们看一下View的源代码,以更深入地了解触摸事件流。通过查看dispatchTouchEvent的实现,我们可以看到,如果您为视图设置了onTouch,然后在其OnTouchListener方法中返回了true,该视图的onTouchEvent将不会被调用。

onTouch

现在,请查看事件操作为public boolean dispatchTouchEvent(MotionEvent event) { // removed lines for conciseness... boolean result = false; // removed lines for conciseness... if (onFilterTouchEventForSecurity(event)) { // removed lines for conciseness... ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { // <== right here! result = true; } if (!result && onTouchEvent(event)) { result = true; } } // removed lines for conciseness... return result; } 的{​​{3}}方法。我们看到执行点击操作就在那里发生。因此,在MotionEvent.ACTION_UP的{​​{1}}中返回true并因此不调用OnTouchListener,导致不调用onTouch的{​​{1}}。

还有一个不调用onTouchEvent的问题,这与按下状态和您在问题中提到的有关。正如我们在下面的代码块中看到的,有一个onTouchEvent实例在运行时调用UnsetPressedState OnClickListener。不调用onClick的结果是视图卡在了按下状态,其可绘制状态不变。

onTouchEvent

setPressed

(false)


关于以上描述,您可以通过自己调用setPressed(false)来更改代码,以更改事件动作为public boolean onTouchEvent(MotionEvent event) { // removed lines for conciseness... if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: // removed lines for conciseness... if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // removed lines for conciseness... if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // removed lines for conciseness... if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClickInternal(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } // removed lines for conciseness... } // removed lines for conciseness... break; // removed lines for conciseness... } return true; } return false; } 的可绘制状态:

private final class UnsetPressedState implements Runnable {
    @Override
    public void run() {
        setPressed(false);
    }
}

答案 1 :(得分:2)

您正在弄乱touchfocus事件。让我们从了解相同颜色的行为开始。默认情况下,将Selector作为背景分配给Android中的Button。因此,只需更改背景颜色,make就是静态的(颜色不会改变)。但这不是本机行为。

Selector可能看起来像这样。

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_focused="true"
        android:state_pressed="true"
        android:drawable="@drawable/bgalt" />

    <item
        android:state_focused="false"
        android:state_pressed="true"
        android:drawable="@drawable/bgalt" />

    <item android:drawable="@drawable/bgnorm" />
</selector>

如您在上面看到的,有状态focused和状态pressed。通过设置onTouchListener,您将处理与focus无关的触摸事件。

在按钮上的点击事件期间,按钮的

Selector应将focus事件替换为touch。但是在代码的第一部分中,您截获了touch的事件(从回调返回true)。颜色更改无法继续进行,并冻结相同的颜色。这就是为什么第二个变体(无拦截)运行良好的原因,这就是您的困惑。

更新

您要做的就是更改Selector的行为和颜色。对于前。通过为Button使用下一个背景。 AND 完全从您的实施中删除onTouchListener

<?xml version="1.0" encoding="utf-8"?> 
  <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_pressed="true"
        android:drawable="@color/color_pressed" />

    <item android:drawable="@color/color_normal" />
</selector>

答案 2 :(得分:0)

如果为按钮分配背景,则单击时不会改变颜色。

 <color name="myColor">#000000</color>

并将其设置为按钮的背景

android:background="@color/myColor"

答案 3 :(得分:0)

您可以仅使用材料芯片代替按钮视图。 参考:https://material.io/develop/android/components/chip 在那里,他们可以处理那些隐蔽的事件,您可以通过应用主题进行自定义。