diff options
| author | 2016-03-11 17:03:56 +0000 | |
|---|---|---|
| committer | 2016-03-11 17:03:58 +0000 | |
| commit | e11936725e6227c98f356572cbfd512f16b98c3b (patch) | |
| tree | 5607d1e90ec1e5de5d91d00ad551546e1c8bc51d | |
| parent | 18fafa4be7de36cb319afe2d0faf258b24c39d1c (diff) | |
| parent | 273ed107f1d414571a01bf8ebd9c9ffa942d9e11 (diff) | |
Merge "Improve interaction around showing / hiding the gear behind notifications" into nyc-dev
3 files changed, 258 insertions, 92 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 1fe218a885fb..81ba23f27f23 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -517,35 +517,16 @@ public class SwipeHelper implements Gefingerpoken { break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - if (mCurrView != null) { - float maxVelocity = MAX_DISMISS_VELOCITY * mDensityScale; - mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity); - float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale; - float velocity = getVelocity(mVelocityTracker); - float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker); - - float translation = getTranslation(mCurrView); - // Decide whether to dismiss the current view - boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH && - Math.abs(translation) > 0.4 * getSize(mCurrView); - boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) && - (Math.abs(velocity) > Math.abs(perpendicularVelocity)) && - (velocity > 0) == (translation > 0); - boolean falsingDetected = mCallback.isAntiFalsingNeeded(); - - if (mFalsingManager.isClassiferEnabled()) { - falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(); - } else { - falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold; - } - - boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) - && !falsingDetected && (childSwipedFastEnough || childSwipedFarEnough) - && ev.getActionMasked() == MotionEvent.ACTION_UP; + if (mCurrView == null) { + break; + } + mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity()); + float velocity = getVelocity(mVelocityTracker); - if (dismissChild) { + if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) { + if (isDismissGesture(ev)) { // flingadingy - dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f); + dismissChild(mCurrView, swipedFastEnough() ? velocity : 0f); } else { // snappity mCallback.onDragCancelled(mCurrView); @@ -562,6 +543,46 @@ public class SwipeHelper implements Gefingerpoken { return (int) (mFalsingThreshold * factor); } + private float getMaxVelocity() { + return MAX_DISMISS_VELOCITY * mDensityScale; + } + + protected float getEscapeVelocity() { + return SWIPE_ESCAPE_VELOCITY * mDensityScale; + } + + protected boolean swipedFarEnough() { + float translation = getTranslation(mCurrView); + return DISMISS_IF_SWIPED_FAR_ENOUGH && Math.abs(translation) > 0.4 * getSize(mCurrView); + } + + protected boolean isDismissGesture(MotionEvent ev) { + boolean falsingDetected = mCallback.isAntiFalsingNeeded(); + if (mFalsingManager.isClassiferEnabled()) { + falsingDetected = falsingDetected && mFalsingManager.isFalseTouch(); + } else { + falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold; + } + return !falsingDetected && (swipedFastEnough() || swipedFarEnough()) + && ev.getActionMasked() == MotionEvent.ACTION_UP + && mCallback.canChildBeDismissed(mCurrView); + } + + protected boolean swipedFastEnough() { + float velocity = getVelocity(mVelocityTracker); + float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker); + float translation = getTranslation(mCurrView); + boolean ret = (Math.abs(velocity) > getEscapeVelocity()) && + (Math.abs(velocity) > Math.abs(perpendicularVelocity)) && + (velocity > 0) == (translation > 0); + return ret; + } + + protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity, + float translation) { + return false; + } + public interface Callback { View getChildAtPosition(MotionEvent ev); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java index 375459f0f30c..fcc48bf8d510 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSettingsIconRow.java @@ -29,11 +29,18 @@ import com.android.systemui.R; public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener { + private static final int GEAR_ALPHA_ANIM_DURATION = 200; + public interface SettingsIconRowListener { /** * Called when the gear behind a notification is touched. */ public void onGearTouched(ExpandableNotificationRow row, int x, int y); + + /** + * Called when a notification is slid back over the gear. + */ + public void onSettingsIconRowReset(NotificationSettingsIconRow row); } private ExpandableNotificationRow mParent; @@ -45,6 +52,8 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC private boolean mSettingsFadedIn = false; private boolean mAnimating = false; private boolean mOnLeft = true; + private boolean mDismissing = false; + private boolean mSnapping = false; private int[] mGearLocation = new int[2]; private int[] mParentLocation = new int[2]; @@ -78,8 +87,14 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC public void resetState() { setGearAlpha(0f); + mSettingsFadedIn = false; mAnimating = false; + mSnapping = false; + mDismissing = false; setIconLocation(true /* on left */); + if (mListener != null) { + mListener.onSettingsIconRowReset(this); + } } public void setGearListener(SettingsIconRowListener listener) { @@ -94,20 +109,24 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC return mParent; } - private void setGearAlpha(float alpha) { + public void setGearAlpha(float alpha) { if (alpha == 0) { mSettingsFadedIn = false; // Can fade in again once it's gone. setVisibility(View.INVISIBLE); } else { - if (alpha == 1) { - mSettingsFadedIn = true; - } setVisibility(View.VISIBLE); } mGearIcon.setAlpha(alpha); } /** + * Returns whether the icon is on the left side of the view or not. + */ + public boolean isIconOnLeft() { + return mOnLeft; + } + + /** * Returns the horizontal space in pixels required to display the gear behind a notification. */ public float getSpaceForGear() { @@ -119,7 +138,7 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC * if entire view is visible. */ public boolean isVisible() { - return mSettingsFadedIn; + return mGearIcon.getAlpha() > 0; } public void cancelFadeAnimator() { @@ -129,16 +148,18 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC } public void updateSettingsIcons(final float transX, final float size) { - if (mAnimating || (mGearIcon.getAlpha() == 0)) { - // Don't adjust when animating or settings aren't visible + if (mAnimating || !mSettingsFadedIn) { + // Don't adjust when animating, or if the gear hasn't been shown yet. return; } - setIconLocation(transX > 0 /* fromLeft */); + final float fadeThreshold = size * 0.3f; final float absTrans = Math.abs(transX); float desiredAlpha = 0; - if (absTrans <= fadeThreshold) { + if (absTrans == 0) { + desiredAlpha = 0; + } else if (absTrans <= fadeThreshold) { desiredAlpha = 1; } else { desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold)); @@ -148,6 +169,12 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC public void fadeInSettings(final boolean fromLeft, final float transX, final float notiThreshold) { + if (mDismissing || mAnimating) { + return; + } + if (isIconLocationChange(transX)) { + setGearAlpha(0f); + } setIconLocation(transX > 0 /* fromLeft */); mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1); mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @@ -164,40 +191,53 @@ public class NotificationSettingsIconRow extends FrameLayout implements View.OnC }); mFadeAnimator.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationCancel(Animator animation) { - super.onAnimationCancel(animation); - mAnimating = false; - mSettingsFadedIn = false; + public void onAnimationStart(Animator animation) { + mAnimating = true; } @Override - public void onAnimationStart(Animator animation) { - super.onAnimationStart(animation); - mAnimating = true; + public void onAnimationCancel(Animator animation) { + // TODO should animate back to 0f from current alpha + mGearIcon.setAlpha(0f); } @Override public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); mAnimating = false; - mSettingsFadedIn = true; + mSettingsFadedIn = mGearIcon.getAlpha() == 1; } }); mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN); - mFadeAnimator.setDuration(200); + mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION); mFadeAnimator.start(); } - private void setIconLocation(boolean onLeft) { - if (onLeft == mOnLeft) { + public void setIconLocation(boolean onLeft) { + if (onLeft == mOnLeft || mSnapping) { // Same side? Do nothing. return; } - setTranslationX(onLeft ? 0 : (mParent.getWidth() - mHorizSpaceForGear)); mOnLeft = onLeft; } + public boolean isIconLocationChange(float translation) { + boolean onLeft = translation > mGearIcon.getPaddingStart(); + boolean onRight = translation < -mGearIcon.getPaddingStart(); + if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) { + return true; + } + return false; + } + + public void setDismissing() { + mDismissing = true; + } + + public void setSnapping(boolean snapping) { + mSnapping = snapping; + } + @Override public void onClick(View v) { if (v.getId() == R.id.gear_icon) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 2da47874b49b..4cb0dea118ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -372,6 +372,11 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override + public void onSettingsIconRowReset(NotificationSettingsIconRow row) { + mSwipeHelper.setSnappedToGear(false); + } + + @Override protected void onDraw(Canvas canvas) { canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint); if (DEBUG) { @@ -717,11 +722,15 @@ public class NotificationStackScrollLayout extends ViewGroup mDragAnimPendingChildren.remove(animView); } - if (targetLeft == 0 && mCurrIconRow != null) { - mCurrIconRow.resetState(); - mCurrIconRow = null; - if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) { - mGearExposedView = null; + if (mCurrIconRow != null) { + if (targetLeft == 0) { + mCurrIconRow.resetState(); + mCurrIconRow = null; + if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) { + mGearExposedView = null; + } + } else { + mSwipeHelper.setSnappedToGear(true); } } } @@ -3379,15 +3388,11 @@ public class NotificationStackScrollLayout extends ViewGroup } private class NotificationSwipeHelper extends SwipeHelper { - private static final int MOVE_STATE_LEFT = -1; - private static final int MOVE_STATE_UNDEFINED = 0; - private static final int MOVE_STATE_RIGHT = 1; - private static final long GEAR_SHOW_DELAY = 60; - private CheckForDrag mCheckForDrag; private Handler mHandler; - private int mMoveState = MOVE_STATE_UNDEFINED; + private boolean mGearSnappedTo; + private boolean mGearSnappedOnLeft; public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) { super(swipeDirection, callback, context); @@ -3400,6 +3405,10 @@ public class NotificationStackScrollLayout extends ViewGroup mTranslatingParentView = currView; // Reset check for drag gesture + cancelCheckForDrag(); + if (mCurrIconRow != null) { + mCurrIconRow.setSnapping(false); + } mCheckForDrag = null; mCurrIconRow = null; @@ -3411,17 +3420,32 @@ public class NotificationStackScrollLayout extends ViewGroup mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow(); mCurrIconRow.setGearListener(NotificationStackScrollLayout.this); } - mMoveState = MOVE_STATE_UNDEFINED; } @Override public void onMoveUpdate(View view, float translation, float delta) { - final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT; - if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) { - // Changed directions, make sure we check for drag again. - mCheckForDrag = null; + if (mCurrIconRow != null) { + mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping. + + // If the gear is visible and the movement is towards it it's not a location change. + boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft(); + boolean locationChange = isTowardsGear(translation, onLeft) + ? false : mCurrIconRow.isIconLocationChange(translation); + if (locationChange) { + // Don't consider it "snapped" if location has changed. + setSnappedToGear(false); + + // Changed directions, make sure we check to fade in icon again. + if (!mHandler.hasCallbacks(mCheckForDrag)) { + // No check scheduled, set null to schedule a new one. + mCheckForDrag = null; + } else { + // Check scheduled, reset alpha and update location; check will fade it in + mCurrIconRow.setGearAlpha(0f); + mCurrIconRow.setIconLocation(translation > 0 /* onLeft */); + } + } } - mMoveState = newMoveState; final boolean gutsExposed = (view instanceof ExpandableNotificationRow) && ((ExpandableNotificationRow) view).areGutsExposed(); @@ -3434,35 +3458,99 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void dismissChild(final View view, float velocity) { - cancelCheckForDrag(); super.dismissChild(view, velocity); + cancelCheckForDrag(); + setSnappedToGear(false); } @Override public void snapChild(final View animView, final float targetLeft, float velocity) { + super.snapChild(animView, targetLeft, velocity); + if (targetLeft == 0) { + cancelCheckForDrag(); + setSnappedToGear(false); + } + } + + + @Override + public boolean handleUpEvent(MotionEvent ev, View animView, float velocity, + float translation) { + if (mCurrIconRow == null) { + cancelCheckForDrag(); + return false; // Let SwipeHelper handle it. + } + + boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft()); + boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity(); + + if (mGearSnappedTo && mCurrIconRow.isVisible()) { + if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) { + boolean coveringGear = + Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f; + if (gestureTowardsGear || coveringGear) { + // Gesture is towards or covering the gear + snapChild(animView, 0 /* leftTarget */, velocity); + } else if (isDismissGesture(ev)) { + // Gesture is a dismiss that's not towards the gear + dismissChild(animView, swipedFastEnough() ? velocity : 0f); + } else { + // Didn't move enough to dismiss or cover, snap to the gear + snapToGear(animView, velocity); + } + } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView)) + || (gestureTowardsGear && !swipedFarEnough())) { + // The gear has been snapped to previously, however, the gear is now on the + // other side. If gesture is towards gear and not too far snap to the gear. + snapToGear(animView, velocity); + } else { + dismissOrSnapBack(animView, velocity, ev); + } + } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView)) + || gestureTowardsGear) { + // Gear has not been snapped to previously and this is gear revealing gesture + snapToGear(animView, velocity); + } else { + dismissOrSnapBack(animView, velocity, ev); + } + return true; + } + + private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) { + if (isDismissGesture(ev)) { + dismissChild(animView, swipedFastEnough() ? velocity : 0f); + } else { + snapChild(animView, 0 /* leftTarget */, velocity); + } + } + + private void snapToGear(View animView, float velocity) { + final float snapBackThreshold = getSpaceForGear(animView); + final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold + : -snapBackThreshold; + mGearExposedView = mTranslatingParentView; + if (mGearDisplayedListener != null + && (animView instanceof ExpandableNotificationRow)) { + mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView); + } + if (mCurrIconRow != null) { + mCurrIconRow.setSnapping(true); + setSnappedToGear(true); + } + super.snapChild(animView, target, velocity); + } + + private boolean swipedEnoughToShowGear(View animView) { final float snapBackThreshold = getSpaceForGear(animView); final float translation = getTranslation(animView); final boolean fromLeft = translation > 0; final float absTrans = Math.abs(translation); final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; - boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f - && translation <= notiThreshold) || - (!fromLeft && absTrans >= snapBackThreshold * 0.4f - && absTrans <= notiThreshold); - - if (pastGear && !isPinnedHeadsUp(animView) - && (animView instanceof ExpandableNotificationRow)) { - // bouncity - final float target = fromLeft ? snapBackThreshold : -snapBackThreshold; - mGearExposedView = mTranslatingParentView; - if (mGearDisplayedListener != null) { - mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView); - } - super.snapChild(animView, target, velocity); - } else { - super.snapChild(animView, 0, velocity); - } + // If the notification can't be dismissed then how far it can move is + // restricted -- reduce the distance it needs to move in this case. + final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f; + return absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold; } @Override @@ -3499,6 +3587,25 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * Returns whether the gesture is towards the gear location or not. + */ + private boolean isTowardsGear(float velocity, boolean onLeft) { + if (mCurrIconRow == null) { + return false; + } + return mCurrIconRow.isVisible() + && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0)); + } + + /** + * Indicates the the gear has been snapped to. + */ + private void setSnappedToGear(boolean snapped) { + mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false; + mGearSnappedTo = snapped && mCurrIconRow != null; + } + + /** * Returns the horizontal space in pixels required to display the gear behind a * notification. */ @@ -3510,7 +3617,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void checkForDrag() { - if (mCheckForDrag == null) { + if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) { mCheckForDrag = new CheckForDrag(); mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY); } @@ -3521,7 +3628,6 @@ public class NotificationStackScrollLayout extends ViewGroup mCurrIconRow.cancelFadeAnimator(); } mHandler.removeCallbacks(mCheckForDrag); - mCheckForDrag = null; } private final class CheckForDrag implements Runnable { @@ -3531,14 +3637,13 @@ public class NotificationStackScrollLayout extends ViewGroup final float absTransX = Math.abs(translation); final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView); final float notiThreshold = getSize(mTranslatingParentView) * 0.4f; - if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4 + if ((mCurrIconRow != null && (!mCurrIconRow.isVisible() + || mCurrIconRow.isIconLocationChange(translation))) + && absTransX >= bounceBackToGearWidth * 0.4 && absTransX < notiThreshold) { - // Show icon + // Fade in the gear mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation, notiThreshold); - } else { - // Allow more to be posted if this wasn't a drag. - mCheckForDrag = null; } } } @@ -3551,7 +3656,7 @@ public class NotificationStackScrollLayout extends ViewGroup final View prevGearExposedView = mGearExposedView; mGearExposedView = null; - + mGearSnappedTo = false; Animator anim = getViewTranslationAnimator(prevGearExposedView, 0 /* leftTarget */, null /* updateListener */); if (anim != null) { |