Introduces haptic feedback to launcher overview
Haptics introduced at the key moments:
- Task scroll in overview or quick switch, trigger when a new task comes
to the center of the screen;
- Task scroll in overview when overscroll animation is triggered;
- Task dismissed in overview;
There is also a configured min gap between two scroll haptics set to
20ms to prevent fast scrolls from creating a chain of cancelled effects.
Fix: 182382085
Test: manual
Change-Id: I43c0f8c879a06f317e8a660240dafb7f7abe79f7
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d67b23b..31c0f5f 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -30,6 +30,7 @@
determines how many thumbnails will be fetched in the background. -->
<integer name="recentsThumbnailCacheSize">3</integer>
<integer name="recentsIconCacheSize">12</integer>
+ <integer name="recentsScrollHapticMinGapMillis">20</integer>
<!-- Assistant Gesture -->
<integer name="assistant_gesture_min_time_threshold">200</integer>
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 283743d..ef6f53e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -24,7 +24,7 @@
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.ObjectAnimator;
@@ -38,11 +38,11 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.OverviewToHomeAnim;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 40c3e02..ff3c517 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -41,7 +41,7 @@
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
@@ -67,14 +67,15 @@
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
import com.android.launcher3.util.TouchController;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsView;
/**
* Handles quick switching to a recent task from the home screen. To give as much flexibility to
@@ -398,6 +399,14 @@
nonOverviewAnim.setFloatValues(startProgress, endProgress);
mNonOverviewAnim.dispatchOnStart();
}
+ if (targetState == QUICK_SWITCH) {
+ // Navigating to quick switch, add scroll feedback since the first time is not
+ // considered a scroll by the RecentsView.
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(
+ RecentsView.SCROLL_VIBRATION_PRIMITIVE,
+ RecentsView.SCROLL_VIBRATION_PRIMITIVE_SCALE,
+ RecentsView.SCROLL_VIBRATION_FALLBACK);
+ }
nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 0603ba5..010f463 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -22,6 +22,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.os.SystemClock;
+import android.os.VibrationEffect;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Interpolator;
@@ -41,6 +42,7 @@
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -55,6 +57,12 @@
private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
private static final long MAX_TASK_DISMISS_ANIMATION_DURATION = 600;
+ public static final int TASK_DISMISS_VIBRATION_PRIMITIVE =
+ Utilities.ATLEAST_R ? VibrationEffect.Composition.PRIMITIVE_TICK : -1;
+ public static final float TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE = 1f;
+ public static final VibrationEffect TASK_DISMISS_VIBRATION_FALLBACK =
+ VibratorWrapper.EFFECT_TEXTURE_TICK;
+
protected final T mActivity;
private final SingleAxisSwipeDetector mDetector;
private final RecentsView mRecentsView;
@@ -334,10 +342,10 @@
fling = false;
}
PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
+ boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
float progress = mCurrentAnimation.getProgressFraction();
float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
if (fling) {
- boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
} else {
goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
@@ -357,6 +365,10 @@
mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
velocity * orientationHandler.getSecondaryTranslationDirectionFactor(),
mEndDisplacement, animationDuration);
+ if (goingUp && goingToEnd) {
+ VibratorWrapper.INSTANCE.get(mActivity).vibrate(TASK_DISMISS_VIBRATION_PRIMITIVE,
+ TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE, TASK_DISMISS_VIBRATION_FALLBACK);
+ }
}
private void clearState() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 42c89fd..48a4e77 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -36,7 +36,6 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -46,6 +45,7 @@
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
@@ -90,7 +90,6 @@
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
@@ -107,6 +106,7 @@
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
index 0521db4..063eb6a 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGesturePanel.java
@@ -43,7 +43,7 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.util.VibratorWrapper;
/** Forked from platform/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java. */
public class EdgeBackGesturePanel extends View {
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index a9a9e2a..5e127d4 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -16,7 +16,6 @@
package com.android.quickstep.interaction;
import static com.android.launcher3.Utilities.squaredHypot;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
@@ -26,6 +25,7 @@
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_GESTURE_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE;
+import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -47,11 +47,11 @@
import com.android.launcher3.R;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.systemui.shared.system.QuickStepContract;
/** Utility class to handle Home and Assistant gestures. */
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
similarity index 65%
rename from src/com/android/launcher3/util/VibratorWrapper.java
rename to quickstep/src/com/android/quickstep/util/VibratorWrapper.java
index b0defd4..211bd08 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/quickstep/src/com/android/quickstep/util/VibratorWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.launcher3.util;
+package com.android.quickstep.util;
import static android.os.VibrationEffect.createPredefined;
import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
@@ -21,15 +21,20 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.media.AudioAttributes;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MainThreadInitializedObject;
+
/**
* Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
*/
@@ -39,8 +44,15 @@
public static final MainThreadInitializedObject<VibratorWrapper> INSTANCE =
new MainThreadInitializedObject<>(VibratorWrapper::new);
+ public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build();
+
public static final VibrationEffect EFFECT_CLICK =
createPredefined(VibrationEffect.EFFECT_CLICK);
+ public static final VibrationEffect EFFECT_TEXTURE_TICK =
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TEXTURE_TICK);
/**
* Haptic when entering overview.
@@ -78,7 +90,27 @@
/** Vibrates with the given effect if haptic feedback is available and enabled. */
public void vibrate(VibrationEffect vibrationEffect) {
if (mHasVibrator && mIsHapticFeedbackEnabled) {
- UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect));
+ UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect, VIBRATION_ATTRS));
+ }
+ }
+
+ /**
+ * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only
+ * vibrates if haptic feedback is available and enabled.
+ */
+ @SuppressLint("NewApi")
+ public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) {
+ if (mHasVibrator && mIsHapticFeedbackEnabled) {
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (Utilities.ATLEAST_R && primitiveId >= 0
+ && mVibrator.areAllPrimitivesSupported(primitiveId)) {
+ mVibrator.vibrate(VibrationEffect.startComposition()
+ .addPrimitive(primitiveId, primitiveScale)
+ .compose(), VIBRATION_ATTRS);
+ } else {
+ mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS);
+ }
+ });
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 3aed7cc..9ec5138 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -77,7 +77,9 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
+import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.VibrationEffect;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -154,6 +156,7 @@
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.VibratorWrapper;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -238,6 +241,12 @@
}
};
+ public static final int SCROLL_VIBRATION_PRIMITIVE =
+ Utilities.ATLEAST_S ? VibrationEffect.Composition.PRIMITIVE_LOW_TICK : -1;
+ public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f;
+ public static final VibrationEffect SCROLL_VIBRATION_FALLBACK =
+ VibratorWrapper.EFFECT_TEXTURE_TICK;
+
/**
* Can be used to tint the color of the RecentsView to simulate a scrim that can views
* excluded from. Really should be a proper scrim.
@@ -385,6 +394,7 @@
protected final ACTIVITY_TYPE mActivity;
private final float mFastFlingVelocity;
+ private final int mScrollHapticMinGapMillis;
private final RecentsModel mModel;
private final int mGridSideMargin;
private final ClearAllButton mClearAllButton;
@@ -430,6 +440,7 @@
private ObjectAnimator mTintingAnimator;
private int mOverScrollShift = 0;
+ private long mScrollLastHapticTimestamp;
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -615,6 +626,8 @@
final int rotation = mActivity.getDisplay().getRotation();
mOrientationState.setRecentsRotation(rotation);
+ mScrollHapticMinGapMillis = getResources()
+ .getInteger(R.integer.recentsScrollHapticMinGapMillis);
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mModel = RecentsModel.INSTANCE.get(context);
@@ -1152,6 +1165,25 @@
}
@Override
+ protected void onEdgeAbsorbingScroll() {
+ vibrateForScroll();
+ }
+
+ @Override
+ protected void onScrollOverPageChanged() {
+ vibrateForScroll();
+ }
+
+ private void vibrateForScroll() {
+ long now = SystemClock.uptimeMillis();
+ if (now - mScrollLastHapticTimestamp > mScrollHapticMinGapMillis) {
+ mScrollLastHapticTimestamp = now;
+ VibratorWrapper.INSTANCE.get(mContext).vibrate(SCROLL_VIBRATION_PRIMITIVE,
+ SCROLL_VIBRATION_PRIMITIVE_SCALE, SCROLL_VIBRATION_FALLBACK);
+ }
+ }
+
+ @Override
protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
// Enables swiping to the left or right only if the task overlay is not modal.
if (!isModal()) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index eb3f94c..e4ee50a 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -108,6 +108,8 @@
// relative scroll position unchanged in updateCurrentPageScroll. Cleared when snapping to a
// page.
protected int mCurrentPageScrollDiff;
+ // The current page the PagedView is scrolling over on it's way to the destination page.
+ protected int mCurrentScrollOverPage;
@ViewDebug.ExportedProperty(category = "launcher")
protected int mNextPage = INVALID_PAGE;
@@ -180,6 +182,7 @@
mScroller = new OverScroller(context, SCROLL);
mCurrentPage = 0;
+ mCurrentScrollOverPage = 0;
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
@@ -437,6 +440,7 @@
}
int prevPage = overridePrevPage != INVALID_PAGE ? overridePrevPage : mCurrentPage;
mCurrentPage = validateNewPage(currentPage);
+ mCurrentScrollOverPage = mCurrentPage;
updateCurrentPageScroll();
notifyPageSwitchListener(prevPage);
invalidate();
@@ -557,9 +561,11 @@
if (newPos < mMinScroll && oldPos >= mMinScroll) {
mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
mScroller.abortAnimation();
+ onEdgeAbsorbingScroll();
} else if (newPos > mMaxScroll && oldPos <= mMaxScroll) {
mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
mScroller.abortAnimation();
+ onEdgeAbsorbingScroll();
}
}
@@ -577,6 +583,7 @@
sendScrollAccessibilityEvent();
int prevPage = mCurrentPage;
mCurrentPage = validateNewPage(mNextPage);
+ mCurrentScrollOverPage = mCurrentPage;
mNextPage = INVALID_PAGE;
notifyPageSwitchListener(prevPage);
@@ -837,6 +844,7 @@
public void onViewRemoved(View child) {
super.onViewRemoved(child);
mCurrentPage = validateNewPage(mCurrentPage);
+ mCurrentScrollOverPage = mCurrentPage;
dispatchPageCountChanged();
}
@@ -1408,6 +1416,20 @@
protected void onNotSnappingToPageInFreeScroll() { }
+ /**
+ * Called when the view edges absorb part of the scroll. Subclasses can override this
+ * to provide custom behavior during animation.
+ */
+ protected void onEdgeAbsorbingScroll() {
+ }
+
+ /**
+ * Called when the current page closest to the center of the screen changes as part of the
+ * scroll. Subclasses can override this to provide custom behavior during scroll.
+ */
+ protected void onScrollOverPageChanged() {
+ }
+
protected boolean shouldFlingForVelocity(int velocity) {
float threshold = mAllowEasyFling ? mEasyFlingThresholdVelocity : mFlingThresholdVelocity;
return Math.abs(velocity) > threshold;
@@ -1692,6 +1714,15 @@
}
@Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ int newDestinationPage = getDestinationPage();
+ if (newDestinationPage >= 0 && newDestinationPage != mCurrentScrollOverPage) {
+ mCurrentScrollOverPage = newDestinationPage;
+ onScrollOverPageChanged();
+ }
+ }
+
+ @Override
public CharSequence getAccessibilityClassName() {
// Some accessibility services have special logic for ScrollView. Since we provide same
// accessibility info as ScrollView, inform the service to handle use the same way.