summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/notification
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2017-07-14 00:02:27 -0700
committerSunny Goyal <sunnygoyal@google.com>2017-07-20 01:33:10 -0700
commitb72d8b2c8b999f3842dc7b0d93bb1a816b6204b9 (patch)
treeb20bd154d07c5b169dd5745ffd12812b6c17baac /src/com/android/launcher3/notification
parentf567ee8f42cab072f10002302d85e99083122481 (diff)
downloadandroid_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')
-rw-r--r--src/com/android/launcher3/notification/FlingAnimationUtils.java356
-rw-r--r--src/com/android/launcher3/notification/NotificationItemView.java22
-rw-r--r--src/com/android/launcher3/notification/NotificationMainView.java85
-rw-r--r--src/com/android/launcher3/notification/SwipeHelper.java687
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