I created a custom drawable (base on a drawable selector) for a regular Button
and I decided to test my app with both a physical mouse and keyboard.
That's when I noticed that every time I click the button with the physical mouse, it loses the hovered
state for a very brief moment and only after a few milliseconds it gains the pressed
state.
Even though this happens only for a very brief moment, it produces an annoying and very noticeable "blink".
I do not know if I made myself clear, so I will reproduce the steps:
1 - Button is idle, and the mouse pointer is not over the button: button renders ok with the default state
2 - I move the mouse cursor over the button and I leave the mouse still: button renders with the hovered state
3 - I press the mouse's left button: button renders with the default state (not hovered) for a brief moment and then renders with pressed state, producing a visual blink
That weird behavior can be easily reproduced/verified by replacing the button with a custom Button
class overriding drawableStateChanged():
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
final int[] states = getDrawableState();
StringBuilder builder = new StringBuilder("@@@ ");
if (states != null) {
for (int i = states.length - 1; i >= 0; i--) {
switch (states[i]) {
case android.R.attr.state_pressed:
builder.append("p");
break;
case android.R.attr.state_focused:
builder.append("f");
break;
case android.R.attr.state_hovered:
builder.append("h");
break;
}
}
}
System.out.println(builder.toString());
}
Logcat shows:
@@@ [the button is in the default state]
@@@ h [the button is in the hovered state]
[I physically click the left mouse button]
@@@ [the button goes back to the default state even though it *is* hovered and pressed]
@@@ p [after a visual blink, the button goes to its pressed state]
...
I tried almost everything: overriding onHoverChanged()
, setHovered()
and so on... I have even tried to call isHovered()
within drawableStateChanged()
to see if there is a difference... Nothing!!!! Android actively changes both the drawable state and the hovered
attribute to false before setting the pressed state. That's on purpose!
I looked everywhere on the web, and even here at SO. Nothing! It's like no one has noticed it yet, or I must be forgetting something really simple...
FYI: I am not using any libraries (like AppCompat etc etc etc). It is a plain button, placed in a plain Activity. Tested on a Samsung A5 2017 (with USB mouse) and on an Asus Chromebook Flip C100 (using the track pad).
FYI 2: The style in effect is v21/styles.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<style name="AppBaseTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
<item name="android:colorPrimary">#ff3344bb</item>
<item name="android:colorAccent">#ff3344bb</item>
<item name="android:colorControlNormal">#ff6d6d6d</item>
</style>
</resources>
FYI 3: I also tried to override onGenericMotionEvent()
. Again, as soon as I click the button logcat shows @@@ HEXIT
. Soon after that, logcat shows @@@ HENTER
... but I never even moved the mouse cursor by one pixel... let alone moving it outside the button!
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_HOVER_ENTER:
System.out.println("@@@ HENTER");
break;
case MotionEvent.ACTION_HOVER_EXIT:
System.out.println("@@@ HEXIT");
break;
}
return super.onGenericMotionEvent(event);
}
How can I get rid of that behavior? Does anyone know a flag or a hack to disable this blink?
Important: I do not want to use the only workaround I came up with so far: create private flags to manually control the hovered state, ignoring Android's hovered state and using timers/scheduled delays/etc.