mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-19 18:58:19 +00:00
b/30486958 Cleaning up/refactoring this CL will be done in polish branch. Change-Id: Ib51633a1e1cb79f58b505f835053ef238ae1bdec
1027 lines
44 KiB
Java
1027 lines
44 KiB
Java
/*
|
|
* Copyright (C) 2015 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;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.AnimatorListenerAdapter;
|
|
import android.animation.AnimatorSet;
|
|
import android.animation.ObjectAnimator;
|
|
import android.animation.PropertyValuesHolder;
|
|
import android.animation.TimeInterpolator;
|
|
import android.animation.ValueAnimator;
|
|
import android.annotation.SuppressLint;
|
|
import android.annotation.TargetApi;
|
|
import android.content.res.Resources;
|
|
import android.os.Build;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
import android.view.animation.AccelerateInterpolator;
|
|
import android.view.animation.DecelerateInterpolator;
|
|
|
|
import com.android.launcher3.allapps.AllAppsContainerView;
|
|
import com.android.launcher3.allapps.AllAppsTransitionController;
|
|
import com.android.launcher3.config.FeatureFlags;
|
|
import com.android.launcher3.util.CircleRevealOutlineProvider;
|
|
import com.android.launcher3.util.Thunk;
|
|
import com.android.launcher3.widget.WidgetsContainerView;
|
|
|
|
import java.util.HashMap;
|
|
|
|
/**
|
|
* TODO: figure out what kind of tests we can write for this
|
|
*
|
|
* Things to test when changing the following class.
|
|
* - Home from workspace
|
|
* - from center screen
|
|
* - from other screens
|
|
* - Home from all apps
|
|
* - from center screen
|
|
* - from other screens
|
|
* - Back from all apps
|
|
* - from center screen
|
|
* - from other screens
|
|
* - Launch app from workspace and quit
|
|
* - with back
|
|
* - with home
|
|
* - Launch app from all apps and quit
|
|
* - with back
|
|
* - with home
|
|
* - Go to a screen that's not the default, then all
|
|
* apps, and launch and app, and go back
|
|
* - with back
|
|
* -with home
|
|
* - On workspace, long press power and go back
|
|
* - with back
|
|
* - with home
|
|
* - On all apps, long press power and go back
|
|
* - with back
|
|
* - with home
|
|
* - On workspace, power off
|
|
* - On all apps, power off
|
|
* - Launch an app and turn off the screen while in that app
|
|
* - Go back with home key
|
|
* - Go back with back key TODO: make this not go to workspace
|
|
* - From all apps
|
|
* - From workspace
|
|
* - Enter and exit car mode (becuase it causes an extra configuration changed)
|
|
* - From all apps
|
|
* - From the center workspace
|
|
* - From another workspace
|
|
*/
|
|
public class LauncherStateTransitionAnimation {
|
|
|
|
/**
|
|
* animation used for all apps and widget tray when
|
|
*{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code false}
|
|
*/
|
|
public static final int CIRCULAR_REVEAL = 0;
|
|
/**
|
|
* animation used for all apps and not widget tray when
|
|
*{@link FeatureFlags#LAUNCHER3_ALL_APPS_PULL_UP} is {@code true}
|
|
*/
|
|
public static final int PULLUP = 1;
|
|
|
|
private static final float FINAL_REVEAL_ALPHA_FOR_WIDGETS = 0.3f;
|
|
|
|
/**
|
|
* Private callbacks made during transition setup.
|
|
*/
|
|
private static class PrivateTransitionCallbacks {
|
|
private final float materialRevealViewFinalAlpha;
|
|
|
|
PrivateTransitionCallbacks(float revealAlpha) {
|
|
materialRevealViewFinalAlpha = revealAlpha;
|
|
}
|
|
|
|
float getMaterialRevealViewStartFinalRadius() {
|
|
return 0;
|
|
}
|
|
AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
|
|
View buttonView) {
|
|
return null;
|
|
}
|
|
void onTransitionComplete() {}
|
|
}
|
|
|
|
public static final String TAG = "LSTAnimation";
|
|
|
|
// Flags to determine how to set the layers on views before the transition animation
|
|
public static final int BUILD_LAYER = 0;
|
|
public static final int BUILD_AND_SET_LAYER = 1;
|
|
public static final int SINGLE_FRAME_DELAY = 16;
|
|
|
|
@Thunk Launcher mLauncher;
|
|
@Thunk AnimatorSet mCurrentAnimation;
|
|
AllAppsTransitionController mAllAppsController;
|
|
|
|
public LauncherStateTransitionAnimation(Launcher l, AllAppsTransitionController allAppsController) {
|
|
mLauncher = l;
|
|
mAllAppsController = allAppsController;
|
|
}
|
|
|
|
/**
|
|
* Starts an animation to the apps view.
|
|
*
|
|
* @param startSearchAfterTransition Immediately starts app search after the transition to
|
|
* All Apps is completed.
|
|
*/
|
|
public void startAnimationToAllApps(final Workspace.State fromWorkspaceState,
|
|
final boolean animated, final boolean startSearchAfterTransition) {
|
|
final AllAppsContainerView toView = mLauncher.getAppsView();
|
|
final View buttonView = mLauncher.getStartViewForAllAppsRevealAnimation();
|
|
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
|
|
@Override
|
|
public float getMaterialRevealViewStartFinalRadius() {
|
|
int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
|
|
return allAppsButtonSize / 2;
|
|
}
|
|
@Override
|
|
public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
|
|
final View revealView, final View allAppsButtonView) {
|
|
return new AnimatorListenerAdapter() {
|
|
public void onAnimationStart(Animator animation) {
|
|
allAppsButtonView.setVisibility(View.INVISIBLE);
|
|
}
|
|
public void onAnimationEnd(Animator animation) {
|
|
allAppsButtonView.setVisibility(View.VISIBLE);
|
|
}
|
|
};
|
|
}
|
|
@Override
|
|
void onTransitionComplete() {
|
|
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
|
|
if (startSearchAfterTransition) {
|
|
toView.startAppsSearch();
|
|
}
|
|
}
|
|
};
|
|
int animType = CIRCULAR_REVEAL;
|
|
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
|
animType = PULLUP;
|
|
}
|
|
// Only animate the search bar if animating from spring loaded mode back to all apps
|
|
startAnimationToOverlay(fromWorkspaceState,
|
|
Workspace.State.NORMAL_HIDDEN, buttonView, toView, animated, animType, cb);
|
|
}
|
|
|
|
/**
|
|
* Starts an animation to the widgets view.
|
|
*/
|
|
public void startAnimationToWidgets(final Workspace.State fromWorkspaceState,
|
|
final boolean animated) {
|
|
final WidgetsContainerView toView = mLauncher.getWidgetsView();
|
|
final View buttonView = mLauncher.getWidgetsButton();
|
|
startAnimationToOverlay(fromWorkspaceState,
|
|
Workspace.State.OVERVIEW_HIDDEN, buttonView, toView, animated, CIRCULAR_REVEAL,
|
|
new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS){
|
|
@Override
|
|
void onTransitionComplete() {
|
|
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Starts an animation to the workspace from the current overlay view.
|
|
*/
|
|
public void startAnimationToWorkspace(final Launcher.State fromState,
|
|
final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
|
|
final boolean animated, final Runnable onCompleteRunnable) {
|
|
if (toWorkspaceState != Workspace.State.NORMAL &&
|
|
toWorkspaceState != Workspace.State.SPRING_LOADED &&
|
|
toWorkspaceState != Workspace.State.OVERVIEW) {
|
|
Log.e(TAG, "Unexpected call to startAnimationToWorkspace");
|
|
}
|
|
|
|
if (fromState == Launcher.State.APPS || fromState == Launcher.State.APPS_SPRING_LOADED
|
|
|| mAllAppsController.isTransitioning()) {
|
|
int animType = CIRCULAR_REVEAL;
|
|
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP) {
|
|
animType = PULLUP;
|
|
}
|
|
startAnimationToWorkspaceFromAllApps(fromWorkspaceState, toWorkspaceState,
|
|
animated, animType, onCompleteRunnable);
|
|
} else if (fromState == Launcher.State.WIDGETS ||
|
|
fromState == Launcher.State.WIDGETS_SPRING_LOADED) {
|
|
startAnimationToWorkspaceFromWidgets(fromWorkspaceState, toWorkspaceState,
|
|
animated, onCompleteRunnable);
|
|
} else {
|
|
startAnimationToNewWorkspaceState(fromWorkspaceState, toWorkspaceState,
|
|
animated, onCompleteRunnable);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates and starts a new animation to a particular overlay view.
|
|
*/
|
|
@SuppressLint("NewApi")
|
|
private void startAnimationToOverlay(
|
|
final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
|
|
final View buttonView, final BaseContainerView toView,
|
|
final boolean animated, int animType, final PrivateTransitionCallbacks pCb) {
|
|
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
|
|
final Resources res = mLauncher.getResources();
|
|
final boolean material = Utilities.ATLEAST_LOLLIPOP;
|
|
final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
|
|
final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
|
|
|
|
final int itemsAlphaStagger = res.getInteger(R.integer.config_overlayItemsAlphaStagger);
|
|
|
|
final View fromView = mLauncher.getWorkspace();
|
|
|
|
final HashMap<View, Integer> layerViews = new HashMap<>();
|
|
|
|
// If for some reason our views aren't initialized, don't animate
|
|
boolean initialized = buttonView != null;
|
|
|
|
// Cancel the current animation
|
|
cancelAnimation();
|
|
|
|
final View contentView = toView.getContentView();
|
|
playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
|
|
animated, initialized, animation, layerViews);
|
|
if (!animated || !initialized) {
|
|
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
|
|
toWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
|
|
mAllAppsController.finishPullUp();
|
|
}
|
|
toView.setTranslationX(0.0f);
|
|
toView.setTranslationY(0.0f);
|
|
toView.setScaleX(1.0f);
|
|
toView.setScaleY(1.0f);
|
|
toView.setAlpha(1.0f);
|
|
toView.setVisibility(View.VISIBLE);
|
|
toView.bringToFront();
|
|
|
|
// Show the content view
|
|
contentView.setVisibility(View.VISIBLE);
|
|
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
|
|
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
|
dispatchOnLauncherTransitionEnd(fromView, animated, false);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, false);
|
|
dispatchOnLauncherTransitionStart(toView, animated, false);
|
|
dispatchOnLauncherTransitionEnd(toView, animated, false);
|
|
pCb.onTransitionComplete();
|
|
return;
|
|
}
|
|
if (animType == CIRCULAR_REVEAL) {
|
|
// Setup the reveal view animation
|
|
final View revealView = toView.getRevealView();
|
|
|
|
int width = revealView.getMeasuredWidth();
|
|
int height = revealView.getMeasuredHeight();
|
|
float revealRadius = (float) Math.hypot(width / 2, height / 2);
|
|
revealView.setVisibility(View.VISIBLE);
|
|
revealView.setAlpha(0f);
|
|
revealView.setTranslationY(0f);
|
|
revealView.setTranslationX(0f);
|
|
|
|
// Calculate the final animation values
|
|
final float revealViewToAlpha;
|
|
final float revealViewToXDrift;
|
|
final float revealViewToYDrift;
|
|
if (material) {
|
|
int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(
|
|
revealView, buttonView, null);
|
|
revealViewToAlpha = pCb.materialRevealViewFinalAlpha;
|
|
revealViewToYDrift = buttonViewToPanelDelta[1];
|
|
revealViewToXDrift = buttonViewToPanelDelta[0];
|
|
} else {
|
|
revealViewToAlpha = 0f;
|
|
revealViewToYDrift = 2 * height / 3;
|
|
revealViewToXDrift = 0;
|
|
}
|
|
|
|
// Create the animators
|
|
PropertyValuesHolder panelAlpha =
|
|
PropertyValuesHolder.ofFloat(View.ALPHA, revealViewToAlpha, 1f);
|
|
PropertyValuesHolder panelDriftY =
|
|
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, revealViewToYDrift, 0);
|
|
PropertyValuesHolder panelDriftX =
|
|
PropertyValuesHolder.ofFloat(View.TRANSLATION_X, revealViewToXDrift, 0);
|
|
ObjectAnimator panelAlphaAndDrift = ObjectAnimator.ofPropertyValuesHolder(revealView,
|
|
panelAlpha, panelDriftY, panelDriftX);
|
|
panelAlphaAndDrift.setDuration(revealDuration);
|
|
panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
|
|
|
|
// Play the animation
|
|
layerViews.put(revealView, BUILD_AND_SET_LAYER);
|
|
animation.play(panelAlphaAndDrift);
|
|
|
|
// Setup the animation for the content view
|
|
contentView.setVisibility(View.VISIBLE);
|
|
contentView.setAlpha(0f);
|
|
contentView.setTranslationY(revealViewToYDrift);
|
|
layerViews.put(contentView, BUILD_AND_SET_LAYER);
|
|
|
|
// Create the individual animators
|
|
ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
|
|
revealViewToYDrift, 0);
|
|
pageDrift.setDuration(revealDuration);
|
|
pageDrift.setInterpolator(new LogDecelerateInterpolator(100, 0));
|
|
pageDrift.setStartDelay(itemsAlphaStagger);
|
|
animation.play(pageDrift);
|
|
|
|
ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
|
|
itemsAlpha.setDuration(revealDuration);
|
|
itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
|
|
itemsAlpha.setStartDelay(itemsAlphaStagger);
|
|
animation.play(itemsAlpha);
|
|
|
|
if (material) {
|
|
float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
|
|
AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
|
|
revealView, buttonView);
|
|
Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
|
|
startRadius, revealRadius).createRevealAnimator(revealView);
|
|
reveal.setDuration(revealDuration);
|
|
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
|
|
if (listener != null) {
|
|
reveal.addListener(listener);
|
|
}
|
|
animation.play(reveal);
|
|
}
|
|
|
|
animation.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
dispatchOnLauncherTransitionEnd(fromView, animated, false);
|
|
dispatchOnLauncherTransitionEnd(toView, animated, false);
|
|
|
|
// Hide the reveal view
|
|
revealView.setVisibility(View.INVISIBLE);
|
|
|
|
// Disable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
}
|
|
}
|
|
|
|
// This can hold unnecessary references to views.
|
|
cleanupAnimation();
|
|
pCb.onTransitionComplete();
|
|
}
|
|
|
|
});
|
|
|
|
// Dispatch the prepare transition signal
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, false);
|
|
|
|
final AnimatorSet stateAnimation = animation;
|
|
final Runnable startAnimRunnable = new Runnable() {
|
|
public void run() {
|
|
// Check that mCurrentAnimation hasn't changed while
|
|
// we waited for a layout/draw pass
|
|
if (mCurrentAnimation != stateAnimation)
|
|
return;
|
|
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
|
dispatchOnLauncherTransitionStart(toView, animated, false);
|
|
|
|
// Enable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
}
|
|
if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
|
|
v.buildLayer();
|
|
}
|
|
}
|
|
|
|
// Focus the new view
|
|
toView.requestFocus();
|
|
|
|
stateAnimation.start();
|
|
}
|
|
};
|
|
toView.bringToFront();
|
|
toView.setVisibility(View.VISIBLE);
|
|
toView.post(startAnimRunnable);
|
|
mCurrentAnimation = animation;
|
|
} else if (animType == PULLUP) {
|
|
// We are animating the content view alpha, so ensure we have a layer for it
|
|
layerViews.put(contentView, BUILD_AND_SET_LAYER);
|
|
|
|
animation.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
dispatchOnLauncherTransitionEnd(fromView, animated, false);
|
|
dispatchOnLauncherTransitionEnd(toView, animated, false);
|
|
|
|
// Disable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
}
|
|
}
|
|
|
|
cleanupAnimation();
|
|
pCb.onTransitionComplete();
|
|
}
|
|
});
|
|
boolean shouldPost = mAllAppsController.animateToAllApps(animation, revealDurationSlide);
|
|
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, false);
|
|
|
|
final AnimatorSet stateAnimation = animation;
|
|
final Runnable startAnimRunnable = new Runnable() {
|
|
public void run() {
|
|
// Check that mCurrentAnimation hasn't changed while
|
|
// we waited for a layout/draw pass
|
|
if (mCurrentAnimation != stateAnimation)
|
|
return;
|
|
|
|
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
|
dispatchOnLauncherTransitionStart(toView, animated, false);
|
|
|
|
// Enable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
}
|
|
if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
|
|
v.buildLayer();
|
|
}
|
|
}
|
|
|
|
toView.requestFocus();
|
|
stateAnimation.start();
|
|
}
|
|
};
|
|
mCurrentAnimation = animation;
|
|
if (shouldPost) {
|
|
toView.post(startAnimRunnable);
|
|
} else {
|
|
startAnimRunnable.run();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Plays animations used by various transitions.
|
|
*/
|
|
private void playCommonTransitionAnimations(
|
|
Workspace.State toWorkspaceState, View fromView, View toView,
|
|
boolean animated, boolean initialized, AnimatorSet animation,
|
|
HashMap<View, Integer> layerViews) {
|
|
// Create the workspace animation.
|
|
// NOTE: this call apparently also sets the state for the workspace if !animated
|
|
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
|
|
animated, layerViews);
|
|
|
|
if (animated && initialized) {
|
|
// Play the workspace animation
|
|
if (workspaceAnim != null) {
|
|
animation.play(workspaceAnim);
|
|
}
|
|
// Dispatch onLauncherTransitionStep() as the animation interpolates.
|
|
animation.play(dispatchOnLauncherTransitionStepAnim(fromView, toView));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an Animator that calls {@link #dispatchOnLauncherTransitionStep(View, float)} on
|
|
* {@param fromView} and {@param toView} as the animation interpolates.
|
|
*
|
|
* This is a bit hacky: we create a dummy ValueAnimator just for the AnimatorUpdateListener.
|
|
*/
|
|
private Animator dispatchOnLauncherTransitionStepAnim(final View fromView, final View toView) {
|
|
ValueAnimator updateAnimator = ValueAnimator.ofFloat(0, 1);
|
|
updateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
|
@Override
|
|
public void onAnimationUpdate(ValueAnimator animation) {
|
|
dispatchOnLauncherTransitionStep(fromView, animation.getAnimatedFraction());
|
|
dispatchOnLauncherTransitionStep(toView, animation.getAnimatedFraction());
|
|
}
|
|
});
|
|
return updateAnimator;
|
|
}
|
|
|
|
/**
|
|
* Starts an animation to the workspace from the apps view.
|
|
*/
|
|
private void startAnimationToWorkspaceFromAllApps(final Workspace.State fromWorkspaceState,
|
|
final Workspace.State toWorkspaceState, final boolean animated, int type,
|
|
final Runnable onCompleteRunnable) {
|
|
AllAppsContainerView appsView = mLauncher.getAppsView();
|
|
// No alpha anim from all apps
|
|
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks(1f) {
|
|
@Override
|
|
float getMaterialRevealViewStartFinalRadius() {
|
|
int allAppsButtonSize = mLauncher.getDeviceProfile().allAppsButtonVisualSize;
|
|
return allAppsButtonSize / 2;
|
|
}
|
|
@Override
|
|
public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
|
|
final View revealView, final View allAppsButtonView) {
|
|
return new AnimatorListenerAdapter() {
|
|
public void onAnimationStart(Animator animation) {
|
|
// We set the alpha instead of visibility to ensure that the focus does not
|
|
// get taken from the all apps view
|
|
allAppsButtonView.setVisibility(View.VISIBLE);
|
|
allAppsButtonView.setAlpha(0f);
|
|
}
|
|
public void onAnimationEnd(Animator animation) {
|
|
// Hide the reveal view
|
|
revealView.setVisibility(View.INVISIBLE);
|
|
|
|
// Show the all apps button, and focus it
|
|
allAppsButtonView.setAlpha(1f);
|
|
}
|
|
};
|
|
}
|
|
@Override
|
|
void onTransitionComplete() {
|
|
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
|
|
}
|
|
};
|
|
// Only animate the search bar if animating to spring loaded mode from all apps
|
|
startAnimationToWorkspaceFromOverlay(fromWorkspaceState, toWorkspaceState,
|
|
mLauncher.getStartViewForAllAppsRevealAnimation(), appsView,
|
|
animated, type, onCompleteRunnable, cb);
|
|
}
|
|
|
|
/**
|
|
* Starts an animation to the workspace from the widgets view.
|
|
*/
|
|
private void startAnimationToWorkspaceFromWidgets(final Workspace.State fromWorkspaceState,
|
|
final Workspace.State toWorkspaceState, final boolean animated,
|
|
final Runnable onCompleteRunnable) {
|
|
final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
|
|
PrivateTransitionCallbacks cb =
|
|
new PrivateTransitionCallbacks(FINAL_REVEAL_ALPHA_FOR_WIDGETS) {
|
|
@Override
|
|
public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
|
|
final View revealView, final View widgetsButtonView) {
|
|
return new AnimatorListenerAdapter() {
|
|
public void onAnimationEnd(Animator animation) {
|
|
// Hide the reveal view
|
|
revealView.setVisibility(View.INVISIBLE);
|
|
}
|
|
};
|
|
}
|
|
@Override
|
|
void onTransitionComplete() {
|
|
mLauncher.getUserEventDispatcher().resetElapsedContainerMillis();
|
|
}
|
|
};
|
|
startAnimationToWorkspaceFromOverlay(
|
|
fromWorkspaceState, toWorkspaceState,
|
|
mLauncher.getWidgetsButton(), widgetsView,
|
|
animated, CIRCULAR_REVEAL, onCompleteRunnable, cb);
|
|
}
|
|
|
|
/**
|
|
* Starts an animation to the workspace from another workspace state, e.g. normal to overview.
|
|
*/
|
|
private void startAnimationToNewWorkspaceState(final Workspace.State fromWorkspaceState,
|
|
final Workspace.State toWorkspaceState, final boolean animated,
|
|
final Runnable onCompleteRunnable) {
|
|
final View fromWorkspace = mLauncher.getWorkspace();
|
|
final HashMap<View, Integer> layerViews = new HashMap<>();
|
|
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
|
|
final int revealDuration = mLauncher.getResources()
|
|
.getInteger(R.integer.config_overlayRevealTime);
|
|
|
|
// Cancel the current animation
|
|
cancelAnimation();
|
|
|
|
boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
|
|
|
|
playCommonTransitionAnimations(toWorkspaceState, fromWorkspace, null,
|
|
animated, animated, animation, layerViews);
|
|
|
|
if (animated) {
|
|
dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
|
|
|
|
final AnimatorSet stateAnimation = animation;
|
|
final Runnable startAnimRunnable = new Runnable() {
|
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
public void run() {
|
|
// Check that mCurrentAnimation hasn't changed while
|
|
// we waited for a layout/draw pass
|
|
if (mCurrentAnimation != stateAnimation)
|
|
return;
|
|
|
|
dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
|
|
|
|
// Enable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
}
|
|
if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
|
|
v.buildLayer();
|
|
}
|
|
}
|
|
stateAnimation.start();
|
|
}
|
|
};
|
|
animation.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
|
|
|
|
// Run any queued runnables
|
|
if (onCompleteRunnable != null) {
|
|
onCompleteRunnable.run();
|
|
}
|
|
|
|
// Disable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
}
|
|
}
|
|
|
|
// This can hold unnecessary references to views.
|
|
cleanupAnimation();
|
|
}
|
|
});
|
|
fromWorkspace.post(startAnimRunnable);
|
|
mCurrentAnimation = animation;
|
|
} else /* if (!animated) */ {
|
|
dispatchOnLauncherTransitionPrepare(fromWorkspace, animated, multiplePagesVisible);
|
|
dispatchOnLauncherTransitionStart(fromWorkspace, animated, true);
|
|
dispatchOnLauncherTransitionEnd(fromWorkspace, animated, true);
|
|
|
|
// Run any queued runnables
|
|
if (onCompleteRunnable != null) {
|
|
onCompleteRunnable.run();
|
|
}
|
|
|
|
mCurrentAnimation = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates and starts a new animation to the workspace.
|
|
*/
|
|
private void startAnimationToWorkspaceFromOverlay(
|
|
final Workspace.State fromWorkspaceState, final Workspace.State toWorkspaceState,
|
|
final View buttonView, final BaseContainerView fromView,
|
|
final boolean animated, int animType, final Runnable onCompleteRunnable,
|
|
final PrivateTransitionCallbacks pCb) {
|
|
final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
|
|
final Resources res = mLauncher.getResources();
|
|
final boolean material = Utilities.ATLEAST_LOLLIPOP;
|
|
final int revealDuration = res.getInteger(R.integer.config_overlayRevealTime);
|
|
final int revealDurationSlide = res.getInteger(R.integer.config_overlaySlideRevealTime);
|
|
final int itemsAlphaStagger =
|
|
res.getInteger(R.integer.config_overlayItemsAlphaStagger);
|
|
|
|
final View toView = mLauncher.getWorkspace();
|
|
final View revealView = fromView.getRevealView();
|
|
final View contentView = fromView.getContentView();
|
|
|
|
final HashMap<View, Integer> layerViews = new HashMap<>();
|
|
|
|
// If for some reason our views aren't initialized, don't animate
|
|
boolean initialized = buttonView != null;
|
|
|
|
// Cancel the current animation
|
|
cancelAnimation();
|
|
|
|
boolean multiplePagesVisible = toWorkspaceState.hasMultipleVisiblePages;
|
|
|
|
playCommonTransitionAnimations(toWorkspaceState, fromView, toView,
|
|
animated, initialized, animation, layerViews);
|
|
if (!animated || !initialized) {
|
|
if (FeatureFlags.LAUNCHER3_ALL_APPS_PULL_UP &&
|
|
fromWorkspaceState == Workspace.State.NORMAL_HIDDEN) {
|
|
mAllAppsController.finishPullDown();
|
|
}
|
|
fromView.setVisibility(View.GONE);
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
|
|
dispatchOnLauncherTransitionStart(fromView, animated, true);
|
|
dispatchOnLauncherTransitionEnd(fromView, animated, true);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
|
|
dispatchOnLauncherTransitionStart(toView, animated, true);
|
|
dispatchOnLauncherTransitionEnd(toView, animated, true);
|
|
pCb.onTransitionComplete();
|
|
|
|
// Run any queued runnables
|
|
if (onCompleteRunnable != null) {
|
|
onCompleteRunnable.run();
|
|
}
|
|
return;
|
|
}
|
|
if (animType == CIRCULAR_REVEAL) {
|
|
// hideAppsCustomizeHelper is called in some cases when it is already hidden
|
|
// don't perform all these no-op animations. In particularly, this was causing
|
|
// the all-apps button to pop in and out.
|
|
if (fromView.getVisibility() == View.VISIBLE) {
|
|
int width = revealView.getMeasuredWidth();
|
|
int height = revealView.getMeasuredHeight();
|
|
float revealRadius = (float) Math.hypot(width / 2, height / 2);
|
|
revealView.setVisibility(View.VISIBLE);
|
|
revealView.setAlpha(1f);
|
|
revealView.setTranslationY(0);
|
|
layerViews.put(revealView, BUILD_AND_SET_LAYER);
|
|
|
|
// Calculate the final animation values
|
|
final float revealViewToXDrift;
|
|
final float revealViewToYDrift;
|
|
if (material) {
|
|
int[] buttonViewToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
|
|
buttonView, null);
|
|
revealViewToYDrift = buttonViewToPanelDelta[1];
|
|
revealViewToXDrift = buttonViewToPanelDelta[0];
|
|
} else {
|
|
revealViewToYDrift = 2 * height / 3;
|
|
revealViewToXDrift = 0;
|
|
}
|
|
|
|
// The vertical motion of the apps panel should be delayed by one frame
|
|
// from the conceal animation in order to give the right feel. We correspondingly
|
|
// shorten the duration so that the slide and conceal end at the same time.
|
|
TimeInterpolator decelerateInterpolator = material ?
|
|
new LogDecelerateInterpolator(100, 0) :
|
|
new DecelerateInterpolator(1f);
|
|
ObjectAnimator panelDriftY = ObjectAnimator.ofFloat(revealView, "translationY",
|
|
0, revealViewToYDrift);
|
|
panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
|
|
panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
|
|
panelDriftY.setInterpolator(decelerateInterpolator);
|
|
animation.play(panelDriftY);
|
|
|
|
ObjectAnimator panelDriftX = ObjectAnimator.ofFloat(revealView, "translationX",
|
|
0, revealViewToXDrift);
|
|
panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
|
|
panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
|
|
panelDriftX.setInterpolator(decelerateInterpolator);
|
|
animation.play(panelDriftX);
|
|
|
|
// Setup animation for the reveal panel alpha
|
|
final float revealViewToAlpha = !material ? 0f :
|
|
pCb.materialRevealViewFinalAlpha;
|
|
if (revealViewToAlpha != 1f) {
|
|
ObjectAnimator panelAlpha = ObjectAnimator.ofFloat(revealView, "alpha",
|
|
1f, revealViewToAlpha);
|
|
panelAlpha.setDuration(material ? revealDuration : 150);
|
|
panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
|
|
panelAlpha.setInterpolator(decelerateInterpolator);
|
|
animation.play(panelAlpha);
|
|
}
|
|
|
|
// Setup the animation for the content view
|
|
layerViews.put(contentView, BUILD_AND_SET_LAYER);
|
|
|
|
// Create the individual animators
|
|
ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY",
|
|
0, revealViewToYDrift);
|
|
contentView.setTranslationY(0);
|
|
pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
|
|
pageDrift.setInterpolator(decelerateInterpolator);
|
|
pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
|
|
animation.play(pageDrift);
|
|
|
|
contentView.setAlpha(1f);
|
|
ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 1f, 0f);
|
|
itemsAlpha.setDuration(100);
|
|
itemsAlpha.setInterpolator(decelerateInterpolator);
|
|
animation.play(itemsAlpha);
|
|
|
|
// Invalidate the scrim throughout the animation to ensure the highlight
|
|
// cutout is correct throughout.
|
|
ValueAnimator invalidateScrim = ValueAnimator.ofFloat(0f, 1f);
|
|
invalidateScrim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
|
@Override
|
|
public void onAnimationUpdate(ValueAnimator animation) {
|
|
mLauncher.getDragLayer().invalidateScrim();
|
|
}
|
|
});
|
|
animation.play(invalidateScrim);
|
|
|
|
if (material) {
|
|
// Animate the all apps button
|
|
float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
|
|
AnimatorListenerAdapter listener =
|
|
pCb.getMaterialRevealViewAnimatorListener(revealView, buttonView);
|
|
Animator reveal = new CircleRevealOutlineProvider(width / 2, height / 2,
|
|
revealRadius, finalRadius).createRevealAnimator(revealView);
|
|
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
|
|
reveal.setDuration(revealDuration);
|
|
reveal.setStartDelay(itemsAlphaStagger);
|
|
if (listener != null) {
|
|
reveal.addListener(listener);
|
|
}
|
|
animation.play(reveal);
|
|
}
|
|
}
|
|
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
|
|
|
|
animation.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
fromView.setVisibility(View.GONE);
|
|
dispatchOnLauncherTransitionEnd(fromView, animated, true);
|
|
dispatchOnLauncherTransitionEnd(toView, animated, true);
|
|
|
|
// Run any queued runnables
|
|
if (onCompleteRunnable != null) {
|
|
onCompleteRunnable.run();
|
|
}
|
|
|
|
// Disable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
}
|
|
}
|
|
|
|
// Reset page transforms
|
|
if (contentView != null) {
|
|
contentView.setTranslationX(0);
|
|
contentView.setTranslationY(0);
|
|
contentView.setAlpha(1);
|
|
}
|
|
|
|
// This can hold unnecessary references to views.
|
|
cleanupAnimation();
|
|
pCb.onTransitionComplete();
|
|
}
|
|
});
|
|
|
|
final AnimatorSet stateAnimation = animation;
|
|
final Runnable startAnimRunnable = new Runnable() {
|
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
public void run() {
|
|
// Check that mCurrentAnimation hasn't changed while
|
|
// we waited for a layout/draw pass
|
|
if (mCurrentAnimation != stateAnimation)
|
|
return;
|
|
|
|
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
|
dispatchOnLauncherTransitionStart(toView, animated, false);
|
|
|
|
// Enable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
}
|
|
if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
|
|
v.buildLayer();
|
|
}
|
|
}
|
|
stateAnimation.start();
|
|
}
|
|
};
|
|
mCurrentAnimation = animation;
|
|
fromView.post(startAnimRunnable);
|
|
} else if (animType == PULLUP) {
|
|
// We are animating the content view alpha, so ensure we have a layer for it
|
|
layerViews.put(contentView, BUILD_AND_SET_LAYER);
|
|
|
|
animation.addListener(new AnimatorListenerAdapter() {
|
|
boolean canceled = false;
|
|
@Override
|
|
public void onAnimationCancel(Animator animation) {
|
|
canceled = true;
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
if (canceled) return;
|
|
dispatchOnLauncherTransitionEnd(fromView, animated, true);
|
|
dispatchOnLauncherTransitionEnd(toView, animated, true);
|
|
// Run any queued runnables
|
|
if (onCompleteRunnable != null) {
|
|
onCompleteRunnable.run();
|
|
}
|
|
|
|
// Disable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
}
|
|
}
|
|
|
|
cleanupAnimation();
|
|
pCb.onTransitionComplete();
|
|
}
|
|
|
|
});
|
|
boolean shouldPost = mAllAppsController.animateToWorkspace(animation, revealDurationSlide);
|
|
|
|
// Dispatch the prepare transition signal
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, multiplePagesVisible);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, multiplePagesVisible);
|
|
|
|
final AnimatorSet stateAnimation = animation;
|
|
final Runnable startAnimRunnable = new Runnable() {
|
|
public void run() {
|
|
// Check that mCurrentAnimation hasn't changed while
|
|
// we waited for a layout/draw pass
|
|
if (mCurrentAnimation != stateAnimation)
|
|
return;
|
|
|
|
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
|
dispatchOnLauncherTransitionStart(toView, animated, false);
|
|
|
|
// Enable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
}
|
|
if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) {
|
|
v.buildLayer();
|
|
}
|
|
}
|
|
|
|
// Focus the new view
|
|
toView.requestFocus();
|
|
stateAnimation.start();
|
|
}
|
|
};
|
|
mCurrentAnimation = animation;
|
|
if (shouldPost) {
|
|
fromView.post(startAnimRunnable);
|
|
} else {
|
|
startAnimRunnable.run();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Dispatches the prepare-transition event to suitable views.
|
|
*/
|
|
void dispatchOnLauncherTransitionPrepare(View v, boolean animated,
|
|
boolean multiplePagesVisible) {
|
|
if (v instanceof LauncherTransitionable) {
|
|
((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
|
|
multiplePagesVisible);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatches the start-transition event to suitable views.
|
|
*/
|
|
void dispatchOnLauncherTransitionStart(View v, boolean animated, boolean toWorkspace) {
|
|
if (v instanceof LauncherTransitionable) {
|
|
((LauncherTransitionable) v).onLauncherTransitionStart(mLauncher, animated,
|
|
toWorkspace);
|
|
}
|
|
|
|
// Update the workspace transition step as well
|
|
dispatchOnLauncherTransitionStep(v, 0f);
|
|
}
|
|
|
|
/**
|
|
* Dispatches the step-transition event to suitable views.
|
|
*/
|
|
void dispatchOnLauncherTransitionStep(View v, float t) {
|
|
if (v instanceof LauncherTransitionable) {
|
|
((LauncherTransitionable) v).onLauncherTransitionStep(mLauncher, t);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatches the end-transition event to suitable views.
|
|
*/
|
|
void dispatchOnLauncherTransitionEnd(View v, boolean animated, boolean toWorkspace) {
|
|
if (v instanceof LauncherTransitionable) {
|
|
((LauncherTransitionable) v).onLauncherTransitionEnd(mLauncher, animated,
|
|
toWorkspace);
|
|
}
|
|
|
|
// Update the workspace transition step as well
|
|
dispatchOnLauncherTransitionStep(v, 1f);
|
|
}
|
|
|
|
/**
|
|
* Cancels the current animation.
|
|
*/
|
|
private void cancelAnimation() {
|
|
if (mCurrentAnimation != null) {
|
|
mCurrentAnimation.setDuration(0);
|
|
mCurrentAnimation.cancel();
|
|
mCurrentAnimation = null;
|
|
}
|
|
}
|
|
|
|
@Thunk void cleanupAnimation() {
|
|
mCurrentAnimation = null;
|
|
}
|
|
}
|