From fb4cbc4a06b1ff6064c831cee8a0cded78df8442 Mon Sep 17 00:00:00 2001 From: Alan Viverette Date: Wed, 20 Jan 2016 11:26:06 -0500 Subject: [PATCH] Postpone hover state resolution in DropDownListView Prevents a HOVER_EXIT => TOUCH_DOWN transition from triggering two Drawable.setState() calls in the same frame. Bug: 25849904 Change-Id: Ib10f127fd68e433992b6b11e62ac2263d790ef52 --- .../java/android/widget/DropDownListView.java | 71 ++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java index 02f7e7a783079..69e42180aa6a1 100644 --- a/core/java/android/widget/DropDownListView.java +++ b/core/java/android/widget/DropDownListView.java @@ -74,6 +74,12 @@ public class DropDownListView extends ListView { /** Helper for drag-to-open auto scrolling. */ private AbsListViewAutoScroller mScrollHelper; + /** + * Runnable posted when we are awaiting hover event resolution. When set, + * drawable state changes are postponed. + */ + private ResolveHoverRunnable mResolveHoverRunnable; + /** * Creates a new list view wrapper. * @@ -100,19 +106,37 @@ public class DropDownListView extends ListView { return isHovered() || super.shouldShowSelector(); } + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mResolveHoverRunnable != null) { + // Resolved hover event as hover => touch transition. + mResolveHoverRunnable.cancel(); + } + + return super.onTouchEvent(ev); + } + @Override public boolean onHoverEvent(@NonNull MotionEvent ev) { + final int action = ev.getActionMasked(); + if (action == MotionEvent.ACTION_HOVER_EXIT && mResolveHoverRunnable == null) { + // This may be transitioning to TOUCH_DOWN. Postpone drawable state + // updates until either the next frame or the next touch event. + mResolveHoverRunnable = new ResolveHoverRunnable(); + mResolveHoverRunnable.post(); + } + // Allow the super class to handle hover state management first. final boolean handled = super.onHoverEvent(ev); - final int action = ev.getActionMasked(); if (action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_MOVE) { final int position = pointToPosition((int) ev.getX(), (int) ev.getY()); if (position != INVALID_POSITION && position != mSelectedPosition) { final View hoveredItem = getChildAt(position - getFirstVisiblePosition()); if (hoveredItem.isEnabled()) { - // Force a focus so that the proper selector state gets used when we update. + // Force a focus so that the proper selector state gets + // used when we update. requestFocus(); positionSelector(position, hoveredItem); @@ -122,7 +146,8 @@ public class DropDownListView extends ListView { updateSelectorState(); } } else { - // Do not cancel the selected position if the selection is visible by other reasons. + // Do not cancel the selected position if the selection is visible + // by other means. if (!super.shouldShowSelector()) { setSelectedPositionInt(INVALID_POSITION); setNextSelectedPositionInt(INVALID_POSITION); @@ -132,6 +157,13 @@ public class DropDownListView extends ListView { return handled; } + @Override + protected void drawableStateChanged() { + if (mResolveHoverRunnable == null) { + super.drawableStateChanged(); + } + } + /** * Handles forwarded events. * @@ -196,12 +228,14 @@ public class DropDownListView extends ListView { } /** - * Sets whether the list selection is hidden, as part of a workaround for a touch mode issue - * (see the declaration for mListSelectionHidden). - * @param listSelectionHidden + * Sets whether the list selection is hidden, as part of a workaround for a + * touch mode issue (see the declaration for mListSelectionHidden). + * + * @param hideListSelection {@code true} to hide list selection, + * {@code false} to show */ - public void setListSelectionHidden(boolean listSelectionHidden) { - this.mListSelectionHidden = listSelectionHidden; + public void setListSelectionHidden(boolean hideListSelection) { + mListSelectionHidden = hideListSelection; } private void clearPressedItem() { @@ -312,4 +346,25 @@ public class DropDownListView extends ListView { public boolean hasFocus() { return mHijackFocus || super.hasFocus(); } + + /** + * Runnable that forces hover event resolution and updates drawable state. + */ + private class ResolveHoverRunnable implements Runnable { + @Override + public void run() { + // Resolved hover event as standard hover exit. + mResolveHoverRunnable = null; + drawableStateChanged(); + } + + public void cancel() { + mResolveHoverRunnable = null; + removeCallbacks(this); + } + + public void post() { + DropDownListView.this.post(this); + } + } } \ No newline at end of file