diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2017-07-14 00:02:27 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2017-07-20 01:33:10 -0700 |
commit | b72d8b2c8b999f3842dc7b0d93bb1a816b6204b9 (patch) | |
tree | b20bd154d07c5b169dd5745ffd12812b6c17baac /src/com/android/launcher3/notification | |
parent | f567ee8f42cab072f10002302d85e99083122481 (diff) | |
download | android_packages_apps_Trebuchet-b72d8b2c8b999f3842dc7b0d93bb1a816b6204b9.tar.gz android_packages_apps_Trebuchet-b72d8b2c8b999f3842dc7b0d93bb1a816b6204b9.tar.bz2 android_packages_apps_Trebuchet-b72d8b2c8b999f3842dc7b0d93bb1a816b6204b9.zip |
Using common fling detection logic for notification and all-apps
> Refactoring SwipeDetector to both allow vertical and horizontal swipes
> Using SwipeDetector and common overscroll effect for notification swipes
instead of a separate logic
Change-Id: Ib706ee179811ade59ddb68184e1c202365d147c4
Diffstat (limited to 'src/com/android/launcher3/notification')
4 files changed, 67 insertions, 1083 deletions
diff --git a/src/com/android/launcher3/notification/FlingAnimationUtils.java b/src/com/android/launcher3/notification/FlingAnimationUtils.java deleted file mode 100644 index a1f7e49c0..000000000 --- a/src/com/android/launcher3/notification/FlingAnimationUtils.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.notification; - -import android.animation.Animator; -import android.content.Context; -import android.view.ViewPropertyAnimator; -import android.view.animation.Interpolator; -import android.view.animation.PathInterpolator; - -/** - * Utility class to calculate general fling animation when the finger is released. - * - * This class was copied from com.android.systemui.statusbar. - */ -public class FlingAnimationUtils { - - private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f; - private static final float LINEAR_OUT_SLOW_IN_X2_MAX = 0.68f; - private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f; - private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f; - private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f; - private static final float MIN_VELOCITY_DP_PER_SECOND = 250; - private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000; - - private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 0.75f; - private final float mSpeedUpFactor; - private final float mY2; - - private float mMinVelocityPxPerSecond; - private float mMaxLengthSeconds; - private float mHighVelocityPxPerSecond; - private float mLinearOutSlowInX2; - - private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); - private PathInterpolator mInterpolator; - private float mCachedStartGradient = -1; - private float mCachedVelocityFactor = -1; - - public FlingAnimationUtils(Context ctx, float maxLengthSeconds) { - this(ctx, maxLengthSeconds, 0.0f); - } - - /** - * @param maxLengthSeconds the longest duration an animation can become in seconds - * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards - * the end of the animation. 0 means it's at the beginning and no - * acceleration will take place. - */ - public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor) { - this(ctx, maxLengthSeconds, speedUpFactor, -1.0f, 1.0f); - } - - /** - * @param maxLengthSeconds the longest duration an animation can become in seconds - * @param speedUpFactor a factor from 0 to 1 how much the slow down should be shifted towards - * the end of the animation. 0 means it's at the beginning and no - * acceleration will take place. - * @param x2 the x value to take for the second point of the bezier spline. If a value below 0 - * is provided, the value is automatically calculated. - * @param y2 the y value to take for the second point of the bezier spline - */ - public FlingAnimationUtils(Context ctx, float maxLengthSeconds, float speedUpFactor, float x2, - float y2) { - mMaxLengthSeconds = maxLengthSeconds; - mSpeedUpFactor = speedUpFactor; - if (x2 < 0) { - mLinearOutSlowInX2 = interpolate(LINEAR_OUT_SLOW_IN_X2, - LINEAR_OUT_SLOW_IN_X2_MAX, - mSpeedUpFactor); - } else { - mLinearOutSlowInX2 = x2; - } - mY2 = y2; - - mMinVelocityPxPerSecond - = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; - mHighVelocityPxPerSecond - = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density; - } - - private static float interpolate(float start, float end, float amount) { - return start * (1.0f - amount) + end * amount; - } - - /** - * Applies the interpolator and length to the animator, such that the fling animation is - * consistent with the finger motion. - * - * @param animator the animator to apply - * @param currValue the current value - * @param endValue the end value of the animator - * @param velocity the current velocity of the motion - */ - public void apply(Animator animator, float currValue, float endValue, float velocity) { - apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); - } - - /** - * Applies the interpolator and length to the animator, such that the fling animation is - * consistent with the finger motion. - * - * @param animator the animator to apply - * @param currValue the current value - * @param endValue the end value of the animator - * @param velocity the current velocity of the motion - */ - public void apply(ViewPropertyAnimator animator, float currValue, float endValue, - float velocity) { - apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); - } - - /** - * Applies the interpolator and length to the animator, such that the fling animation is - * consistent with the finger motion. - * - * @param animator the animator to apply - * @param currValue the current value - * @param endValue the end value of the animator - * @param velocity the current velocity of the motion - * @param maxDistance the maximum distance for this interaction; the maximum animation length - * gets multiplied by the ratio between the actual distance and this value - */ - public void apply(Animator animator, float currValue, float endValue, float velocity, - float maxDistance) { - AnimatorProperties properties = getProperties(currValue, endValue, velocity, - maxDistance); - animator.setDuration(properties.duration); - animator.setInterpolator(properties.interpolator); - } - - /** - * Applies the interpolator and length to the animator, such that the fling animation is - * consistent with the finger motion. - * - * @param animator the animator to apply - * @param currValue the current value - * @param endValue the end value of the animator - * @param velocity the current velocity of the motion - * @param maxDistance the maximum distance for this interaction; the maximum animation length - * gets multiplied by the ratio between the actual distance and this value - */ - public void apply(ViewPropertyAnimator animator, float currValue, float endValue, - float velocity, float maxDistance) { - AnimatorProperties properties = getProperties(currValue, endValue, velocity, - maxDistance); - animator.setDuration(properties.duration); - animator.setInterpolator(properties.interpolator); - } - - private AnimatorProperties getProperties(float currValue, - float endValue, float velocity, float maxDistance) { - float maxLengthSeconds = (float) (mMaxLengthSeconds - * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); - float diff = Math.abs(endValue - currValue); - float velAbs = Math.abs(velocity); - float velocityFactor = mSpeedUpFactor == 0.0f - ? 1.0f : Math.min(velAbs / HIGH_VELOCITY_DP_PER_SECOND, 1.0f); - float startGradient = interpolate(LINEAR_OUT_SLOW_IN_START_GRADIENT, - mY2 / mLinearOutSlowInX2, velocityFactor); - float durationSeconds = startGradient * diff / velAbs; - Interpolator slowInInterpolator = getInterpolator(startGradient, velocityFactor); - if (durationSeconds <= maxLengthSeconds) { - mAnimatorProperties.interpolator = slowInInterpolator; - } else if (velAbs >= mMinVelocityPxPerSecond) { - - // Cross fade between fast-out-slow-in and linear interpolator with current velocity. - durationSeconds = maxLengthSeconds; - VelocityInterpolator velocityInterpolator - = new VelocityInterpolator(durationSeconds, velAbs, diff); - InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( - velocityInterpolator, slowInInterpolator, Interpolators.LINEAR_OUT_SLOW_IN); - mAnimatorProperties.interpolator = superInterpolator; - } else { - - // Just use a normal interpolator which doesn't take the velocity into account. - durationSeconds = maxLengthSeconds; - mAnimatorProperties.interpolator = Interpolators.FAST_OUT_SLOW_IN; - } - mAnimatorProperties.duration = (long) (durationSeconds * 1000); - return mAnimatorProperties; - } - - private Interpolator getInterpolator(float startGradient, float velocityFactor) { - if (startGradient != mCachedStartGradient - || velocityFactor != mCachedVelocityFactor) { - float speedup = mSpeedUpFactor * (1.0f - velocityFactor); - mInterpolator = new PathInterpolator(speedup, - speedup * startGradient, - mLinearOutSlowInX2, mY2); - mCachedStartGradient = startGradient; - mCachedVelocityFactor = velocityFactor; - } - return mInterpolator; - } - - /** - * Applies the interpolator and length to the animator, such that the fling animation is - * consistent with the finger motion for the case when the animation is making something - * disappear. - * - * @param animator the animator to apply - * @param currValue the current value - * @param endValue the end value of the animator - * @param velocity the current velocity of the motion - * @param maxDistance the maximum distance for this interaction; the maximum animation length - * gets multiplied by the ratio between the actual distance and this value - */ - public void applyDismissing(Animator animator, float currValue, float endValue, - float velocity, float maxDistance) { - AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, - maxDistance); - animator.setDuration(properties.duration); - animator.setInterpolator(properties.interpolator); - } - - /** - * Applies the interpolator and length to the animator, such that the fling animation is - * consistent with the finger motion for the case when the animation is making something - * disappear. - * - * @param animator the animator to apply - * @param currValue the current value - * @param endValue the end value of the animator - * @param velocity the current velocity of the motion - * @param maxDistance the maximum distance for this interaction; the maximum animation length - * gets multiplied by the ratio between the actual distance and this value - */ - public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, - float velocity, float maxDistance) { - AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, - maxDistance); - animator.setDuration(properties.duration); - animator.setInterpolator(properties.interpolator); - } - - private AnimatorProperties getDismissingProperties(float currValue, float endValue, - float velocity, float maxDistance) { - float maxLengthSeconds = (float) (mMaxLengthSeconds - * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f)); - float diff = Math.abs(endValue - currValue); - float velAbs = Math.abs(velocity); - float y2 = calculateLinearOutFasterInY2(velAbs); - - float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2; - Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2); - float durationSeconds = startGradient * diff / velAbs; - if (durationSeconds <= maxLengthSeconds) { - mAnimatorProperties.interpolator = mLinearOutFasterIn; - } else if (velAbs >= mMinVelocityPxPerSecond) { - - // Cross fade between linear-out-faster-in and linear interpolator with current - // velocity. - durationSeconds = maxLengthSeconds; - VelocityInterpolator velocityInterpolator - = new VelocityInterpolator(durationSeconds, velAbs, diff); - InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( - velocityInterpolator, mLinearOutFasterIn, Interpolators.LINEAR_OUT_SLOW_IN); - mAnimatorProperties.interpolator = superInterpolator; - } else { - - // Just use a normal interpolator which doesn't take the velocity into account. - durationSeconds = maxLengthSeconds; - mAnimatorProperties.interpolator = Interpolators.FAST_OUT_LINEAR_IN; - } - mAnimatorProperties.duration = (long) (durationSeconds * 1000); - return mAnimatorProperties; - } - - /** - * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the - * velocity. The faster the velocity, the more "linear" the interpolator gets. - * - * @param velocity the velocity of the gesture. - * @return the y2 control point for a cubic bezier path interpolator - */ - private float calculateLinearOutFasterInY2(float velocity) { - float t = (velocity - mMinVelocityPxPerSecond) - / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond); - t = Math.max(0, Math.min(1, t)); - return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX; - } - - /** - * @return the minimum velocity a gesture needs to have to be considered a fling - */ - public float getMinVelocityPxPerSecond() { - return mMinVelocityPxPerSecond; - } - - /** - * An interpolator which interpolates two interpolators with an interpolator. - */ - private static final class InterpolatorInterpolator implements Interpolator { - - private Interpolator mInterpolator1; - private Interpolator mInterpolator2; - private Interpolator mCrossfader; - - InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2, - Interpolator crossfader) { - mInterpolator1 = interpolator1; - mInterpolator2 = interpolator2; - mCrossfader = crossfader; - } - - @Override - public float getInterpolation(float input) { - float t = mCrossfader.getInterpolation(input); - return (1 - t) * mInterpolator1.getInterpolation(input) - + t * mInterpolator2.getInterpolation(input); - } - } - - /** - * An interpolator which interpolates with a fixed velocity. - */ - private static final class VelocityInterpolator implements Interpolator { - - private float mDurationSeconds; - private float mVelocity; - private float mDiff; - - private VelocityInterpolator(float durationSeconds, float velocity, float diff) { - mDurationSeconds = durationSeconds; - mVelocity = velocity; - mDiff = diff; - } - - @Override - public float getInterpolation(float input) { - float time = input * mDurationSeconds; - return time * mVelocity / mDiff; - } - } - - private static class AnimatorProperties { - Interpolator interpolator; - long duration; - } - -} diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java index 11f6aa081..78c64d7da 100644 --- a/src/com/android/launcher3/notification/NotificationItemView.java +++ b/src/com/android/launcher3/notification/NotificationItemView.java @@ -37,6 +37,7 @@ import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.logging.UserEventDispatcher.LogContainerProvider; import com.android.launcher3.popup.PopupItemView; +import com.android.launcher3.touch.SwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.Themes; @@ -56,7 +57,7 @@ public class NotificationItemView extends PopupItemView implements LogContainerP private TextView mHeaderCount; private NotificationMainView mMainView; private NotificationFooterLayout mFooter; - private SwipeHelper mSwipeHelper; + private SwipeDetector mSwipeDetector; private boolean mAnimatingNextIcon; private int mNotificationHeaderTextColor = Notification.COLOR_DEFAULT; @@ -75,12 +76,14 @@ public class NotificationItemView extends PopupItemView implements LogContainerP @Override protected void onFinishInflate() { super.onFinishInflate(); - mHeaderText = (TextView) findViewById(R.id.notification_text); - mHeaderCount = (TextView) findViewById(R.id.notification_count); - mMainView = (NotificationMainView) findViewById(R.id.main_view); - mFooter = (NotificationFooterLayout) findViewById(R.id.footer); - mSwipeHelper = new SwipeHelper(SwipeHelper.X, mMainView, getContext()); - mSwipeHelper.setDisableHardwareLayers(true); + mHeaderText = findViewById(R.id.notification_text); + mHeaderCount = findViewById(R.id.notification_count); + mMainView = findViewById(R.id.main_view); + mFooter = findViewById(R.id.footer); + + mSwipeDetector = new SwipeDetector(getContext(), mMainView, SwipeDetector.HORIZONTAL); + mSwipeDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); + mMainView.setSwipeDetector(mSwipeDetector); } public NotificationMainView getMainView() { @@ -136,7 +139,8 @@ public class NotificationItemView extends PopupItemView implements LogContainerP return false; } getParent().requestDisallowInterceptTouchEvent(true); - return mSwipeHelper.onInterceptTouchEvent(ev); + mSwipeDetector.onTouchEvent(ev); + return mSwipeDetector.isDraggingOrSettling(); } @Override @@ -145,7 +149,7 @@ public class NotificationItemView extends PopupItemView implements LogContainerP // The notification hasn't been populated yet. return false; } - return mSwipeHelper.onTouchEvent(ev) || super.onTouchEvent(ev); + return mSwipeDetector.onTouchEvent(ev) || super.onTouchEvent(ev); } public void applyNotificationInfos(final List<NotificationInfo> notificationInfos) { diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 9b8dd648f..5aff28db4 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -23,15 +23,17 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.RippleDrawable; import android.text.TextUtils; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; import android.widget.FrameLayout; import android.widget.TextView; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.R; +import com.android.launcher3.touch.OverScroll; +import com.android.launcher3.touch.SwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.Themes; @@ -39,7 +41,7 @@ import com.android.launcher3.util.Themes; * A {@link android.widget.FrameLayout} that contains a single notification, * e.g. icon + title + text. */ -public class NotificationMainView extends FrameLayout implements SwipeHelper.Callback { +public class NotificationMainView extends FrameLayout implements SwipeDetector.Listener { private NotificationInfo mNotificationInfo; private ViewGroup mTextAndBackground; @@ -47,6 +49,8 @@ public class NotificationMainView extends FrameLayout implements SwipeHelper.Cal private TextView mTitleView; private TextView mTextView; + private SwipeDetector mSwipeDetector; + public NotificationMainView(Context context) { this(context, null, 0); } @@ -78,6 +82,10 @@ public class NotificationMainView extends FrameLayout implements SwipeHelper.Cal applyNotificationInfo(mainNotification, iconView, false); } + public void setSwipeDetector(SwipeDetector swipeDetector) { + mSwipeDetector = swipeDetector; + } + /** * Sets the content of this view, animating it after a new icon shifts up if necessary. */ @@ -113,29 +121,11 @@ public class NotificationMainView extends FrameLayout implements SwipeHelper.Cal } - // SwipeHelper.Callback's - - @Override - public View getChildAtPosition(MotionEvent ev) { - return this; - } - - @Override - public boolean canChildBeDismissed(View v) { + public boolean canChildBeDismissed() { return mNotificationInfo != null && mNotificationInfo.dismissable; } - @Override - public boolean isAntiFalsingNeeded() { - return false; - } - - @Override - public void onBeginDrag(View v) { - } - - @Override - public void onChildDismissed(View v) { + public void onChildDismissed() { Launcher launcher = Launcher.getLauncher(getContext()); launcher.getPopupDataProvider().cancelNotification( mNotificationInfo.notificationKey); @@ -145,22 +135,55 @@ public class NotificationMainView extends FrameLayout implements SwipeHelper.Cal LauncherLogProto.ItemType.NOTIFICATION); } + // SwipeDetector.Listener's @Override - public void onDragCancelled(View v) { - } + public void onDragStart(boolean start) { } - @Override - public void onChildSnappedBack(View animView, float targetLeft) { - } @Override - public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { - // Don't fade out. + public boolean onDrag(float displacement, float velocity) { + setTranslationX(canChildBeDismissed() + ? displacement : OverScroll.dampedScroll(displacement, getWidth())); + animate().cancel(); return true; } @Override - public float getFalsingThresholdFactor() { - return 1; + public void onDragEnd(float velocity, boolean fling) { + final boolean willExit; + final float endTranslation; + + if (!canChildBeDismissed()) { + willExit = false; + endTranslation = 0; + } else if (fling) { + willExit = true; + endTranslation = velocity < 0 ? - getWidth() : getWidth(); + } else if (Math.abs(getTranslationX()) > getWidth() / 2) { + willExit = true; + endTranslation = (getTranslationX() < 0 ? -getWidth() : getWidth()); + } else { + willExit = false; + endTranslation = 0; + } + + SwipeDetector.ScrollInterpolator interpolator = new SwipeDetector.ScrollInterpolator(); + interpolator.setVelocityAtZero(velocity); + + long duration = SwipeDetector.calculateDuration(velocity, + (endTranslation - getTranslationX()) / getWidth()); + animate() + .setDuration(duration) + .setInterpolator(interpolator) + .translationX(endTranslation) + .withEndAction(new Runnable() { + @Override + public void run() { + mSwipeDetector.finishedScrolling(); + if (willExit) { + onChildDismissed(); + } + } + }).start(); } } diff --git a/src/com/android/launcher3/notification/SwipeHelper.java b/src/com/android/launcher3/notification/SwipeHelper.java deleted file mode 100644 index ebbe5fc6a..000000000 --- a/src/com/android/launcher3/notification/SwipeHelper.java +++ /dev/null @@ -1,687 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.notification; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.Context; -import android.graphics.RectF; -import android.os.Handler; -import android.util.ArrayMap; -import android.util.Log; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.accessibility.AccessibilityEvent; -import com.android.launcher3.R; - -/** - * This class was copied from com.android.systemui. - */ -public class SwipeHelper { - private static final String TAG = "SwipeHelper"; - private static final boolean DEBUG_INVALIDATE = false; - private static final boolean SLOW_ANIMATIONS = false; // DEBUG; - private static final boolean CONSTRAIN_SWIPE = true; - private static final boolean FADE_OUT_DURING_SWIPE = true; - private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true; - - public static final int X = 0; - public static final int Y = 1; - - private static final float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec - private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms - private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms - private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec - private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms - - static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width - // beyond which swipe progress->0 - private float mMinSwipeProgress = 0f; - private float mMaxSwipeProgress = 1f; - - private final FlingAnimationUtils mFlingAnimationUtils; - private float mPagingTouchSlop; - private final Callback mCallback; - private final Handler mHandler; - private final int mSwipeDirection; - private final VelocityTracker mVelocityTracker; - - private float mInitialTouchPos; - private float mPerpendicularInitialTouchPos; - private boolean mDragging; - private boolean mSnappingChild; - private View mCurrView; - private boolean mCanCurrViewBeDimissed; - private float mDensityScale; - private float mTranslation = 0; - - private boolean mLongPressSent; - private LongPressListener mLongPressListener; - private Runnable mWatchLongPress; - private final long mLongPressTimeout; - - final private int[] mTmpPos = new int[2]; - private final int mFalsingThreshold; - private boolean mTouchAboveFalsingThreshold; - private boolean mDisableHwLayers; - - private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>(); - - public SwipeHelper(int swipeDirection, Callback callback, Context context) { - mCallback = callback; - mHandler = new Handler(); - mSwipeDirection = swipeDirection; - mVelocityTracker = VelocityTracker.obtain(); - mDensityScale = context.getResources().getDisplayMetrics().density; - mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop(); - - mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); // extra long-press! - mFalsingThreshold = context.getResources().getDimensionPixelSize( - R.dimen.swipe_helper_falsing_threshold); - mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f); - } - - public void setLongPressListener(LongPressListener listener) { - mLongPressListener = listener; - } - - public void setDensityScale(float densityScale) { - mDensityScale = densityScale; - } - - public void setPagingTouchSlop(float pagingTouchSlop) { - mPagingTouchSlop = pagingTouchSlop; - } - - public void setDisableHardwareLayers(boolean disableHwLayers) { - mDisableHwLayers = disableHwLayers; - } - - private float getPos(MotionEvent ev) { - return mSwipeDirection == X ? ev.getX() : ev.getY(); - } - - private float getPerpendicularPos(MotionEvent ev) { - return mSwipeDirection == X ? ev.getY() : ev.getX(); - } - - protected float getTranslation(View v) { - return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY(); - } - - private float getVelocity(VelocityTracker vt) { - return mSwipeDirection == X ? vt.getXVelocity() : - vt.getYVelocity(); - } - - protected ObjectAnimator createTranslationAnimation(View v, float newPos) { - ObjectAnimator anim = ObjectAnimator.ofFloat(v, - mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos); - return anim; - } - - private float getPerpendicularVelocity(VelocityTracker vt) { - return mSwipeDirection == X ? vt.getYVelocity() : - vt.getXVelocity(); - } - - protected Animator getViewTranslationAnimator(View v, float target, - AnimatorUpdateListener listener) { - ObjectAnimator anim = createTranslationAnimation(v, target); - if (listener != null) { - anim.addUpdateListener(listener); - } - return anim; - } - - protected void setTranslation(View v, float translate) { - if (v == null) { - return; - } - if (mSwipeDirection == X) { - v.setTranslationX(translate); - } else { - v.setTranslationY(translate); - } - } - - protected float getSize(View v) { - return mSwipeDirection == X ? v.getMeasuredWidth() : - v.getMeasuredHeight(); - } - - public void setMinSwipeProgress(float minSwipeProgress) { - mMinSwipeProgress = minSwipeProgress; - } - - public void setMaxSwipeProgress(float maxSwipeProgress) { - mMaxSwipeProgress = maxSwipeProgress; - } - - private float getSwipeProgressForOffset(View view, float translation) { - float viewSize = getSize(view); - float result = Math.abs(translation / viewSize); - return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress); - } - - private float getSwipeAlpha(float progress) { - return Math.min(0, Math.max(1, progress / SWIPE_PROGRESS_FADE_END)); - } - - private void updateSwipeProgressFromOffset(View animView, boolean dismissable) { - updateSwipeProgressFromOffset(animView, dismissable, getTranslation(animView)); - } - - private void updateSwipeProgressFromOffset(View animView, boolean dismissable, - float translation) { - float swipeProgress = getSwipeProgressForOffset(animView, translation); - if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) { - if (FADE_OUT_DURING_SWIPE && dismissable) { - float alpha = swipeProgress; - if (!mDisableHwLayers) { - if (alpha != 0f && alpha != 1f) { - animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else { - animView.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - animView.setAlpha(getSwipeAlpha(swipeProgress)); - } - } - invalidateGlobalRegion(animView); - } - - // invalidate the view's own bounds all the way up the view hierarchy - public static void invalidateGlobalRegion(View view) { - invalidateGlobalRegion( - view, - new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom())); - } - - // invalidate a rectangle relative to the view's coordinate system all the way up the view - // hierarchy - public static void invalidateGlobalRegion(View view, RectF childBounds) { - //childBounds.offset(view.getTranslationX(), view.getTranslationY()); - if (DEBUG_INVALIDATE) - Log.v(TAG, "-------------"); - while (view.getParent() != null && view.getParent() instanceof View) { - view = (View) view.getParent(); - view.getMatrix().mapRect(childBounds); - view.invalidate((int) Math.floor(childBounds.left), - (int) Math.floor(childBounds.top), - (int) Math.ceil(childBounds.right), - (int) Math.ceil(childBounds.bottom)); - if (DEBUG_INVALIDATE) { - Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left) - + "," + (int) Math.floor(childBounds.top) - + "," + (int) Math.ceil(childBounds.right) - + "," + (int) Math.ceil(childBounds.bottom)); - } - } - } - - public void removeLongPressCallback() { - if (mWatchLongPress != null) { - mHandler.removeCallbacks(mWatchLongPress); - mWatchLongPress = null; - } - } - - public boolean onInterceptTouchEvent(final MotionEvent ev) { - final int action = ev.getAction(); - - switch (action) { - case MotionEvent.ACTION_DOWN: - mTouchAboveFalsingThreshold = false; - mDragging = false; - mSnappingChild = false; - mLongPressSent = false; - mVelocityTracker.clear(); - mCurrView = mCallback.getChildAtPosition(ev); - - if (mCurrView != null) { - onDownUpdate(mCurrView); - mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView); - mVelocityTracker.addMovement(ev); - mInitialTouchPos = getPos(ev); - mPerpendicularInitialTouchPos = getPerpendicularPos(ev); - mTranslation = getTranslation(mCurrView); - if (mLongPressListener != null) { - if (mWatchLongPress == null) { - mWatchLongPress = new Runnable() { - @Override - public void run() { - if (mCurrView != null && !mLongPressSent) { - mLongPressSent = true; - mCurrView.sendAccessibilityEvent( - AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); - mCurrView.getLocationOnScreen(mTmpPos); - final int x = (int) ev.getRawX() - mTmpPos[0]; - final int y = (int) ev.getRawY() - mTmpPos[1]; - mLongPressListener.onLongPress(mCurrView, x, y); - } - } - }; - } - mHandler.postDelayed(mWatchLongPress, mLongPressTimeout); - } - } - break; - - case MotionEvent.ACTION_MOVE: - if (mCurrView != null && !mLongPressSent) { - mVelocityTracker.addMovement(ev); - float pos = getPos(ev); - float perpendicularPos = getPerpendicularPos(ev); - float delta = pos - mInitialTouchPos; - float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos; - if (Math.abs(delta) > mPagingTouchSlop - && Math.abs(delta) > Math.abs(deltaPerpendicular)) { - mCallback.onBeginDrag(mCurrView); - mDragging = true; - mInitialTouchPos = getPos(ev); - mTranslation = getTranslation(mCurrView); - removeLongPressCallback(); - } - } - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - final boolean captured = (mDragging || mLongPressSent); - mDragging = false; - mCurrView = null; - mLongPressSent = false; - removeLongPressCallback(); - if (captured) return true; - break; - } - return mDragging || mLongPressSent; - } - - /** - * @param view The view to be dismissed - * @param velocity The desired pixels/second speed at which the view should move - * @param useAccelerateInterpolator Should an accelerating Interpolator be used - */ - public void dismissChild(final View view, float velocity, boolean useAccelerateInterpolator) { - dismissChild(view, velocity, null /* endAction */, 0 /* delay */, - useAccelerateInterpolator, 0 /* fixedDuration */, false /* isDismissAll */); - } - - /** - * @param animView The view to be dismissed - * @param velocity The desired pixels/second speed at which the view should move - * @param endAction The action to perform at the end - * @param delay The delay after which we should start - * @param useAccelerateInterpolator Should an accelerating Interpolator be used - * @param fixedDuration If not 0, this exact duration will be taken - */ - public void dismissChild(final View animView, float velocity, final Runnable endAction, - long delay, boolean useAccelerateInterpolator, long fixedDuration, - boolean isDismissAll) { - final boolean canBeDismissed = mCallback.canChildBeDismissed(animView); - float newPos; - boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; - - // if we use the Menu to dismiss an item in landscape, animate up - boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll) - && mSwipeDirection == Y; - // if the language is rtl we prefer swiping to the left - boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll) - && isLayoutRtl; - boolean animateLeft = velocity < 0 - || (velocity == 0 && getTranslation(animView) < 0 && !isDismissAll); - - if (animateLeft || animateLeftForRtl || animateUpForMenu) { - newPos = -getSize(animView); - } else { - newPos = getSize(animView); - } - long duration; - if (fixedDuration == 0) { - duration = MAX_ESCAPE_ANIMATION_DURATION; - if (velocity != 0) { - duration = Math.min(duration, - (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math - .abs(velocity)) - ); - } else { - duration = DEFAULT_ESCAPE_ANIMATION_DURATION; - } - } else { - duration = fixedDuration; - } - - if (!mDisableHwLayers) { - animView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } - AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed); - } - }; - - Animator anim = getViewTranslationAnimator(animView, newPos, updateListener); - if (anim == null) { - return; - } - if (useAccelerateInterpolator) { - anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); - anim.setDuration(duration); - } else { - mFlingAnimationUtils.applyDismissing(anim, getTranslation(animView), - newPos, velocity, getSize(animView)); - } - if (delay > 0) { - anim.setStartDelay(delay); - } - anim.addListener(new AnimatorListenerAdapter() { - private boolean mCancelled; - - public void onAnimationCancel(Animator animation) { - mCancelled = true; - } - - public void onAnimationEnd(Animator animation) { - updateSwipeProgressFromOffset(animView, canBeDismissed); - mDismissPendingMap.remove(animView); - if (!mCancelled) { - mCallback.onChildDismissed(animView); - } - if (endAction != null) { - endAction.run(); - } - if (!mDisableHwLayers) { - animView.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - }); - - prepareDismissAnimation(animView, anim); - mDismissPendingMap.put(animView, anim); - anim.start(); - } - - /** - * Called to update the dismiss animation. - */ - protected void prepareDismissAnimation(View view, Animator anim) { - // Do nothing - } - - public void snapChild(final View animView, final float targetLeft, float velocity) { - final boolean canBeDismissed = mCallback.canChildBeDismissed(animView); - AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed); - } - }; - - Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener); - if (anim == null) { - return; - } - int duration = SNAP_ANIM_LEN; - anim.setDuration(duration); - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animator) { - mSnappingChild = false; - updateSwipeProgressFromOffset(animView, canBeDismissed); - mCallback.onChildSnappedBack(animView, targetLeft); - } - }); - prepareSnapBackAnimation(animView, anim); - mSnappingChild = true; - anim.start(); - } - - /** - * Called to update the snap back animation. - */ - protected void prepareSnapBackAnimation(View view, Animator anim) { - // Do nothing - } - - /** - * Called when there's a down event. - */ - public void onDownUpdate(View currView) { - // Do nothing - } - - /** - * Called on a move event. - */ - protected void onMoveUpdate(View view, float totalTranslation, float delta) { - // Do nothing - } - - /** - * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current - * view is being animated to dismiss or snap. - */ - public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) { - updateSwipeProgressFromOffset(animView, canBeDismissed, value); - } - - private void snapChildInstantly(final View view) { - final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view); - setTranslation(view, 0); - updateSwipeProgressFromOffset(view, canAnimViewBeDismissed); - } - - /** - * Called when a view is updated to be non-dismissable, if the view was being dismissed before - * the update this will handle snapping it back into place. - * - * @param view the view to snap if necessary. - * @param animate whether to animate the snap or not. - * @param targetLeft the target to snap to. - */ - public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) { - if ((mDragging && mCurrView == view) || mSnappingChild) { - return; - } - boolean needToSnap = false; - Animator dismissPendingAnim = mDismissPendingMap.get(view); - if (dismissPendingAnim != null) { - needToSnap = true; - dismissPendingAnim.cancel(); - } else if (getTranslation(view) != 0) { - needToSnap = true; - } - if (needToSnap) { - if (animate) { - snapChild(view, targetLeft, 0.0f /* velocity */); - } else { - snapChildInstantly(view); - } - } - } - - public boolean onTouchEvent(MotionEvent ev) { - if (mLongPressSent) { - return true; - } - - if (!mDragging) { - if (mCallback.getChildAtPosition(ev) != null) { - - // We are dragging directly over a card, make sure that we also catch the gesture - // even if nobody else wants the touch event. - onInterceptTouchEvent(ev); - return true; - } else { - - // We are not doing anything, make sure the long press callback - // is not still ticking like a bomb waiting to go off. - removeLongPressCallback(); - return false; - } - } - - mVelocityTracker.addMovement(ev); - final int action = ev.getAction(); - switch (action) { - case MotionEvent.ACTION_OUTSIDE: - case MotionEvent.ACTION_MOVE: - if (mCurrView != null) { - float delta = getPos(ev) - mInitialTouchPos; - float absDelta = Math.abs(delta); - if (absDelta >= getFalsingThreshold()) { - mTouchAboveFalsingThreshold = true; - } - // don't let items that can't be dismissed be dragged more than - // maxScrollDistance - if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) { - float size = getSize(mCurrView); - float maxScrollDistance = 0.25f * size; - if (absDelta >= size) { - delta = delta > 0 ? maxScrollDistance : -maxScrollDistance; - } else { - delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2)); - } - } - - setTranslation(mCurrView, mTranslation + delta); - updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed); - onMoveUpdate(mCurrView, mTranslation + delta, delta); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (mCurrView == null) { - break; - } - mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity()); - float velocity = getVelocity(mVelocityTracker); - - if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) { - if (isDismissGesture(ev)) { - // flingadingy - dismissChild(mCurrView, velocity, - !swipedFastEnough() /* useAccelerateInterpolator */); - } else { - // snappity - mCallback.onDragCancelled(mCurrView); - snapChild(mCurrView, 0 /* leftTarget */, velocity); - } - mCurrView = null; - } - mDragging = false; - break; - } - return true; - } - - private int getFalsingThreshold() { - float factor = mCallback.getFalsingThresholdFactor(); - return (int) (mFalsingThreshold * factor); - } - - private float getMaxVelocity() { - return MAX_DISMISS_VELOCITY * mDensityScale; - } - - protected float getEscapeVelocity() { - return getUnscaledEscapeVelocity() * mDensityScale; - } - - protected float getUnscaledEscapeVelocity() { - return SWIPE_ESCAPE_VELOCITY; - } - - protected long getMaxEscapeAnimDuration() { - return MAX_ESCAPE_ANIMATION_DURATION; - } - - 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() && !mTouchAboveFalsingThreshold; - return !falsingDetected && (swipedFastEnough() || swipedFarEnough()) - && ev.getActionMasked() == MotionEvent.ACTION_UP - && mCallback.canChildBeDismissed(mCurrView); - } - - protected boolean swipedFastEnough() { - float velocity = getVelocity(mVelocityTracker); - float translation = getTranslation(mCurrView); - boolean ret = (Math.abs(velocity) > getEscapeVelocity()) - && (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); - - boolean canChildBeDismissed(View v); - - boolean isAntiFalsingNeeded(); - - void onBeginDrag(View v); - - void onChildDismissed(View v); - - void onDragCancelled(View v); - - /** - * Called when the child is snapped to a position. - * - * @param animView the view that was snapped. - * @param targetLeft the left position the view was snapped to. - */ - void onChildSnappedBack(View animView, float targetLeft); - - /** - * Updates the swipe progress on a child. - * - * @return if true, prevents the default alpha fading. - */ - boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress); - - /** - * @return The factor the falsing threshold should be multiplied with - */ - float getFalsingThresholdFactor(); - } - - /** - * Equivalent to View.OnLongClickListener with coordinates - */ - public interface LongPressListener { - /** - * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates - * @return whether the longpress was handled - */ - boolean onLongPress(View v, int x, int y); - } -}
\ No newline at end of file |