Merge "Removing extra binder call on state or visibility change" into ub-launcher3-edmonton
diff --git a/quickstep/res/layout/drag_handle_indicator.xml b/quickstep/res/layout/drag_handle_indicator.xml
deleted file mode 100644
index 9ee05d5..0000000
--- a/quickstep/res/layout/drag_handle_indicator.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-<com.android.quickstep.views.QuickstepDragIndicator
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/drag_indicator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:contentDescription="@string/accessibility_desc_recent_apps"
- android:scaleType="centerInside"
- android:tint="?attr/workspaceTextColor" />
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9e4d60c..ad5f767 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -30,8 +30,9 @@
<dimen name="quickstep_fling_min_velocity">250dp</dimen>
<!-- Launcher app transition -->
- <dimen name="content_trans_y">25dp</dimen>
- <dimen name="workspace_trans_y">80dp</dimen>
+ <dimen name="content_trans_y">50dp</dimen>
+ <dimen name="workspace_trans_y">50dp</dimen>
+ <dimen name="closing_window_trans_y">115dp</dimen>
<dimen name="recents_empty_message_text_size">16sp</dimen>
<dimen name="recents_empty_message_text_padding">16dp</dimen>
@@ -50,5 +51,4 @@
<dimen name="clear_all_container_width">168dp</dimen>
<dimen name="shelf_surface_radius">16dp</dimen>
- <dimen name="shelf_surface_top_padding">4dp</dimen>
</resources>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 4963f5d..fc02f72 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -19,12 +19,11 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
-import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
-import static com.android.launcher3.anim.Interpolators.APP_CLOSE_ALPHA;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
@@ -58,7 +57,6 @@
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
-import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
@@ -102,11 +100,10 @@
public static final int RECENTS_LAUNCH_DURATION = 336;
private static final int LAUNCHER_RESUME_START_DELAY = 100;
- private static final int CLOSING_TRANSITION_DURATION_MS = 350;
+ private static final int CLOSING_TRANSITION_DURATION_MS = 250;
// Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
- public static final float ALL_APPS_PROGRESS_OVERSHOOT = 0.99581414f;
private final DragLayer mDragLayer;
private final Launcher mLauncher;
@@ -116,6 +113,7 @@
private final float mContentTransY;
private final float mWorkspaceTransY;
+ private final float mClosingWindowTransY;
private DeviceProfile mDeviceProfile;
private View mFloatingView;
@@ -129,6 +127,16 @@
}
};
+ private final Runnable mDragLayerResetRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
+ mDragLayer.setAlpha(1);
+ mDragLayer.setTranslationY(0);
+ mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
+ }
+ };
+
private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -151,6 +159,7 @@
Resources res = mLauncher.getResources();
mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
+ mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
mLauncher.addOnDeviceProfileChangeListener(this);
registerRemoteAnimations();
@@ -187,7 +196,7 @@
anim.play(getIconAnimator(v));
if (launcherClosing) {
Pair<AnimatorSet, Runnable> launcherContentAnimator =
- getLauncherContentAnimator(false /* show */);
+ getLauncherContentAnimator(true /* isAppOpening */);
anim.play(launcherContentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@Override
@@ -281,21 +290,21 @@
/**
* Content is everything on screen except the background and the floating view (if any).
*
- * @param show If true: Animate the content so that it moves upwards and fades in.
- * Else: Animate the content so that it moves downwards and fades out.
+ * @param isAppOpening True when this is called when an app is opening.
+ * False when this is called when an app is closing.
*/
- private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean show) {
+ private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening) {
AnimatorSet launcherAnimator = new AnimatorSet();
Runnable endListener;
- float[] alphas = show
- ? new float[] {0, 1}
- : new float[] {1, 0};
- float[] trans = show
- ? new float[] {mContentTransY, 0,}
- : new float[] {0, mContentTransY};
+ if (mLauncher.isInState(ALL_APPS)) {
+ float[] alphas = isAppOpening
+ ? new float[] {1, 0}
+ : new float[] {0, 1};
+ float[] trans = isAppOpening
+ ? new float[] {0, mContentTransY}
+ : new float[] {-mContentTransY, 0};
- if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) {
// All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
final View appsView = mLauncher.getAppsView();
final float startAlpha = appsView.getAlpha();
@@ -326,6 +335,9 @@
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
};
} else {
+ float[] alphas = new float[] {1, 0};
+ float[] trans = new float[] {0, mContentTransY};
+
mDragLayer.setAlpha(alphas[0]);
mDragLayer.setTranslationY(trans[0]);
@@ -343,12 +355,7 @@
// Pause page indicator animations as they lead to layer trashing.
mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
- endListener = () -> {
- mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
- mDragLayer.setAlpha(1);
- mDragLayer.setTranslationY(0);
- mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
- };
+ endListener = mDragLayerResetRunnable;
}
return new Pair<>(launcherAnimator, endListener);
}
@@ -646,16 +653,14 @@
*/
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
Matrix matrix = new Matrix();
- float height = mLauncher.getDeviceProfile().heightPx;
- float width = mLauncher.getDeviceProfile().widthPx;
- float endX = (mLauncher.<RecentsView>getOverviewPanel().isRtl() ? -width : width) * 1.16f;
-
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
- closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
+ int duration = CLOSING_TRANSITION_DURATION_MS;
+ closingAnimator.setDuration(duration);
+ Rect crop = new Rect();
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
- FloatProp mDx = new FloatProp(0, endX, 0, 350, AGGRESSIVE_EASE_IN_OUT);
- FloatProp mScale = new FloatProp(1f, 0.8f, 0, 267, AGGRESSIVE_EASE);
- FloatProp mAlpha = new FloatProp(1f, 0f, 0, 350, APP_CLOSE_ALPHA);
+ FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7);
+ FloatProp mScale = new FloatProp(1f, 1.075f, 0, duration, DEACCEL_1_7);
+ FloatProp mAlpha = new FloatProp(1f, 0f, 0, duration, DEACCEL_1_7);
boolean isFirstFrame = true;
@@ -667,13 +672,16 @@
isFirstFrame = false;
}
for (RemoteAnimationTargetCompat app : targets) {
+ crop.set(app.clipRect);
+ crop.top = mDeviceProfile.getInsets().top;
if (app.mode == RemoteAnimationTargetCompat.MODE_CLOSING) {
t.setAlpha(app.leash, mAlpha.value);
matrix.setScale(mScale.value, mScale.value,
app.sourceContainerBounds.centerX(),
app.sourceContainerBounds.centerY());
- matrix.postTranslate(mDx.value, 0);
+ matrix.postTranslate(0, mDy.value);
matrix.postTranslate(app.position.x, app.position.y);
+ t.setWindowCrop(app.leash, crop);
t.setMatrix(app.leash, matrix);
}
}
@@ -694,7 +702,7 @@
if (mLauncher.isInState(LauncherState.ALL_APPS)
|| mLauncher.getDeviceProfile().isVerticalBarLayout()) {
Pair<AnimatorSet, Runnable> contentAnimator =
- getLauncherContentAnimator(true /* show */);
+ getLauncherContentAnimator(false /* isAppOpening */);
contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
anim.play(contentAnimator.first);
anim.addListener(new AnimatorListenerAdapter() {
@@ -706,53 +714,29 @@
} else {
AnimatorSet workspaceAnimator = new AnimatorSet();
- mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY);
- workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(),
- View.TRANSLATION_Y, mWorkspaceTransY, 0));
+ mDragLayer.setTranslationY(-mWorkspaceTransY);
+ workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
+ -mWorkspaceTransY, 0));
- View currentPage = ((CellLayout) mLauncher.getWorkspace()
- .getChildAt(mLauncher.getWorkspace().getCurrentPage()))
- .getShortcutsAndWidgets();
- currentPage.setAlpha(0f);
- workspaceAnimator.play(ObjectAnimator.ofFloat(currentPage, View.ALPHA, 0, 1f));
+ mDragLayer.setAlpha(0);
+ workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, 0, 1f));
workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
workspaceAnimator.setDuration(333);
- workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- currentPage.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
+
+ // Pause page indicator animations as they lead to layer trashing.
+ mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
+ mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
workspaceAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- currentPage.setLayerType(View.LAYER_TYPE_NONE, null);
+ mDragLayerResetRunnable.run();
}
});
-
- // Animate the shelf in two parts: slide in, and overeshoot.
- AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
- // The shelf will start offscreen
- final float startY = ALL_APPS_PROGRESS_OFF_SCREEN;
- // And will end slightly pulled up, so that there is something to overshoot back to 1f.
- final float slideEnd = ALL_APPS_PROGRESS_OVERSHOOT;
-
- allAppsController.setProgress(startY);
-
- Animator allAppsSlideIn =
- ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, startY, slideEnd);
- allAppsSlideIn.setStartDelay(LAUNCHER_RESUME_START_DELAY);
- allAppsSlideIn.setDuration(317);
- allAppsSlideIn.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-
- Animator allAppsOvershoot =
- ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, slideEnd, 1f);
- allAppsOvershoot.setDuration(153);
- allAppsOvershoot.setInterpolator(Interpolators.OVERSHOOT_0);
-
anim.play(workspaceAnimator);
- anim.playSequentially(allAppsSlideIn, allAppsOvershoot);
- anim.addListener(mReapplyStateListener);
}
}
-
private boolean hasControlRemoteAppTransitionPermission() {
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
== PackageManager.PERMISSION_GRANTED;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index e31805c..b0f8d99 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -84,7 +84,7 @@
@Override
public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
- return new float[] {1f, -0.2f};
+ return new float[] {0.9f, -0.2f};
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
index 42f6c74..ce8192f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
@@ -9,6 +9,7 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -56,11 +57,11 @@
}
@Override
- protected float initCurrentAnimation() {
+ protected float initCurrentAnimation(@AnimationComponents int animComponent) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
- mCurrentAnimation = mLauncher.getStateManager()
- .createAnimationToNewWorkspace(mToState, maxAccuracy);
+ mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
+ maxAccuracy, animComponent);
return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 91e1e7f..4029b82 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -25,6 +25,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.views.RecentsView;
@@ -47,8 +48,12 @@
@Override
public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
- // TODO: provide a valid value
- return new float[]{1, 0, -launcher.getDeviceProfile().hotseatBarSizePx / 2};
+ RecentsView recentsView = launcher.getOverviewPanel();
+ Workspace workspace = launcher.getWorkspace();
+ recentsView.getTaskSize(sTempRect);
+ float scale = (float) sTempRect.width() / workspace.getWidth();
+ float parallaxFactor = 0.4f;
+ return new float[]{scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor};
}
@Override
@@ -92,9 +97,9 @@
@Override
public int getVisibleElements(Launcher launcher) {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
- return DRAG_HANDLE_INDICATOR;
+ return 0;
} else {
- return HOTSEAT_SEARCH_BOX | DRAG_HANDLE_INDICATOR |
+ return HOTSEAT_SEARCH_BOX |
(launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 2f0bdc6..514c0e8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -18,20 +18,20 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
@@ -52,43 +52,11 @@
private static final String TAG = "PortraitStatesTouchCtrl";
- private static final float TOTAL_DISTANCE_MULTIPLIER = 3f;
- private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER;
-
- // Must be greater than LINEAR_SCALE_LIMIT;
- private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f;
-
- // Maximum amount to overshoot.
- private static final float MAX_OVERSHOOT = 0.3f;
-
- private static final double PI_BY_2 = Math.PI / 2;
-
private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
// If true, we will finish the current animation instantly on second touch.
private boolean mFinishFastOnSecondTouch;
- private final Interpolator mAllAppsDampedInterpolator = new Interpolator() {
-
- private final double mAngleMultiplier = Math.PI /
- (2 * (MAXIMUM_DISTANCE_FACTOR - LINEAR_SCALE_LIMIT));
-
- @Override
- public float getInterpolation(float v) {
- if (v <= LINEAR_SCALE_LIMIT) {
- return v * TOTAL_DISTANCE_MULTIPLIER;
- }
- float overshoot = (v - LINEAR_SCALE_LIMIT);
- return (float) (1 + MAX_OVERSHOOT * Math.sin(overshoot * mAngleMultiplier));
- }
- };
-
- private final Interpolator mOverviewBoundInterpolator = (v) -> {
- if (v >= MAXIMUM_DISTANCE_FACTOR) {
- return 1;
- }
- return FAST_OUT_SLOW_IN.getInterpolation(v / MAXIMUM_DISTANCE_FACTOR);
- };
public PortraitStatesTouchController(Launcher l) {
super(l, SwipeDetector.VERTICAL);
@@ -144,17 +112,16 @@
}
private AnimatorSetBuilder getNormalToOverviewAnimation() {
- mAllAppsInterpolatorWrapper.baseInterpolator = mAllAppsDampedInterpolator;
+ mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
AnimatorSetBuilder builder = new AnimatorSetBuilder();
builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATION, mOverviewBoundInterpolator);
return builder;
}
@Override
- protected float initCurrentAnimation() {
+ protected float initCurrentAnimation(@AnimationComponents int animComponents) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
@@ -167,7 +134,6 @@
if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
builder = getNormalToOverviewAnimation();
- totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
} else {
builder = new AnimatorSetBuilder();
}
@@ -190,7 +156,8 @@
mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
} else {
mCurrentAnimation = mLauncher.getStateManager()
- .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState);
+ .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState,
+ animComponents);
}
if (totalShift == 0) {
@@ -210,9 +177,9 @@
@Override
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
LauncherState targetState, float velocity, boolean isFling) {
- handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
velocity, isFling);
+ handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
}
private void handleFirstSwipeToOverview(final ValueAnimator animator,
@@ -220,62 +187,22 @@
final boolean isFling) {
if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
mFinishFastOnSecondTouch = true;
-
- // Update all apps interpolator
- float currentFraction = mCurrentAnimation.getProgressFraction();
- float absVelocity = Math.abs(velocity);
- float currentValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction);
-
- if (isFling && absVelocity > 1 && currentFraction < LINEAR_SCALE_LIMIT) {
-
- // TODO: Clean up these magic calculations
- // Linearly interpolate the max value based on the velocity.
- float maxValue = Math.max(absVelocity > 4 ? 1 + MAX_OVERSHOOT :
- 1 + (absVelocity - 1) * MAX_OVERSHOOT / 3,
- currentValue);
- double angleToPeak = PI_BY_2 - Math.asin(currentValue / maxValue);
-
- if (expectedDuration != 0 && angleToPeak != 0) {
-
- float distanceLeft = 1 - currentFraction;
- mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
- float scaledF = (f - currentFraction) / distanceLeft;
-
- if (scaledF < 0.5f) {
- double angle = PI_BY_2 - angleToPeak + scaledF * angleToPeak / 0.5f;
- return (float) (maxValue * Math.sin(angle));
- }
-
- scaledF = ((scaledF - .5f) / .5f);
- double angle = PI_BY_2 + 3 * scaledF * PI_BY_2;
- float amplitude = (1 - scaledF) * (1 - scaledF) * (maxValue - 1);
- return 1 + (float) (amplitude * Math.sin(angle));
- };
-
- animator.setDuration(expectedDuration).setInterpolator(LINEAR);
- return;
- }
+ if (isFling && expectedDuration != 0) {
+ // Update all apps interpolator to add a bit of overshoot starting from currFraction
+ final float currFraction = mCurrentAnimation.getProgressFraction();
+ mAllAppsInterpolatorWrapper.baseInterpolator
+ = new OvershootInterpolator(Math.min(Math.abs(velocity) / 3, 3f)) {
+ @Override
+ public float getInterpolation(float t) {
+ return super.getInterpolation(t) + ((1 - t) * currFraction);
+ }
+ };
+ animator.setFloatValues(0, 1);
+ animator.setDuration(Math.max(expectedDuration, 300)).setInterpolator(LINEAR);
}
-
- if (currentFraction < LINEAR_SCALE_LIMIT) {
- mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
- return;
- }
- float extraValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction) - 1;
- float distanceLeft = 1 - currentFraction;
-
- animator.setFloatValues(currentFraction, 1);
- mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
- float scaledF = (f - currentFraction) / distanceLeft;
-
- double angle = scaledF * 1.5 * Math.PI;
- float amplitude = (1 - scaledF) * (1 - scaledF) * extraValue;
- return 1 + (float) (amplitude * Math.sin(angle));
- };
- animator.setDuration(200).setInterpolator(LINEAR);
- return;
+ } else {
+ mFinishFastOnSecondTouch = false;
}
- mFinishFastOnSecondTouch = false;
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 2579bc2..febe360 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
@@ -63,12 +62,14 @@
@Override
public void setStateWithAnimation(final LauncherState toState,
AnimatorSetBuilder builder, AnimationConfig config) {
+ if (!config.playAtomicComponent()) {
+ // The entire recents animation is played atomically.
+ return;
+ }
PropertySetter setter = config.getPropertySetter(builder);
float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
- setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0],
- builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
- setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
- builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
+ setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0], LINEAR);
+ setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1], LINEAR);
setter.setFloat(mRecentsViewContainer, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
AGGRESSIVE_EASE_IN_OUT);
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 9416a29..ad4af62 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -203,7 +203,12 @@
}
private boolean shouldIgnoreSwipeUpEnabledSettings() {
- String sdkInt = getSystemProperty("ro.product.first_api_level", "0");
+ int deviceApiLevel = Build.VERSION.SDK_INT;
+
+ // Note: on factory ROM devices, this first_api_level property is intentionally not set.
+ // deviceApiLevel is used in these case.
+ String sdkInt = getSystemProperty("ro.product.first_api_level",
+ Integer.toString(deviceApiLevel));
try {
return Integer.parseInt(sdkInt) >= Build.VERSION_CODES.P;
} catch (Exception e) {
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index b1fa5e2..2ff70dd 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -43,8 +43,7 @@
extraSpace = 0;
} else {
Resources res = context.getResources();
- extraSpace = dp.hotseatBarSizePx + res.getDimension(R.dimen.shelf_surface_top_padding)
- + res.getDimension(R.dimen.shelf_surface_radius);
+ extraSpace = dp.hotseatBarSizePx + res.getDimension(R.dimen.vertical_drag_handle_size);
}
calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
index 34f580b..48b07a7 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
@@ -15,7 +15,9 @@
*/
package com.android.quickstep.util;
-import android.animation.TimeInterpolator;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
@@ -30,7 +32,7 @@
public class TaskViewDrawable extends Drawable {
- public static FloatProperty<TaskViewDrawable> PROGRESS =
+ public static final FloatProperty<TaskViewDrawable> PROGRESS =
new FloatProperty<TaskViewDrawable>("progress") {
@Override
public void setValue(TaskViewDrawable taskViewDrawable, float v) {
@@ -43,8 +45,10 @@
}
};
- private static final TimeInterpolator ICON_SIZE_INTERPOLATOR =
- (t) -> (Math.max(t, 0.3f) - 0.3f) / 0.7f;
+ /**
+ * The progress at which we play the atomic icon scale animation.
+ */
+ private static final float ICON_SCALE_THRESHOLD = 0.95f;
private final RecentsView mParent;
private final View mIconView;
@@ -55,11 +59,15 @@
private final ClipAnimationHelper mClipAnimationHelper;
private float mProgress = 1;
+ private boolean mPassedIconScaleThreshold;
+ private ValueAnimator mIconScaleAnimator;
+ private float mIconScale;
public TaskViewDrawable(TaskView tv, RecentsView parent) {
mParent = parent;
mIconView = tv.getIconView();
mIconPos = new int[2];
+ mIconScale = mIconView.getScaleX();
Utilities.getDescendantCoordRelativeToAncestor(mIconView, parent, mIconPos, true);
mThumbnailView = tv.getThumbnail();
@@ -70,6 +78,37 @@
public void setProgress(float progress) {
mProgress = progress;
mParent.invalidate();
+ boolean passedIconScaleThreshold = progress <= ICON_SCALE_THRESHOLD;
+ if (mPassedIconScaleThreshold != passedIconScaleThreshold) {
+ mPassedIconScaleThreshold = passedIconScaleThreshold;
+ animateIconScale(mPassedIconScaleThreshold ? 0 : 1);
+ }
+ }
+
+ private void animateIconScale(float toScale) {
+ if (mIconScaleAnimator != null) {
+ mIconScaleAnimator.cancel();
+ }
+ mIconScaleAnimator = ValueAnimator.ofFloat(mIconScale, toScale);
+ mIconScaleAnimator.addUpdateListener(valueAnimator -> {
+ mIconScale = (float) valueAnimator.getAnimatedValue();
+ if (mProgress > ICON_SCALE_THRESHOLD) {
+ // Speed up the icon scale to ensure it is 1 when progress is 1.
+ float iconProgress = (mProgress - ICON_SCALE_THRESHOLD) / (1 - ICON_SCALE_THRESHOLD);
+ if (iconProgress > mIconScale) {
+ mIconScale = iconProgress;
+ }
+ }
+ invalidateSelf();
+ });
+ mIconScaleAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIconScaleAnimator = null;
+ }
+ });
+ mIconScaleAnimator.setDuration(TaskView.SCALE_ICON_DURATION);
+ mIconScaleAnimator.start();
}
@Override
@@ -81,8 +120,7 @@
canvas.save();
canvas.translate(mIconPos[0], mIconPos[1]);
- float scale = ICON_SIZE_INTERPOLATOR.getInterpolation(mProgress);
- canvas.scale(scale, scale, mIconView.getWidth() / 2, mIconView.getHeight() / 2);
+ canvas.scale(mIconScale, mIconScale, mIconView.getWidth() / 2, mIconView.getHeight() / 2);
mIconView.draw(canvas);
canvas.restore();
}
diff --git a/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java b/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java
deleted file mode 100644
index 5e9cd6e..0000000
--- a/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 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.quickstep.views;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import com.android.launcher3.R;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.launcher3.views.LauncherDragIndicator;
-
-public class QuickstepDragIndicator extends LauncherDragIndicator {
-
- public QuickstepDragIndicator(Context context) {
- super(context);
- }
-
- public QuickstepDragIndicator(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public QuickstepDragIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- private boolean isInOverview() {
- return mLauncher.isInState(OVERVIEW);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setContentDescription(getContext().getString(R.string.all_apps_button_label));
- }
-
- @Override
- protected void initCustomActions(AccessibilityNodeInfo info) {
- if (!isInOverview()) {
- super.initCustomActions(info);
- }
- }
-
- @Override
- public void onClick(View view) {
- mLauncher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
- ControlType.ALL_APPS_BUTTON,
- isInOverview() ? ContainerType.TASKSWITCHER : ContainerType.WORKSPACE);
- mLauncher.getStateManager().goToState(ALL_APPS);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 92f8aef..5035721 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -23,6 +23,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
@@ -965,15 +966,13 @@
if (currTask == null) {
return;
}
- currTask.setScaleX(mAdjacentScale);
- currTask.setScaleY(mAdjacentScale);
+ currTask.setZoomScale(mAdjacentScale);
if (mCurrentPage - 1 >= 0) {
TaskView adjacentTask = getPageAt(mCurrentPage - 1);
float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
mAdjacentScale, 0);
- adjacentTask.setScaleX(scaleAndTranslation[0]);
- adjacentTask.setScaleY(scaleAndTranslation[0]);
+ adjacentTask.setZoomScale(scaleAndTranslation[0]);
adjacentTask.setTranslationX(-scaleAndTranslation[1]);
adjacentTask.setTranslationY(scaleAndTranslation[2]);
}
@@ -981,8 +980,7 @@
TaskView adjacentTask = getPageAt(mCurrentPage + 1);
float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
mAdjacentScale, 0);
- adjacentTask.setScaleX(scaleAndTranslation[0]);
- adjacentTask.setScaleY(scaleAndTranslation[0]);
+ adjacentTask.setZoomScale(scaleAndTranslation[0]);
adjacentTask.setTranslationX(scaleAndTranslation[1]);
adjacentTask.setTranslationY(scaleAndTranslation[2]);
}
@@ -991,7 +989,7 @@
private float[] getAdjacentScaleAndTranslation(TaskView currTask, TaskView adjacentTask,
float currTaskToScale, float currTaskToTranslationY) {
float displacement = currTask.getWidth() * (currTaskToScale - currTask.getCurveScale());
- sTempFloatArray[0] = currTaskToScale * adjacentTask.getCurveScale();
+ sTempFloatArray[0] = currTaskToScale;
sTempFloatArray[1] = mIsRtl ? -displacement : displacement;
sTempFloatArray[2] = currTaskToTranslationY;
return sTempFloatArray;
@@ -1132,13 +1130,15 @@
return anim;
}
- private ObjectAnimator createAnimForChild(View child, float[] toScaleAndTranslation) {
- return ObjectAnimator.ofPropertyValuesHolder(child,
+ private Animator createAnimForChild(TaskView child, float[] toScaleAndTranslation) {
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(ObjectAnimator.ofFloat(child, TaskView.ZOOM_SCALE, toScaleAndTranslation[0]));
+ anim.play(ObjectAnimator.ofPropertyValuesHolder(child,
new PropertyListBuilder()
- .scale(child.getScaleX() * toScaleAndTranslation[0])
.translationX(toScaleAndTranslation[1])
.translationY(toScaleAndTranslation[2])
- .build());
+ .build()));
+ return anim;
}
public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index aa04672..b47af2d 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -31,7 +31,6 @@
import android.util.AttributeSet;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.uioverrides.OverviewState;
import com.android.launcher3.util.Themes;
@@ -49,8 +48,6 @@
private static final int THRESHOLD_ALPHA_DARK = 102;
private static final int THRESHOLD_ALPHA_LIGHT = 46;
- private final Launcher mLauncher;
-
// In transposed layout, we simply draw a flat color.
private boolean mDrawingFlatColor;
@@ -58,7 +55,6 @@
private final int mEndAlpha;
private final int mThresholdAlpha;
private final float mRadius;
- private final float mTopPadding;
private final float mMaxScrimAlpha;
private final Paint mPaint;
@@ -77,15 +73,12 @@
public ShelfScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
-
- mLauncher = Launcher.getLauncher(context);
mMaxScrimAlpha = OVERVIEW.getWorkspaceScrimAlpha(mLauncher);
mEndAlpha = Color.alpha(mEndScrim);
mThresholdAlpha = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark)
? THRESHOLD_ALPHA_DARK : THRESHOLD_ALPHA_LIGHT;
mRadius = mLauncher.getResources().getDimension(R.dimen.shelf_surface_radius);
- mTopPadding = mLauncher.getResources().getDimension(R.dimen.shelf_surface_top_padding);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// Just assume the easiest UI for now, until we have the proper layout information.
@@ -110,10 +103,10 @@
mRemainingScreenPathValid = false;
updateColors();
}
+ updateDragHandleAlpha();
invalidate();
}
-
@Override
public void updateColors() {
super.updateColors();
@@ -131,6 +124,7 @@
(1 - mProgress) / (1 - mMoveThreshold)));
mShelfColor = setAlphaComponent(mEndScrim, alpha);
}
+
mRemainingScreenColor = 0;
} else if (mProgress <= 0) {
mScrimMoveFactor = 0;
@@ -150,23 +144,42 @@
}
@Override
+ protected void updateDragHandleAlpha() {
+ if (mDrawingFlatColor) {
+ super.updateDragHandleAlpha();
+ } else if (mDragHandle != null) {
+ mDragHandle.setAlpha(255);
+ }
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
+ float translate = drawBackground(canvas);
+
+ if (mDragHandle != null) {
+ canvas.translate(0, -translate);
+ mDragHandle.draw(canvas);
+ canvas.translate(0, translate);
+ }
+ }
+
+ private float drawBackground(Canvas canvas) {
if (mDrawingFlatColor) {
if (mCurrentFlatColor != 0) {
canvas.drawColor(mCurrentFlatColor);
}
- return;
+ return 0;
}
if (mShelfColor == 0) {
- return;
+ return 0;
} else if (mScrimMoveFactor <= 0) {
canvas.drawColor(mShelfColor);
- return;
+ return getHeight();
}
float minTop = getHeight() - mMinSize;
- float top = minTop * mScrimMoveFactor - mTopPadding - mRadius;
+ float top = minTop * mScrimMoveFactor - mDragHandleSize;
// Draw the scrim over the remaining screen if needed.
if (mRemainingScreenColor != 0) {
@@ -192,5 +205,6 @@
mPaint.setColor(mShelfColor);
canvas.drawRoundRect(0, top, getWidth(), getHeight() + mRadius,
mRadius, mRadius, mPaint);
+ return minTop - mDragHandleSize - top;
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index b8b9196..4f447b1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -30,14 +30,15 @@
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.util.Log;
+import android.util.Property;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
-import android.widget.ImageView;
-
import android.widget.Toast;
+
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
@@ -76,12 +77,26 @@
*/
private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
- private static final long SCALE_ICON_DURATION = 120;
+ public static final long SCALE_ICON_DURATION = 120;
+
+ public static final Property<TaskView, Float> ZOOM_SCALE =
+ new FloatProperty<TaskView>("zoomScale") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setZoomScale(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mZoomScale;
+ }
+ };
private Task mTask;
private TaskThumbnailView mSnapshotView;
private IconView mIconView;
private float mCurveScale;
+ private float mZoomScale;
private float mCurveDimAlpha;
private Animator mDimAlphaAnim;
@@ -207,8 +222,7 @@
}
public void resetVisualProperties() {
- setScaleX(1f);
- setScaleY(1f);
+ setZoomScale(1);
setTranslationX(0f);
setTranslationY(0f);
setTranslationZ(0);
@@ -226,9 +240,7 @@
mSnapshotView.setDimAlpha(mCurveDimAlpha);
}
- mCurveScale = getCurveScaleForCurveInterpolation(curveInterpolation);
- setScaleX(mCurveScale);
- setScaleY(mCurveScale);
+ setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
}
@Override
@@ -247,10 +259,26 @@
return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
}
+ private void setCurveScale(float curveScale) {
+ mCurveScale = curveScale;
+ onScaleChanged();
+ }
+
public float getCurveScale() {
return mCurveScale;
}
+ public void setZoomScale(float adjacentScale) {
+ mZoomScale = adjacentScale;
+ onScaleChanged();
+ }
+
+ private void onScaleChanged() {
+ float scale = mCurveScale * mZoomScale;
+ setScaleX(scale);
+ setScaleY(scale);
+ }
+
@Override
public boolean hasOverlappingRendering() {
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
diff --git a/res/animator-v23/discovery_bounce.xml b/res/animator-v23/discovery_bounce.xml
index 8d0e8fd..f554853 100644
--- a/res/animator-v23/discovery_bounce.xml
+++ b/res/animator-v23/discovery_bounce.xml
@@ -26,14 +26,14 @@
android:fraction="0"
android:value="1f" />
<keyframe
- android:fraction="0.346"
+ android:fraction="0.246"
android:value="1f" />
<keyframe
android:fraction=".423"
android:interpolator="@interpolator/disco_bounce"
- android:value="0.9438f" />
+ android:value="0.9738f" />
<keyframe
- android:fraction="0.654"
+ android:fraction="0.754"
android:interpolator="@interpolator/disco_bounce"
android:value="1f" />
<keyframe
diff --git a/res/drawable/all_apps_handle_landscape.xml b/res/drawable/drag_handle_indicator.xml
similarity index 77%
rename from res/drawable/all_apps_handle_landscape.xml
rename to res/drawable/drag_handle_indicator.xml
index 15518ff..b01b84a 100644
--- a/res/drawable/all_apps_handle_landscape.xml
+++ b/res/drawable/drag_handle_indicator.xml
@@ -15,25 +15,25 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="@dimen/dynamic_grid_min_page_indicator_size"
- android:height="@dimen/dynamic_grid_min_page_indicator_size"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0" >
+ android:width="@dimen/vertical_drag_handle_size"
+ android:height="@dimen/vertical_drag_handle_size"
+ android:viewportWidth="36.0"
+ android:viewportHeight="36.0" >
<group
- android:translateX="17.5"
- android:translateY="17.5">
+ android:translateX="11.5"
+ android:translateY="11.5">
<path
android:pathData="M2 8.5L6.5 4L11 8.5"
android:strokeColor="?attr/workspaceAmbientShadowColor"
- android:strokeWidth="4"
+ android:strokeWidth="3.6"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M2 8.5L6.5 4L11 8.5"
android:strokeColor="?attr/workspaceTextColor"
- android:strokeWidth="2"
+ android:strokeWidth="1.8"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</group>
diff --git a/res/drawable/ic_drag_indicator.xml b/res/drawable/ic_drag_indicator.xml
deleted file mode 100644
index d50bdd3..0000000
--- a/res/drawable/ic_drag_indicator.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="2dp"
- android:width="16dp"
- android:viewportHeight="2.0"
- android:viewportWidth="16.0">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M1,0h14c0.55,0,1,0.45,1,1s-0.45,1-1,1H1C0.45,2,0,1.55,0,1S0.45,0,1,0z"/>
-</vector>
\ No newline at end of file
diff --git a/res/layout/drag_handle_indicator.xml b/res/layout/drag_handle_indicator.xml
deleted file mode 100644
index d5a7b8a..0000000
--- a/res/layout/drag_handle_indicator.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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.
--->
-<com.android.launcher3.views.LauncherDragIndicator
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/drag_indicator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:contentDescription="@string/all_apps_button_label"
- android:scaleType="centerInside"
- android:tint="?attr/workspaceTextColor" />
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index cd8a425..ec8bd5c 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -67,9 +67,6 @@
android:layout_height="match_parent"
android:visibility="invisible" />
- <include android:id="@+id/drag_indicator"
- layout="@layout/drag_handle_indicator" />
-
<!-- DO NOT CHANGE THE ID -->
<include
android:id="@+id/hotseat"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b1ad11e..cd050e8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -42,6 +42,7 @@
<dimen name="all_apps_scrim_radius">8dp</dimen>
<dimen name="all_apps_scrim_margin">8dp</dimen>
<dimen name="all_apps_scrim_blur">4dp</dimen>
+ <dimen name="vertical_drag_handle_size">24dp</dimen>
<!-- Drop target bar -->
<dimen name="dynamic_grid_drop_target_size">48dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5dc7844..65fffd3 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -310,7 +310,7 @@
<!-- Accessibility action to show quick actions menu for an icon. [CHAR_LIMIT=30] -->
<string name="action_deep_shortcut">Shortcuts</string>
- <!-- Accessibility description when the shortcuts menu has notifications as well as shortcuts. [CHAR_LIMIT=50] -->
+ <!-- Accessibility description when the context menu of a launcher icon that has notifications as well as shortcuts (providing quick access to app's actions). The "shortcuts" translation should be consistent with the one for action_deep_shortcut. [CHAR_LIMIT=50] -->
<string name="shortcuts_menu_with_notifications_description">Shortcuts and notifications
</string>
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8088f64..2f83f45 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -196,7 +196,6 @@
private final int[] mTmpAddItemCellCoordinates = new int[2];
@Thunk Hotseat mHotseat;
- private View mDragHandleIndicator;
@Nullable private View mHotseatSearchBox;
private DropTargetBar mDropTargetBar;
@@ -933,7 +932,6 @@
mOverviewPanel = findViewById(R.id.overview_panel);
mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
mHotseat = findViewById(R.id.hotseat);
- mDragHandleIndicator = findViewById(R.id.drag_indicator);
mHotseatSearchBox = findViewById(R.id.search_container_hotseat);
mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -1194,10 +1192,6 @@
return mHotseat;
}
- public View getDragHandleIndicator() {
- return mDragHandleIndicator;
- }
-
public View getHotseatSearchBox() {
return mHotseatSearchBox;
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index f548095..4e10ab6 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
+import android.graphics.Rect;
import android.view.View;
import android.view.animation.Interpolator;
@@ -50,7 +51,6 @@
public static final int ALL_APPS_HEADER = 1 << 2;
public static final int ALL_APPS_HEADER_EXTRA = 1 << 3; // e.g. app predictions
public static final int ALL_APPS_CONTENT = 1 << 4;
- public static final int DRAG_HANDLE_INDICATOR = 1 << 5;
protected static final int FLAG_MULTI_PAGE = 1 << 0;
protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 1;
@@ -86,6 +86,8 @@
public static final LauncherState FAST_OVERVIEW = new FastOverviewState(3);
public static final LauncherState ALL_APPS = new AllAppsState(4);
+ protected static final Rect sTempRect = new Rect();
+
public final int ordinal;
/**
@@ -194,9 +196,9 @@
public int getVisibleElements(Launcher launcher) {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
- return HOTSEAT_ICONS | DRAG_HANDLE_INDICATOR;
+ return HOTSEAT_ICONS;
}
- return HOTSEAT_ICONS | DRAG_HANDLE_INDICATOR | HOTSEAT_SEARCH_BOX;
+ return HOTSEAT_ICONS | HOTSEAT_SEARCH_BOX;
}
/**
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 1b9ac21..cf0c7fc 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -24,6 +24,7 @@
import android.animation.AnimatorSet;
import android.os.Handler;
import android.os.Looper;
+import android.support.annotation.IntDef;
import android.view.View;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -33,6 +34,8 @@
import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
import com.android.launcher3.uioverrides.UiFactory;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
@@ -80,6 +83,21 @@
public static final String TAG = "StateManager";
+ // We separate the state animations into "atomic" and "non-atomic" components. The atomic
+ // components may be run atomically - that is, all at once, instead of user-controlled. However,
+ // atomic components are not restricted to this purpose; they can be user-controlled alongside
+ // non atomic components as well.
+ @IntDef(flag = true, value = {
+ NON_ATOMIC_COMPONENT,
+ ATOMIC_COMPONENT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AnimationComponents {}
+ public static final int NON_ATOMIC_COMPONENT = 1 << 0;
+ public static final int ATOMIC_COMPONENT = 1 << 1;
+
+ public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_COMPONENT;
+
private final AnimationConfig mConfig = new AnimationConfig();
private final Handler mUiHandler;
private final Launcher mLauncher;
@@ -238,13 +256,21 @@
*/
public AnimatorPlaybackController createAnimationToNewWorkspace(
LauncherState state, long duration) {
- return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null);
+ return createAnimationToNewWorkspace(state, duration, LauncherStateManager.ANIM_ALL);
+ }
+
+ public AnimatorPlaybackController createAnimationToNewWorkspace(
+ LauncherState state, long duration, @AnimationComponents int animComponents) {
+ return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null,
+ animComponents);
}
public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
- AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable) {
+ AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable,
+ @AnimationComponents int animComponents) {
mConfig.reset();
mConfig.userControlled = true;
+ mConfig.animComponents = animComponents;
mConfig.duration = duration;
mConfig.playbackController = AnimatorPlaybackController.wrap(
createAnimationToNewWorkspaceInternal(state, builder, null), duration,
@@ -361,6 +387,7 @@
}
public void setCurrentUserControlledAnimation(AnimatorPlaybackController controller) {
+ clearCurrentAnimation();
setCurrentAnimation(controller.getTarget());
mConfig.userControlled = true;
mConfig.playbackController = controller;
@@ -379,15 +406,10 @@
}
if (mConfig.playbackController != null
&& mConfig.playbackController.getTarget() == childAnim) {
- if (mConfig.mCurrentAnimation != null) {
- mConfig.mCurrentAnimation.removeListener(mConfig);
- mConfig.mCurrentAnimation = null;
- }
- mConfig.playbackController = null;
+ clearCurrentAnimation();
break;
} else if (mConfig.mCurrentAnimation == childAnim) {
- mConfig.mCurrentAnimation.removeListener(mConfig);
- mConfig.mCurrentAnimation = null;
+ clearCurrentAnimation();
break;
}
}
@@ -399,6 +421,14 @@
mConfig.setAnimation(anim, null);
}
+ private void clearCurrentAnimation() {
+ if (mConfig.mCurrentAnimation != null) {
+ mConfig.mCurrentAnimation.removeListener(mConfig);
+ mConfig.mCurrentAnimation = null;
+ }
+ mConfig.playbackController = null;
+ }
+
private class StartAnimRunnable implements Runnable {
private final AnimatorSet mAnim;
@@ -425,6 +455,7 @@
public long duration;
public boolean userControlled;
public AnimatorPlaybackController playbackController;
+ public @AnimationComponents int animComponents = ANIM_ALL;
private PropertySetter mPropertySetter;
private AnimatorSet mCurrentAnimation;
@@ -436,6 +467,7 @@
public void reset() {
duration = 0;
userControlled = false;
+ animComponents = ANIM_ALL;
mPropertySetter = null;
mTargetState = null;
@@ -471,6 +503,14 @@
mTargetState = targetState;
mCurrentAnimation.addListener(this);
}
+
+ public boolean playAtomicComponent() {
+ return (animComponents & ATOMIC_COMPONENT) != 0;
+ }
+
+ public boolean playNonAtomicComponent() {
+ return (animComponents & NON_ATOMIC_COMPONENT) != 0;
+ }
}
public interface StateHandler {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 25d9bb6..d870109 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -82,7 +82,6 @@
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.touch.WorkspaceTouchListener;
-import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 8d3d459..d6b2349 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,12 +18,12 @@
import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.DRAG_HANDLE_INDICATOR;
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.HOTSEAT_SEARCH_BOX;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
import android.view.View;
+import android.view.animation.Interpolator;
import com.android.launcher3.LauncherState.PageAlphaProvider;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
@@ -48,12 +48,12 @@
}
public void setState(LauncherState toState) {
- setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER);
+ setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER, new AnimationConfig());
}
public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
AnimationConfig config) {
- setWorkspaceProperty(toState, config.getPropertySetter(builder));
+ setWorkspaceProperty(toState, config.getPropertySetter(builder), config);
}
public float getFinalScale() {
@@ -63,37 +63,45 @@
/**
* Starts a transition animation for the workspace.
*/
- private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter) {
+ private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter,
+ AnimationConfig config) {
float[] scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
mNewScale = scaleAndTranslation[0];
PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
final int childCount = mWorkspace.getChildCount();
for (int i = 0; i < childCount; i++) {
applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, pageAlphaProvider,
- propertySetter);
+ propertySetter, config);
}
- propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_OUT);
- propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
- scaleAndTranslation[1], Interpolators.ZOOM_OUT);
- propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
- scaleAndTranslation[2], Interpolators.ZOOM_OUT);
int elements = state.getVisibleElements(mLauncher);
- float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
- propertySetter.setViewAlpha(mLauncher.getHotseat().getLayout(), hotseatIconsAlpha,
- pageAlphaProvider.interpolator);
- propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
- hotseatIconsAlpha, pageAlphaProvider.interpolator);
+ boolean playAtomicComponent = config.playAtomicComponent();
+ if (playAtomicComponent) {
+ propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, Interpolators.ZOOM_OUT);
+ float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
+ propertySetter.setViewAlpha(mLauncher.getHotseat().getLayout(), hotseatIconsAlpha,
+ pageAlphaProvider.interpolator);
+ propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
+ hotseatIconsAlpha, pageAlphaProvider.interpolator);
+ }
+
+ if (!config.playNonAtomicComponent()) {
+ // Only the alpha and scale, handled above, are included in the atomic animation.
+ return;
+ }
+
+ Interpolator translationInterpolator = !playAtomicComponent ? Interpolators.LINEAR
+ : Interpolators.ZOOM_OUT;
+ propertySetter.setFloat(mWorkspace, View.TRANSLATION_X,
+ scaleAndTranslation[1], translationInterpolator);
+ propertySetter.setFloat(mWorkspace, View.TRANSLATION_Y,
+ scaleAndTranslation[2], translationInterpolator);
propertySetter.setViewAlpha(mLauncher.getHotseatSearchBox(),
(elements & HOTSEAT_SEARCH_BOX) != 0 ? 1 : 0,
pageAlphaProvider.interpolator);
- propertySetter.setViewAlpha(mLauncher.getDragHandleIndicator(),
- (elements & DRAG_HANDLE_INDICATOR) != 0 ? 1 : 0,
- pageAlphaProvider.interpolator);
-
// Set scrim
propertySetter.setFloat(ViewScrim.get(mWorkspace), ViewScrim.PROGRESS,
state.getWorkspaceScrimAlpha(mLauncher), Interpolators.LINEAR);
@@ -101,17 +109,22 @@
public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
applyChildState(state, cl, childIndex, state.getWorkspacePageAlphaProvider(mLauncher),
- NO_ANIM_PROPERTY_SETTER);
+ NO_ANIM_PROPERTY_SETTER, new AnimationConfig());
}
private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
- PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter) {
+ PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter,
+ AnimationConfig config) {
float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
- propertySetter.setInt(cl.getScrimBackground(),
- DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_OUT);
- propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
- pageAlpha, pageAlphaProvider.interpolator);
+ if (config.playNonAtomicComponent()) {
+ propertySetter.setInt(cl.getScrimBackground(),
+ DRAWABLE_ALPHA, drawableAlpha, Interpolators.ZOOM_OUT);
+ }
+ if (config.playAtomicComponent()) {
+ propertySetter.setFloat(cl.getShortcutsAndWidgets(), View.ALPHA,
+ pageAlpha, pageAlphaProvider.interpolator);
+ }
}
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 6d70a08..113571a 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -101,7 +101,6 @@
mAppsView.setAlpha(1);
mLauncher.getHotseat().setTranslationY(0);
mLauncher.getWorkspace().getPageIndicator().setTranslationY(0);
- mLauncher.getDragHandleIndicator().setTranslationY(0);
}
}
@@ -125,7 +124,6 @@
if (!mIsVerticalLayout) {
mLauncher.getHotseat().setTranslationY(hotseatTranslation);
mLauncher.getWorkspace().getPageIndicator().setTranslationY(hotseatTranslation);
- mLauncher.getDragHandleIndicator().setTranslationY(hotseatTranslation);
}
// Use a light system UI (dark icons) if all apps is behind at least half of the
@@ -168,6 +166,11 @@
return;
}
+ if (!config.playNonAtomicComponent()) {
+ // There is no atomic component for the all apps transition, so just return early.
+ return;
+ }
+
Interpolator interpolator = config.userControlled ? LINEAR : FAST_OUT_SLOW_IN;
ObjectAnimator anim =
ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, mProgress, targetProgress);
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 8f1c8df..e8c5f15 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -156,14 +156,14 @@
float verticalProgress = OVERVIEW.getVerticalProgress(launcher);
TimeInterpolator pathInterpolator = new PathInterpolator(0.35f, 0, 0.5f, 1);
- Keyframe keyframe3 = Keyframe.ofFloat(0.423f, verticalProgress - (1 - 0.9438f));
+ Keyframe keyframe3 = Keyframe.ofFloat(0.423f, verticalProgress - (1 - 0.9738f));
keyframe3.setInterpolator(pathInterpolator);
- Keyframe keyframe4 = Keyframe.ofFloat(0.654f, verticalProgress);
+ Keyframe keyframe4 = Keyframe.ofFloat(0.754f, verticalProgress);
keyframe4.setInterpolator(pathInterpolator);
PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofKeyframe("progress",
Keyframe.ofFloat(0, verticalProgress),
- Keyframe.ofFloat(0.346f, verticalProgress), keyframe3, keyframe4,
+ Keyframe.ofFloat(0.246f, verticalProgress), keyframe3, keyframe4,
Keyframe.ofFloat(1f, verticalProgress));
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(null,
new PropertyValuesHolder[]{propertyValuesHolder});
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
index b209a2d..dae2dbc 100644
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
@@ -32,7 +32,6 @@
public class AnimatorSetBuilder {
public static final int ANIM_VERTICAL_PROGRESS = 0;
- public static final int ANIM_OVERVIEW_TRANSLATION = 1;
protected final ArrayList<Animator> mAnims = new ArrayList<>();
@@ -56,9 +55,9 @@
AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
anim.playTogether(mAnims);
if (!mOnFinishRunnables.isEmpty()) {
- anim.addListener(new AnimatorListenerAdapter() {
+ anim.addListener(new AnimationSuccessListener() {
@Override
- public void onAnimationEnd(Animator animation) {
+ public void onAnimationSuccess(Animator animation) {
for (Runnable onFinishRunnable : mOnFinishRunnables) {
onFinishRunnable.run();
}
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 06ddf22..0d388fe 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -38,6 +38,7 @@
public static final Interpolator DEACCEL = new DecelerateInterpolator();
public static final Interpolator DEACCEL_1_5 = new DecelerateInterpolator(1.5f);
+ public static final Interpolator DEACCEL_1_7 = new DecelerateInterpolator(1.7f);
public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2);
public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f);
public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f);
@@ -57,8 +58,6 @@
EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase);
}
- public static final Interpolator APP_CLOSE_ALPHA = new PathInterpolator(0.4f, 0, 1f, 1f);
-
public static final Interpolator OVERSHOOT_0 = new OvershootInterpolator(0);
public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index e1e1f83..1e5f854 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -288,7 +288,7 @@
if (mScaledMaskPath != null) {
mBgSpringDrawable.setColorFilter(mBaseFilter);
- mBgSpringDrawable.setColorFilter(mBaseFilter);
+ mFgSpringDrawable.setColorFilter(mBaseFilter);
mBadge.setColorFilter(mBaseFilter);
}
} else {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index bad4976..6e48c36 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -18,16 +18,28 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.ANIM_ALL;
+import static com.android.launcher3.LauncherStateManager.ATOMIC_COMPONENT;
+import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
+import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -41,11 +53,17 @@
implements TouchController, SwipeDetector.Listener {
private static final String TAG = "ASCTouchController";
- public static final float RECATCH_REJECTION_FRACTION = .0875f;
// Progress after which the transition is assumed to be a success in case user does not fling
public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+ /**
+ * Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this.
+ */
+ public static final float ATOMIC_OVERVIEW_ANIM_THRESHOLD = 0.5f;
+ private static final long ATOMIC_NORMAL_TO_OVERVIEW_DURATION = 120;
+ private static final long ATOMIC_OVERVIEW_TO_NORMAL_DURATION = 200;
+
protected final Launcher mLauncher;
protected final SwipeDetector mDetector;
@@ -61,6 +79,18 @@
// Ratio of transition process [0, 1] to drag displacement (px)
private float mProgressMultiplier;
private float mDisplacementShift;
+ private boolean mCanBlockFling;
+ private boolean mBlockFling;
+
+ private AnimatorSet mAtomicAnim;
+ private boolean mPassedOverviewAtomicThreshold;
+ // mAtomicAnim plays the atomic components of the state animations when we pass the threshold.
+ // However, if we reinit to transition to a new state (e.g. OVERVIEW -> ALL_APPS) before the
+ // atomic animation finishes, we only control the non-atomic components so that we don't
+ // interfere with the atomic animation. When the atomic animation ends, we start controlling
+ // the atomic components as well, using this controller.
+ private AnimatorPlaybackController mAtomicComponentsController;
+ private float mAtomicComponentsStartProgress;
public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
mLauncher = l;
@@ -83,14 +113,8 @@
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
- if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
- } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
- } else {
- directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
- ignoreSlopWhenSettling = true;
- }
+ directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+ ignoreSlopWhenSettling = true;
} else {
directionsToDetectScroll = getSwipeDirection();
if (directionsToDetectScroll == 0) {
@@ -138,7 +162,7 @@
protected abstract LauncherState getTargetState(LauncherState fromState,
boolean isDragTowardPositive);
- protected abstract float initCurrentAnimation();
+ protected abstract float initCurrentAnimation(@AnimationComponents int animComponents);
/**
* Returns the container that the touch started from when leaving NORMAL state.
@@ -169,24 +193,55 @@
mToState = newToState;
mStartProgress = 0;
+ mPassedOverviewAtomicThreshold = false;
if (mCurrentAnimation != null) {
mCurrentAnimation.setOnCancelRunnable(null);
}
- mProgressMultiplier = initCurrentAnimation();
+ int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
+ ? NON_ATOMIC_COMPONENT : ANIM_ALL;
+ if (mAtomicAnim != null) {
+ // Control the non-atomic components until the atomic animation finishes, then control
+ // the atomic components as well.
+ animComponents = NON_ATOMIC_COMPONENT;
+ mAtomicAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animation) {
+ cancelAtomicComponentsController();
+ mAtomicComponentsStartProgress = mCurrentAnimation.getProgressFraction();
+ long duration = (long) (getShiftRange() * 2);
+ mAtomicComponentsController = AnimatorPlaybackController.wrap(
+ createAtomicAnimForState(mToState, duration), duration);
+ mAtomicComponentsController.dispatchOnStart();
+ }
+ });
+ }
+ if (goingBetweenNormalAndOverview(mFromState, mToState)) {
+ cancelAtomicComponentsController();
+ }
+ mProgressMultiplier = initCurrentAnimation(animComponents);
mCurrentAnimation.dispatchOnStart();
return true;
}
+ private boolean goingBetweenNormalAndOverview(LauncherState fromState, LauncherState toState) {
+ return (fromState == NORMAL || fromState == OVERVIEW)
+ && (toState == NORMAL || toState == OVERVIEW)
+ && mPendingAnimation == null;
+ }
+
@Override
public void onDragStart(boolean start) {
if (mCurrentAnimation == null) {
mFromState = mToState = null;
+ mAtomicComponentsController = null;
reinitCurrentAnimation(false, mDetector.wasInitialTouchPositive());
mDisplacementShift = 0;
} else {
mCurrentAnimation.pause();
mStartProgress = mCurrentAnimation.getProgressFraction();
}
+ mCanBlockFling = mFromState == NORMAL;
+ mBlockFling = false;
}
@Override
@@ -198,17 +253,70 @@
if (progress <= 0) {
if (reinitCurrentAnimation(false, isDragTowardPositive)) {
mDisplacementShift = displacement;
+ mBlockFling = mCanBlockFling;
}
} else if (progress >= 1) {
if (reinitCurrentAnimation(true, isDragTowardPositive)) {
mDisplacementShift = displacement;
+ mBlockFling = mCanBlockFling;
}
+ } else if (Math.abs(velocity) < SwipeDetector.RELEASE_VELOCITY_PX_MS) {
+ // We prevent flinging after passing a state, but allow it if the user pauses briefly.
+ mBlockFling = false;
}
+
return true;
}
protected void updateProgress(float fraction) {
mCurrentAnimation.setPlayFraction(fraction);
+ if (mAtomicComponentsController != null) {
+ mAtomicComponentsController.setPlayFraction(fraction - mAtomicComponentsStartProgress);
+ }
+ maybeUpdateAtomicAnim(mFromState, mToState, fraction);
+ }
+
+ /**
+ * When going between normal and overview states, see if we passed the overview threshold and
+ * play the appropriate atomic animation if so.
+ */
+ private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
+ float progress) {
+ if (!goingBetweenNormalAndOverview(fromState, toState)) {
+ return;
+ }
+ float threshold = toState == OVERVIEW ? ATOMIC_OVERVIEW_ANIM_THRESHOLD
+ : 1f - ATOMIC_OVERVIEW_ANIM_THRESHOLD;
+ boolean passedThreshold = progress >= threshold;
+ if (passedThreshold != mPassedOverviewAtomicThreshold) {
+ LauncherState targetState = passedThreshold ? toState : fromState;
+ mPassedOverviewAtomicThreshold = passedThreshold;
+ if (mAtomicAnim != null) {
+ mAtomicAnim.cancel();
+ }
+ long duration = targetState == OVERVIEW ? ATOMIC_NORMAL_TO_OVERVIEW_DURATION
+ : ATOMIC_OVERVIEW_TO_NORMAL_DURATION;
+ mAtomicAnim = createAtomicAnimForState(targetState, duration);
+ mAtomicAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAtomicAnim = null;
+ }
+ });
+ mAtomicAnim.start();
+ mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
+ }
+ }
+
+ private AnimatorSet createAtomicAnimForState(LauncherState targetState, long duration) {
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ AnimationConfig config = new AnimationConfig();
+ config.animComponents = ATOMIC_COMPONENT;
+ config.duration = duration;
+ for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
+ handler.setStateWithAnimation(targetState, builder, config);
+ }
+ return builder.build();
}
@Override
@@ -217,6 +325,11 @@
final LauncherState targetState;
final float progress = mCurrentAnimation.getProgressFraction();
+ boolean blockedFling = fling && mBlockFling;
+ if (blockedFling) {
+ fling = false;
+ }
+
if (fling) {
logAction = Touch.FLING;
targetState =
@@ -231,6 +344,8 @@
final float endProgress;
final float startProgress;
final long duration;
+ // Increase the duration if we prevented the fling, as we are going against a high velocity.
+ final long durationMultiplier = blockedFling && targetState == mFromState ? 6 : 1;
if (targetState == mToState) {
endProgress = 1;
@@ -241,7 +356,7 @@
startProgress = Utilities.boundToRange(
progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
duration = SwipeDetector.calculateDuration(velocity,
- endProgress - Math.max(progress, 0));
+ endProgress - Math.max(progress, 0)) * durationMultiplier;
}
} else {
// Let the state manager know that the animation didn't go to the target state,
@@ -259,16 +374,65 @@
startProgress = Utilities.boundToRange(
progress + velocity * SINGLE_FRAME_MS * mProgressMultiplier, 0f, 1f);
duration = SwipeDetector.calculateDuration(velocity,
- Math.min(progress, 1) - endProgress);
+ Math.min(progress, 1) - endProgress) * durationMultiplier;
}
}
mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
anim.setFloatValues(startProgress, endProgress);
- updateSwipeCompleteAnimation(anim, duration, targetState, velocity, fling);
+ maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
+ updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
+ targetState, velocity, fling);
mCurrentAnimation.dispatchOnStart();
anim.start();
+ if (mAtomicAnim == null) {
+ startAtomicComponentsAnim(endProgress, anim.getDuration());
+ } else {
+ mAtomicAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ startAtomicComponentsAnim(endProgress, anim.getDuration());
+ }
+ });
+ }
+ }
+
+ /**
+ * Animates the atomic components from the current progress to the final progress.
+ *
+ * Note that this only applies when we are controlling the atomic components separately from
+ * the non-atomic components, which only happens if we reinit before the atomic animation
+ * finishes.
+ */
+ private void startAtomicComponentsAnim(float toProgress, long duration) {
+ if (mAtomicComponentsController != null) {
+ ValueAnimator atomicAnim = mAtomicComponentsController.getAnimationPlayer();
+ atomicAnim.setFloatValues(mAtomicComponentsController.getProgressFraction(), toProgress);
+ atomicAnim.setDuration(duration);
+ atomicAnim.start();
+ atomicAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAtomicComponentsController = null;
+ }
+ });
+ }
+ }
+
+ private long getRemainingAtomicDuration() {
+ if (mAtomicAnim == null) {
+ return 0;
+ }
+ if (Utilities.ATLEAST_OREO) {
+ return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime();
+ } else {
+ long remainingDuration = 0;
+ for (Animator anim : mAtomicAnim.getChildAnimations()) {
+ remainingDuration = Math.max(remainingDuration, anim.getDuration());
+ }
+ return remainingDuration;
+ }
}
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
@@ -310,7 +474,15 @@
protected void clearState() {
mCurrentAnimation = null;
+ cancelAtomicComponentsController();
mDetector.finishedScrolling();
mDetector.setDetectableScrollConditions(0, false);
}
+
+ private void cancelAtomicComponentsController() {
+ if (mAtomicComponentsController != null) {
+ mAtomicComponentsController.getAnimationPlayer().cancel();
+ mAtomicComponentsController = null;
+ }
+ }
}
diff --git a/src/com/android/launcher3/views/LauncherDragIndicator.java b/src/com/android/launcher3/views/LauncherDragIndicator.java
deleted file mode 100644
index 986e4be..0000000
--- a/src/com/android/launcher3/views/LauncherDragIndicator.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2018 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.views;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-
-public class LauncherDragIndicator extends ImageView implements Insettable, OnClickListener {
-
- private static final int WALLPAPERS = R.string.wallpaper_button_text;
- private static final int WIDGETS = R.string.widget_button_text;
- private static final int SETTINGS = R.string.settings_button_text;
-
- protected final Launcher mLauncher;
-
- public LauncherDragIndicator(Context context) {
- this(context, null);
- }
-
- public LauncherDragIndicator(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public LauncherDragIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
- setOnClickListener(this);
- }
-
- @Override
- public void setInsets(Rect insets) {
- DeviceProfile grid = mLauncher.getDeviceProfile();
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-
- if (grid.isVerticalBarLayout()) {
- if (grid.isSeascape()) {
- lp.leftMargin = grid.hotseatBarSidePaddingPx;
- lp.rightMargin = insets.right;
- lp.gravity = Gravity.RIGHT | Gravity.BOTTOM;
- } else {
- lp.leftMargin = insets.left;
- lp.rightMargin = grid.hotseatBarSidePaddingPx;
- lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
- }
- lp.bottomMargin = grid.workspacePadding.bottom;
- setImageResource(R.drawable.all_apps_handle_landscape);
- } else {
- lp.leftMargin = lp.rightMargin = 0;
- lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- lp.bottomMargin = getPortraitBottomMargin(grid, insets);
- setImageResource(R.drawable.ic_drag_indicator);
- }
-
- lp.width = lp.height = grid.pageIndicatorSizePx;
- setLayoutParams(lp);
- }
-
- protected int getPortraitBottomMargin(DeviceProfile grid, Rect insets) {
- return grid.hotseatBarSizePx + insets.bottom - grid.pageIndicatorSizePx;
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- initCustomActions(info);
- }
-
- protected void initCustomActions(AccessibilityNodeInfo info) {
- Context context = getContext();
- if (Utilities.isWallpaperAllowed(context)) {
- info.addAction(new AccessibilityAction(WALLPAPERS, context.getText(WALLPAPERS)));
- }
- info.addAction(new AccessibilityAction(WIDGETS, context.getText(WIDGETS)));
- info.addAction(new AccessibilityAction(SETTINGS, context.getText(SETTINGS)));
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (action == WALLPAPERS) {
- return OptionsPopupView.startWallpaperPicker(this);
- } else if (action == WIDGETS) {
- return OptionsPopupView.onWidgetsClicked(this);
- } else if (action == SETTINGS) {
- return OptionsPopupView.startSettings(this);
- }
- return super.performAccessibilityAction(action, arguments);
- }
-
- @Override
- public void onClick(View view) {
- if (!mLauncher.isInState(ALL_APPS)) {
- mLauncher.getUserEventDispatcher().logActionOnControl(
- Action.Touch.TAP, ControlType.ALL_APPS_BUTTON);
- mLauncher.getStateManager().goToState(ALL_APPS);
- }
- }
-}
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index b49b565..28602f5 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -15,28 +15,63 @@
*/
package com.android.launcher3.views;
+import static android.content.Context.ACCESSIBILITY_SERVICE;
import static android.support.v4.graphics.ColorUtils.compositeColors;
import static android.support.v4.graphics.ColorUtils.setAlphaComponent;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.LauncherStateManager.StateListener;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.Themes;
+import java.util.List;
+
/**
* Simple scrim which draws a flat color
*/
-public class ScrimView extends View implements Insettable, OnChangeListener {
+public class ScrimView extends View implements Insettable, OnChangeListener,
+ AccessibilityStateChangeListener, StateListener {
+ private static final int WALLPAPERS = R.string.wallpaper_button_text;
+ private static final int WIDGETS = R.string.widget_button_text;
+ private static final int SETTINGS = R.string.settings_button_text;
+
+ private final Rect mTempRect = new Rect();
+ private final int[] mTempPos = new int[2];
+
+ protected final Launcher mLauncher;
private final WallpaperColorInfo mWallpaperColorInfo;
+ private final AccessibilityManager mAM;
protected final int mEndScrim;
protected float mMaxScrimAlpha;
@@ -48,28 +83,56 @@
protected int mEndFlatColor;
protected int mEndFlatColorAlpha;
+ protected final int mDragHandleSize;
+ private final Rect mDragHandleBounds;
+ private final AccessibilityHelper mAccessibilityHelper;
+ @Nullable
+ protected Drawable mDragHandle;
+
public ScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mLauncher = Launcher.getLauncher(context);
mWallpaperColorInfo = WallpaperColorInfo.getInstance(context);
mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
mMaxScrimAlpha = 0.7f;
+
+ mDragHandleSize = context.getResources()
+ .getDimensionPixelSize(R.dimen.vertical_drag_handle_size);
+ mDragHandleBounds = new Rect(0, 0, mDragHandleSize, mDragHandleSize);
+
+ mAccessibilityHelper = new AccessibilityHelper();
+ ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
+
+ mAM = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
}
@Override
- public void setInsets(Rect insets) { }
+ public void setInsets(Rect insets) {
+ updateDragHandleBounds();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ updateDragHandleBounds();
+ }
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mWallpaperColorInfo.addOnChangeListener(this);
onExtractedColorsChanged(mWallpaperColorInfo);
+
+ mAM.addAccessibilityStateChangeListener(this);
+ onAccessibilityStateChanged(mAM.isEnabled());
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mWallpaperColorInfo.removeOnChangeListener(this);
+ mAM.removeAccessibilityStateChangeListener(this);
}
@Override
@@ -91,6 +154,7 @@
if (mProgress != progress) {
mProgress = progress;
updateColors();
+ updateDragHandleAlpha();
invalidate();
}
}
@@ -102,10 +166,158 @@
mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha));
}
+ protected void updateDragHandleAlpha() {
+ if (mDragHandle != null) {
+ mDragHandle.setAlpha(Math.round(255 * Utilities.boundToRange(mProgress, 0, 1)));
+ }
+ }
+
@Override
protected void onDraw(Canvas canvas) {
if (mCurrentFlatColor != 0) {
canvas.drawColor(mCurrentFlatColor);
}
}
+
+ protected void updateDragHandleBounds() {
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ final int left;
+ final int width = getMeasuredWidth();
+ final int top = getMeasuredHeight() - mDragHandleSize - grid.getInsets().bottom;
+ final int topMargin;
+
+ if (grid.isVerticalBarLayout()) {
+ topMargin = grid.workspacePadding.bottom;
+ if (grid.isSeascape()) {
+ left = width - grid.getInsets().right - mDragHandleSize;
+ } else {
+ left = mDragHandleSize + grid.getInsets().left;
+ }
+ } else {
+ left = (width - mDragHandleSize) / 2;
+ topMargin = grid.hotseatBarSizePx;
+ }
+ mDragHandleBounds.offsetTo(left, top - topMargin);
+
+ if (mDragHandle != null) {
+ mDragHandle.setBounds(mDragHandleBounds);
+ }
+ }
+
+ @Override
+ public void onAccessibilityStateChanged(boolean enabled) {
+ LauncherStateManager stateManager = mLauncher.getStateManager();
+ stateManager.removeStateListener(this);
+
+ if (enabled) {
+ mDragHandle = mLauncher.getDrawable(R.drawable.drag_handle_indicator);
+ mDragHandle.setBounds(mDragHandleBounds);
+
+ stateManager.addStateListener(this);
+ onStateSetImmediately(mLauncher.getStateManager().getState());
+
+ updateDragHandleAlpha();
+ } else {
+ mDragHandle = null;
+ }
+ invalidate();
+ }
+
+ @Override
+ public boolean dispatchHoverEvent(MotionEvent event) {
+ return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mAccessibilityHelper.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void onFocusChanged(boolean gainFocus, int direction,
+ Rect previouslyFocusedRect) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ mAccessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ }
+
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {}
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ onStateSetImmediately(finalState);
+ }
+
+ @Override
+ public void onStateSetImmediately(LauncherState state) {
+ setImportantForAccessibility(state == ALL_APPS
+ ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ }
+
+ private class AccessibilityHelper extends ExploreByTouchHelper {
+
+ private static final int DRAG_HANDLE_ID = 1;
+
+ public AccessibilityHelper() {
+ super(ScrimView.this);
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ return mDragHandleBounds.contains((int) x, (int) y)
+ ? DRAG_HANDLE_ID : INVALID_ID;
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+ virtualViewIds.add(DRAG_HANDLE_ID);
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId,
+ AccessibilityNodeInfoCompat node) {
+ node.setContentDescription(getContext().getString(R.string.all_apps_button_label));
+ node.setBoundsInParent(mDragHandleBounds);
+
+ getLocationOnScreen(mTempPos);
+ mTempRect.set(mDragHandleBounds);
+ mTempRect.offset(mTempPos[0], mTempPos[1]);
+ node.setBoundsInScreen(mTempRect);
+
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+ node.setClickable(true);
+ node.setFocusable(true);
+
+ if (mLauncher.isInState(NORMAL)) {
+ Context context = getContext();
+ if (Utilities.isWallpaperAllowed(context)) {
+ node.addAction(
+ new AccessibilityActionCompat(WALLPAPERS, context.getText(WALLPAPERS)));
+ }
+ node.addAction(new AccessibilityActionCompat(WIDGETS, context.getText(WIDGETS)));
+ node.addAction(new AccessibilityActionCompat(SETTINGS, context.getText(SETTINGS)));
+ }
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(
+ int virtualViewId, int action, Bundle arguments) {
+ if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+ mLauncher.getUserEventDispatcher().logActionOnControl(
+ Action.Touch.TAP, ControlType.ALL_APPS_BUTTON,
+ mLauncher.getStateManager().getState().containerType);
+ mLauncher.getStateManager().goToState(ALL_APPS);
+ return true;
+ } else if (action == WALLPAPERS) {
+ return OptionsPopupView.startWallpaperPicker(ScrimView.this);
+ } else if (action == WIDGETS) {
+ return OptionsPopupView.onWidgetsClicked(ScrimView.this);
+ } else if (action == SETTINGS) {
+ return OptionsPopupView.startSettings(ScrimView.this);
+ }
+
+ return false;
+ }
+ }
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index 860be5f..80c2485 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -8,6 +8,7 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -62,11 +63,11 @@
}
@Override
- protected float initCurrentAnimation() {
+ protected float initCurrentAnimation(@AnimationComponents int animComponents) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
mCurrentAnimation = mLauncher.getStateManager()
- .createAnimationToNewWorkspace(mToState, maxAccuracy);
+ .createAnimationToNewWorkspace(mToState, maxAccuracy, animComponents);
float startVerticalShift = mFromState.getVerticalProgress(mLauncher) * range;
float endVerticalShift = mToState.getVerticalProgress(mLauncher) * range;
float totalShift = endVerticalShift - startVerticalShift;