Такие настройки не требуют программных изменений. Вы можете сделать это просто в 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](https://i.stack.imgur.com/xVpoy.gif)
РЕДАКТИРОВАНИЕ: (для решения проблемы сенсорного события)
С общей точки зрения поток сенсорного события начинается с Activity
, а затем направляется вниз к макету (изродительский для дочерних макетов), а затем к представлениям. (Поток LTR на следующем рисунке)
![enter image description here](https://i.stack.imgur.com/Id2Xz.png)
Когда событие касания достигает целевого представления, представление может обработать событие, а затем принять решение передать егопредыдущие макеты / действия или нет (возвращая false
из true
в onTouch
метод). (RTL-поток на рисунке выше)
Теперь давайте взглянем на исходный код View , чтобы получить более глубокое понимание потоков сенсорных событий. Посмотрев на реализацию dispatchTouchEvent
, мы увидим, что если вы установите OnTouchListener
для представления, а затем вернете true
в его методе onTouch
, 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
. Мы видим, что там происходит действие «выполнить щелчок». Таким образом, возвращая true
в OnTouchListener
onTouch
и, следовательно, не вызывая onTouchEvent
, вы не вызываете OnClickListener
* onClick
.
. вызов 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;
}
});