Add atomic recents animation while swiping up
State handlers can now specify atomic and non-atomic components of their animations to states, which can be specified when creating a new animation. There is now one atomic animation, when going from NORMAL to OVERVIEW (and in reverse): - RecentsViewStateController's animation (scale/alpha) is all atomic - WorkspaceStateTransitionAnimation has atomic and non-atomic: - Hotseat and workspace alpha is atomic, as is workspace scale - Everything else (scrim, translation, qsb and drag handle alpha) is non-atomic - All apps progress is non-atomic Also simplified dragging through overview; no longer pulls against you, so we use an OvershootInterpolator when flinging instead of our custom interpolator for the spring effect. Bug: 76449024 Bug: 78089840 Change-Id: Iafac84d0c2b99ee9cf9dd5b30e2218286713b449
This commit is contained in:
parent
639b07178f
commit
6becf7c07d
|
@ -84,7 +84,7 @@ public class AllAppsState extends LauncherState {
|
|||
|
||||
@Override
|
||||
public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
|
||||
return new float[] {1f, -0.2f};
|
||||
return new float[] {0.9f, -0.2f};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.view.MotionEvent;
|
|||
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 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro
|
|||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.android.launcher3.AbstractFloatingView;
|
|||
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 @@ public class OverviewState extends LauncherState {
|
|||
|
||||
@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
|
||||
|
|
|
@ -18,20 +18,20 @@ package com.android.launcher3.uioverrides;
|
|||
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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
|||
|
||||
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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
|||
}
|
||||
|
||||
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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
|||
|
||||
if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
|
||||
builder = getNormalToOverviewAnimation();
|
||||
totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
|
||||
} else {
|
||||
builder = new AnimatorSetBuilder();
|
||||
}
|
||||
|
@ -190,7 +156,8 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
|||
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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
|||
@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 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
|
|||
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
|
||||
|
|
|
@ -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 @@ public class RecentsViewStateController implements StateHandler {
|
|||
@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);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CH
|
|||
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;
|
||||
|
||||
|
@ -86,6 +87,8 @@ public class LauncherState {
|
|||
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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.animation.AnimatorListenerAdapter;
|
|||
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;
|
|||
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 class LauncherStateManager {
|
|||
|
||||
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 class LauncherStateManager {
|
|||
*/
|
||||
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,
|
||||
|
@ -425,6 +451,7 @@ public class LauncherStateManager {
|
|||
public long duration;
|
||||
public boolean userControlled;
|
||||
public AnimatorPlaybackController playbackController;
|
||||
public @AnimationComponents int animComponents = ANIM_ALL;
|
||||
private PropertySetter mPropertySetter;
|
||||
|
||||
private AnimatorSet mCurrentAnimation;
|
||||
|
@ -436,6 +463,7 @@ public class LauncherStateManager {
|
|||
public void reset() {
|
||||
duration = 0;
|
||||
userControlled = false;
|
||||
animComponents = ANIM_ALL;
|
||||
mPropertySetter = null;
|
||||
mTargetState = null;
|
||||
|
||||
|
@ -471,6 +499,14 @@ public class LauncherStateManager {
|
|||
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 {
|
||||
|
|
|
@ -82,7 +82,6 @@ import com.android.launcher3.popup.PopupContainerWithArrow;
|
|||
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;
|
||||
|
|
|
@ -24,6 +24,7 @@ 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 +49,12 @@ public class WorkspaceStateTransitionAnimation {
|
|||
}
|
||||
|
||||
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,28 +64,40 @@ public class WorkspaceStateTransitionAnimation {
|
|||
/**
|
||||
* 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,
|
||||
|
@ -101,17 +114,22 @@ public class WorkspaceStateTransitionAnimation {
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -168,6 +168,11 @@ public class AllAppsTransitionController implements StateHandler, OnDeviceProfil
|
|||
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);
|
||||
|
|
|
@ -32,7 +32,6 @@ import java.util.List;
|
|||
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 @@ public class AnimatorSetBuilder {
|
|||
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();
|
||||
}
|
||||
|
|
|
@ -18,16 +18,26 @@ package com.android.launcher3.touch;
|
|||
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.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.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 +51,17 @@ public abstract class AbstractStateChangeTouchController
|
|||
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;
|
||||
|
||||
|
@ -62,6 +78,11 @@ public abstract class AbstractStateChangeTouchController
|
|||
private float mProgressMultiplier;
|
||||
private float mDisplacementShift;
|
||||
|
||||
private AnimatorSet mAtomicAnim;
|
||||
private boolean mPassedOverviewAtomicThreshold;
|
||||
private boolean mCanBlockFling;
|
||||
private boolean mBlockFling;
|
||||
|
||||
public AbstractStateChangeTouchController(Launcher l, SwipeDetector.Direction dir) {
|
||||
mLauncher = l;
|
||||
mDetector = new SwipeDetector(l, this, dir);
|
||||
|
@ -83,14 +104,8 @@ public abstract class AbstractStateChangeTouchController
|
|||
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 +153,7 @@ public abstract class AbstractStateChangeTouchController
|
|||
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,14 +184,28 @@ public abstract class AbstractStateChangeTouchController
|
|||
mToState = newToState;
|
||||
|
||||
mStartProgress = 0;
|
||||
mPassedOverviewAtomicThreshold = false;
|
||||
if (mAtomicAnim != null) {
|
||||
// Most likely the animation is finished by now, but just in case it's not,
|
||||
// make sure the next state animation starts from the expected state.
|
||||
mAtomicAnim.end();
|
||||
}
|
||||
if (mCurrentAnimation != null) {
|
||||
mCurrentAnimation.setOnCancelRunnable(null);
|
||||
}
|
||||
mProgressMultiplier = initCurrentAnimation();
|
||||
int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
|
||||
? NON_ATOMIC_COMPONENT : ANIM_ALL;
|
||||
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) {
|
||||
|
@ -187,6 +216,8 @@ public abstract class AbstractStateChangeTouchController
|
|||
mCurrentAnimation.pause();
|
||||
mStartProgress = mCurrentAnimation.getProgressFraction();
|
||||
}
|
||||
mCanBlockFling = mFromState == NORMAL;
|
||||
mBlockFling = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -198,17 +229,61 @@ public abstract class AbstractStateChangeTouchController
|
|||
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);
|
||||
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();
|
||||
}
|
||||
AnimatorSetBuilder builder = new AnimatorSetBuilder();
|
||||
AnimationConfig config = new AnimationConfig();
|
||||
config.animComponents = ATOMIC_COMPONENT;
|
||||
config.duration = targetState == OVERVIEW ? ATOMIC_NORMAL_TO_OVERVIEW_DURATION
|
||||
: ATOMIC_OVERVIEW_TO_NORMAL_DURATION;
|
||||
for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
|
||||
handler.setStateWithAnimation(targetState, builder, config);
|
||||
}
|
||||
mAtomicAnim = builder.build();
|
||||
mAtomicAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mAtomicAnim = null;
|
||||
}
|
||||
});
|
||||
mAtomicAnim.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -217,6 +292,11 @@ public abstract class AbstractStateChangeTouchController
|
|||
final LauncherState targetState;
|
||||
final float progress = mCurrentAnimation.getProgressFraction();
|
||||
|
||||
boolean blockedFling = fling && mBlockFling;
|
||||
if (blockedFling) {
|
||||
fling = false;
|
||||
}
|
||||
|
||||
if (fling) {
|
||||
logAction = Touch.FLING;
|
||||
targetState =
|
||||
|
@ -231,6 +311,8 @@ public abstract class AbstractStateChangeTouchController
|
|||
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 +323,7 @@ public abstract class AbstractStateChangeTouchController
|
|||
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,18 +341,35 @@ public abstract class AbstractStateChangeTouchController
|
|||
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();
|
||||
}
|
||||
|
||||
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,
|
||||
LauncherState targetState, float velocity, boolean isFling) {
|
||||
animator.setDuration(expectedDuration)
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.view.MotionEvent;
|
|||
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 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController {
|
|||
}
|
||||
|
||||
@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;
|
||||
|
|
Loading…
Reference in New Issue