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


10

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

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)父视图

有人也可以确认吗?


您实际要做的是通过触摸处理还是只需按一下即可更改颜色?
Haider Saleem

我想运行一些逻辑,然后调用performClick,并且不要更改按钮的颜色。
Whitebear19年

@Whitebear请检查以下答案。也许我可以添加更多信息。
GensaGames

可能有助于您了解触摸事件的流程。目前尚不清楚您想发生什么。您是否需要点击处理程序并执行点击?您是否希望按钮从其初始颜色变为彩色滤光片设置的状态,然后再返回其初始颜色?
Cheticamp

让我解释一下我的意思。我在按钮上有一个touchListener和一个clickListener。触摸优先于单击之前,如果处理事件,则返回true,这意味着没有其他人可以处理它。这正是我通过触摸,处理单击并返回true所做的事情,但是即使调用了onclick侦听器并且流程正确完成,按钮仍然保持突出显示状态。
Whitebear19年

Answers:


10

这样的自定义不需要编程修改。您可以简单地在xml文件中进行操作。首先,完全删除setOnTouchListener您提供的方法onCreate。接下来,在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" />


视觉结果:

在此处输入图片说明



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

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

在此处输入图片说明

当触摸事件到达目标视图,该视图可以处理随后事件决定将它传递给现有的布局/活性或不(返回falsetrueonTouch法)。(上图中的RTL流程)

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

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

现在,查看onTouchEvent事件操作为的方法MotionEvent.ACTION_UP。我们看到执行点击操作就在那里发生。所以,回到trueOnTouchListeneronTouch,因此没有要求的onTouchEvent,将导致不调用OnClickListeneronClick

还有一个不调用的问题onTouchEvent,它与按下状态和您在问题中提到的有关。正如我们在下面的代码块中看到的那样,它在运行时有一个UnsetPressedState调用实例。不调用的结果是视图卡在了按下状态,并且其可绘制状态不变。 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;
}

UnsetPressedState

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


关于以上描述,您可以通过调用setPressed(false)自己来更改事件动作为的可绘制状态来更改代码MotionEvent.ACTION_UP

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.setPressed(false);
                v.performClick();
                Log.d("Test", "Performing click");
                return true;
            }
        }
        return false;
    }
});

这不是我要找的朋友。我希望了解上述两种情况下行为的变化。如果有什么我想说的,请告诉我。我不想删除触摸处理程序或单击处理程序。请查看我对以上答案的评论。
Whitebear

@Whitebear:我已经更新了答案。老兄,请检查一下。
Aminography '19

这是一个很好的详细答案,我会接受的。更改的一些指示: Now, look at the onTouchEvent method where the event action is MotionEvent.ACTION_UP. We see that perform-click action happens there. So, returning true in the OnTouchListener's onTouch and consequently not calling the onTouchEvent, causes not calling the OnClickListener's onClick.在我的情况下,调用onClick。mUnsetPressedState会在设置为false之前检查它是否为null,并且如果我们被预压,则runnable不能确定是否运行。我不太了解您如何推断应该将其设置为false
Whitebear

onClick之所以称为,是因为您在打电话v.performClick();。请MotionEvent.ACTION_UP再次检查以上代码,setPressed(false)无论如何都将被调用,是否mUnsetPressedState为null,是否prepressed为true。区别setPressed(false)在于可以通过post/ postDelayed或直接调用的方式。
Aminography '19

2

你在搞乱touchfocus事件。让我们从了解相同颜色的行为开始。默认情况下,在Android Selector中将其分配为背景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和state pressed。通过设置,onTouchListener您将处理与无关的触摸事件focus

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

更新

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

<?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>

您如何将第一个示例更改为正常工作?
Whitebear

我不想从我的实现中删除触摸侦听器。因为我想使用触摸处理程序来捕获特定视图上的单击,然后自己处理(使用performClick)并从触摸侦听器返回true,以便告诉其他处理程序没有进一步的处理。在我的示例中,日志打印得很好,但是按钮仍然保持突出显示状态。
Whitebear

@Whitebear在您的问题中您没有提到。无论如何,您都可以根据需要使用任意多个onTouchListener。您只是不需要消耗事件,通过return true
GensaGames

@Whitebear 删除选择器,然后通过设置按钮的原色backgroundColor
GensaGames

家伙,你仍然没有解决我在原来的职位写..
Whitebear

0

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

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

并将其设置为按钮

android:background="@color/myColor"

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.