Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e14579ba authored by Adam Powell's avatar Adam Powell
Browse files

Tap delays; making tap UI feel better.

Addresses bug 2317577

Views now will not enter PRESSED state until
ViewConfiguration.getTapTimeout() milliseconds have elapsed. This
prevents scrolls and other gestures from causing views to enter
PRESSED state prematurely.

Quick taps within the same view still work as expected. There is now a
PREPRESSED state flag within View to help track quick taps.

If tapped quickly, views will remain pressed for
ViewConfiguration.getPressedStateDuration().

Tap timeout has been changed to 115ms. Pressed state duration has been
changed to 125ms.
parent 91373073
Loading
Loading
Loading
Loading
+60 −18
Original line number Diff line number Diff line
@@ -1495,6 +1495,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
     */
    static final int OPAQUE_MASK                    = 0x01800000;
    
    /**
     * Indicates a prepressed state;
     * the short time between ACTION_DOWN and recognizing
     * a 'real' press. Prepressed is used to recognize quick taps
     * even when they are shorter than ViewConfiguration.getTapTimeout().
     * 
     * @hide
     */
    private static final int PREPRESSED             = 0x02000000;

    /**
     * The parent this view is attached to.
     * {@hide}
@@ -1722,6 +1732,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
    private int mNextFocusDownId = View.NO_ID;

    private CheckForLongPress mPendingCheckForLongPress;
    private CheckForTap mPendingCheckForTap = null;
    
    private UnsetPressedState mUnsetPressedState;

    /**
@@ -1763,6 +1775,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
     */
    private ViewTreeObserver mFloatingTreeObserver;
    
    /**
     * Cache the touch slop from the context that created the view.
     */
    private int mTouchSlop;

    // Used for debug only
    static long sInstanceCount = 0;

@@ -1777,6 +1794,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
        mResources = context != null ? context.getResources() : null;
        mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
        ++sInstanceCount;
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    /**
@@ -3951,7 +3969,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
                        (event.getRepeatCount() == 0)) {
                    setPressed(true);
                    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
                        postCheckForLongClick();
                        postCheckForLongClick(0);
                    }
                    return true;
                }
@@ -4174,7 +4192,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    if ((mPrivateFlags & PRESSED) != 0) {
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
@@ -4196,24 +4215,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (!post(mUnsetPressedState)) {
                        if (prepressed) {
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }
                        removeTapCallback();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mPrivateFlags |= PRESSED;
                    refreshDrawableState();
                    if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
                        postCheckForLongClick();
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPrivateFlags |= PREPRESSED;
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    break;

                case MotionEvent.ACTION_CANCEL:
                    mPrivateFlags &= ~PRESSED;
                    refreshDrawableState();
                    removeTapCallback();
                    break;

                case MotionEvent.ACTION_MOVE:
@@ -4221,25 +4247,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
                    final int y = (int) event.getY();

                    // Be lenient about moving outside of buttons
                    int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
                    int slop = mTouchSlop;
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                            (y < 0 - slop) || (y >= getHeight() + slop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PRESSED) != 0) {
                            // Remove any future long press checks
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            // Need to switch from pressed to not pressed
                            mPrivateFlags &= ~PRESSED;
                            refreshDrawableState();
                        }
                    } else {
                        // Inside button
                        if ((mPrivateFlags & PRESSED) == 0) {
                            // Need to switch from not pressed to pressed
                            mPrivateFlags |= PRESSED;
                            refreshDrawableState();
                        }
                    }
                    break;
            }
@@ -4258,6 +4278,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
        }
    }
    
    /**
     * Remove the tap detection timer.
     */
    private void removeTapCallback() {
        if (mPendingCheckForTap != null) {
            mPrivateFlags &= ~PREPRESSED;
            removeCallbacks(mPendingCheckForTap);
        }
    }

    /**
     * Cancels a pending long press.  Your subclass can use this if you
     * want the context menu to come up if the user presses and holds
@@ -8427,14 +8457,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
        }
    }

    private void postCheckForLongClick() {
    private void postCheckForLongClick(int delayOffset) {
        mHasPerformedLongPress = false;

        if (mPendingCheckForLongPress == null) {
            mPendingCheckForLongPress = new CheckForLongPress();
        }
        mPendingCheckForLongPress.rememberWindowAttachCount();
        postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout());
        postDelayed(mPendingCheckForLongPress,
                ViewConfiguration.getLongPressTimeout() - delayOffset);
    }

    private static int[] stateSetUnion(final int[] stateSet1,
@@ -8612,6 +8643,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
        }
    }
    
    private final class CheckForTap implements Runnable {
        public void run() {
            mPrivateFlags &= ~PREPRESSED;
            mPrivateFlags |= PRESSED;
            refreshDrawableState();
            if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
                postCheckForLongClick(ViewConfiguration.getTapTimeout());
            }
        }
    }

    /**
     * Interface definition for a callback to be invoked when a key event is
     * dispatched to this view. The callback will be invoked before the key
+2 −2
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ public class ViewConfiguration {
     * Defines the duration in milliseconds of the pressed state in child
     * components.
     */
    private static final int PRESSED_STATE_DURATION = 85;
    private static final int PRESSED_STATE_DURATION = 125;
    
    /**
     * Defines the duration in milliseconds before a press turns into
@@ -69,7 +69,7 @@ public class ViewConfiguration {
     * is a tap or a scroll. If the user does not move within this interval, it is
     * considered to be a tap. 
     */
    private static final int TAP_TIMEOUT = 100;
    private static final int TAP_TIMEOUT = 115;
    
    /**
     * Defines the duration in milliseconds we will wait to see if a touch event