mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-18 10:18:20 +00:00
-> Pulling out the parts of device profile which can (and need to be) initialized and accessed without access to an Activity context, ie. the invariant bits. -> The invariant bits are stored in InvariantDeviceProfile which is initialized statically from LauncherAppState. -> The DeviceProfile contains the Activity context-dependent bits, and we will create one of these for each Activity instance, and this instance is accessed through the Launcher activity. -> It's possible that we can continue to refactor this such that all appropriate dimensions can be computed without an Activity context (by only specifying orientation). This would be an extension of this CL and allow us to know exactly how launcher will look in both orientations from any context. Sets the stage for some improvements around b/19514688 Change-Id: Ia7daccf14d8ca2b9cb340b8780b684769e9f1892
786 lines
34 KiB
Java
786 lines
34 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.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.ViewAnimationUtils;
|
|
import android.view.animation.AccelerateInterpolator;
|
|
import android.view.animation.DecelerateInterpolator;
|
|
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 {
|
|
|
|
/**
|
|
* Callbacks made during the state transition
|
|
*/
|
|
interface Callbacks {
|
|
public void onStateTransitionHideSearchBar();
|
|
}
|
|
|
|
/**
|
|
* Private callbacks made during transition setup.
|
|
*/
|
|
static abstract class PrivateTransitionCallbacks {
|
|
void onRevealViewVisible(View revealView, View contentView, View allAppsButtonView) {}
|
|
void onAnimationComplete(View revealView, View contentView, View allAppsButtonView) {}
|
|
float getMaterialRevealViewFinalAlpha(View revealView) {
|
|
return 0;
|
|
}
|
|
float getMaterialRevealViewFinalXDrift(View revealView) {
|
|
return 0;
|
|
}
|
|
float getMaterialRevealViewFinalYDrift(View revealView) {
|
|
return 0;
|
|
}
|
|
float getMaterialRevealViewStartFinalRadius() {
|
|
return 0;
|
|
}
|
|
AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(View revealView,
|
|
View allAppsButtonView) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static final String TAG = "LauncherStateTransitionAnimation";
|
|
|
|
// 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 Callbacks mCb;
|
|
@Thunk AnimatorSet mStateAnimation;
|
|
|
|
public LauncherStateTransitionAnimation(Launcher l, Callbacks cb) {
|
|
mLauncher = l;
|
|
mCb = cb;
|
|
}
|
|
|
|
/**
|
|
* Starts an animation to the apps view.
|
|
*/
|
|
public void startAnimationToAllApps(final boolean animated) {
|
|
final AppsContainerView toView = mLauncher.getAppsView();
|
|
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
|
|
private int[] mAllAppsToPanelDelta;
|
|
|
|
@Override
|
|
public void onRevealViewVisible(View revealView, View contentView,
|
|
View allAppsButtonView) {
|
|
toView.setBackground(null);
|
|
// Get the y delta between the center of the page and the center of the all apps
|
|
// button
|
|
mAllAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
|
|
allAppsButtonView, null);
|
|
}
|
|
@Override
|
|
public float getMaterialRevealViewFinalAlpha(View revealView) {
|
|
return 1f;
|
|
}
|
|
@Override
|
|
public float getMaterialRevealViewFinalXDrift(View revealView) {
|
|
return mAllAppsToPanelDelta[0];
|
|
}
|
|
@Override
|
|
public float getMaterialRevealViewFinalYDrift(View revealView) {
|
|
return mAllAppsToPanelDelta[1];
|
|
}
|
|
@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);
|
|
}
|
|
};
|
|
}
|
|
};
|
|
startAnimationToOverlay(Workspace.State.NORMAL_HIDDEN, toView, toView.getContentView(),
|
|
toView.getRevealView(), animated,
|
|
!mLauncher.isAllAppsSearchOverridden() /* hideSearchBar */, cb);
|
|
}
|
|
|
|
/**
|
|
* Starts an animation to the widgets view.
|
|
*/
|
|
public void startAnimationToWidgets(final boolean animated) {
|
|
final WidgetsContainerView toView = mLauncher.getWidgetsView();
|
|
final Resources res = mLauncher.getResources();
|
|
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
|
|
@Override
|
|
public void onRevealViewVisible(View revealView, View contentView,
|
|
View allAppsButtonView) {
|
|
revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
|
|
}
|
|
@Override
|
|
public float getMaterialRevealViewFinalAlpha(View revealView) {
|
|
return 0.3f;
|
|
}
|
|
@Override
|
|
public float getMaterialRevealViewFinalYDrift(View revealView) {
|
|
return revealView.getMeasuredHeight() / 2;
|
|
}
|
|
};
|
|
startAnimationToOverlay(Workspace.State.OVERVIEW_HIDDEN, toView,
|
|
toView.getContentView(), toView.getRevealView(), animated, true /* hideSearchBar */,
|
|
cb);
|
|
}
|
|
|
|
/**
|
|
* Starts and animation to the workspace from the current overlay view.
|
|
*/
|
|
public void startAnimationToWorkspace(final Launcher.State fromState,
|
|
final Workspace.State toWorkspaceState, final int toWorkspacePage,
|
|
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) {
|
|
startAnimationToWorkspaceFromAllApps(fromState, toWorkspaceState, toWorkspacePage,
|
|
animated, onCompleteRunnable);
|
|
} else {
|
|
startAnimationToWorkspaceFromWidgets(fromState, toWorkspaceState, toWorkspacePage,
|
|
animated, onCompleteRunnable);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates and starts a new animation to a particular overlay view.
|
|
*/
|
|
@SuppressLint("NewApi")
|
|
private void startAnimationToOverlay(final Workspace.State toWorkspaceState, final View toView,
|
|
final View contentView, final View revealView, final boolean animated,
|
|
final boolean hideSearchBar, final PrivateTransitionCallbacks pCb) {
|
|
final Resources res = mLauncher.getResources();
|
|
final boolean material = Utilities.isLmpOrAbove();
|
|
final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime);
|
|
final int itemsAlphaStagger =
|
|
res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
|
|
|
|
final View allAppsButtonView = mLauncher.getAllAppsButton();
|
|
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 = allAppsButtonView != null;
|
|
|
|
// Cancel the current animation
|
|
cancelAnimation();
|
|
|
|
// Create the workspace animation.
|
|
// NOTE: this call apparently also sets the state for the workspace if !animated
|
|
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, -1,
|
|
animated, layerViews);
|
|
|
|
if (animated && initialized) {
|
|
mStateAnimation = LauncherAnimUtils.createAnimatorSet();
|
|
|
|
// Setup the reveal view animation
|
|
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);
|
|
pCb.onRevealViewVisible(revealView, contentView, allAppsButtonView);
|
|
|
|
// Calculate the final animation values
|
|
final float revealViewToAlpha;
|
|
final float revealViewToXDrift;
|
|
final float revealViewToYDrift;
|
|
if (material) {
|
|
revealViewToAlpha = pCb.getMaterialRevealViewFinalAlpha(revealView);
|
|
revealViewToYDrift = pCb.getMaterialRevealViewFinalYDrift(revealView);
|
|
revealViewToXDrift = pCb.getMaterialRevealViewFinalXDrift(revealView);
|
|
} else {
|
|
revealViewToAlpha = 0f;
|
|
revealViewToYDrift = 2 * height / 3;
|
|
revealViewToXDrift = 0;
|
|
}
|
|
|
|
// Create the animators
|
|
PropertyValuesHolder panelAlpha =
|
|
PropertyValuesHolder.ofFloat("alpha", revealViewToAlpha, 1f);
|
|
PropertyValuesHolder panelDriftY =
|
|
PropertyValuesHolder.ofFloat("translationY", revealViewToYDrift, 0);
|
|
PropertyValuesHolder panelDriftX =
|
|
PropertyValuesHolder.ofFloat("translationX", 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);
|
|
mStateAnimation.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);
|
|
mStateAnimation.play(pageDrift);
|
|
|
|
ObjectAnimator itemsAlpha = ObjectAnimator.ofFloat(contentView, "alpha", 0f, 1f);
|
|
itemsAlpha.setDuration(revealDuration);
|
|
itemsAlpha.setInterpolator(new AccelerateInterpolator(1.5f));
|
|
itemsAlpha.setStartDelay(itemsAlphaStagger);
|
|
mStateAnimation.play(itemsAlpha);
|
|
|
|
if (material) {
|
|
// Animate the all apps button
|
|
float startRadius = pCb.getMaterialRevealViewStartFinalRadius();
|
|
AnimatorListenerAdapter listener = pCb.getMaterialRevealViewAnimatorListener(
|
|
revealView, allAppsButtonView);
|
|
Animator reveal = ViewAnimationUtils.createCircularReveal(revealView, width / 2,
|
|
height / 2, startRadius, revealRadius);
|
|
reveal.setDuration(revealDuration);
|
|
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
|
|
if (listener != null) {
|
|
reveal.addListener(listener);
|
|
}
|
|
mStateAnimation.play(reveal);
|
|
}
|
|
|
|
mStateAnimation.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);
|
|
pCb.onAnimationComplete(revealView, contentView, allAppsButtonView);
|
|
|
|
// Disable all necessary layers
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
}
|
|
}
|
|
|
|
if (hideSearchBar) {
|
|
mCb.onStateTransitionHideSearchBar();
|
|
}
|
|
|
|
// This can hold unnecessary references to views.
|
|
mStateAnimation = null;
|
|
}
|
|
|
|
});
|
|
|
|
// Play the workspace animation
|
|
if (workspaceAnim != null) {
|
|
mStateAnimation.play(workspaceAnim);
|
|
}
|
|
|
|
// Dispatch the prepare transition signal
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, false);
|
|
|
|
|
|
final AnimatorSet stateAnimation = mStateAnimation;
|
|
final Runnable startAnimRunnable = new Runnable() {
|
|
public void run() {
|
|
// Check that mStateAnimation hasn't changed while
|
|
// we waited for a layout/draw pass
|
|
if (mStateAnimation != stateAnimation)
|
|
return;
|
|
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
|
dispatchOnLauncherTransitionStart(toView, animated, false);
|
|
|
|
// Enable all necessary layers
|
|
boolean isLmpOrAbove = Utilities.isLmpOrAbove();
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
}
|
|
if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
|
|
v.buildLayer();
|
|
}
|
|
}
|
|
|
|
// Focus the new view
|
|
toView.requestFocus();
|
|
|
|
mStateAnimation.start();
|
|
}
|
|
};
|
|
toView.bringToFront();
|
|
toView.setVisibility(View.VISIBLE);
|
|
toView.post(startAnimRunnable);
|
|
} else {
|
|
toView.setTranslationX(0.0f);
|
|
toView.setTranslationY(0.0f);
|
|
toView.setScaleX(1.0f);
|
|
toView.setScaleY(1.0f);
|
|
toView.setVisibility(View.VISIBLE);
|
|
toView.bringToFront();
|
|
|
|
// Show the content view
|
|
contentView.setVisibility(View.VISIBLE);
|
|
|
|
if (hideSearchBar) {
|
|
mCb.onStateTransitionHideSearchBar();
|
|
}
|
|
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, false);
|
|
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
|
dispatchOnLauncherTransitionEnd(fromView, animated, false);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, false);
|
|
dispatchOnLauncherTransitionStart(toView, animated, false);
|
|
dispatchOnLauncherTransitionEnd(toView, animated, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Starts and animation to the workspace from the apps view.
|
|
*/
|
|
private void startAnimationToWorkspaceFromAllApps(final Launcher.State fromState,
|
|
final Workspace.State toWorkspaceState, final int toWorkspacePage,
|
|
final boolean animated, final Runnable onCompleteRunnable) {
|
|
AppsContainerView appsView = mLauncher.getAppsView();
|
|
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
|
|
int[] mAllAppsToPanelDelta;
|
|
|
|
@Override
|
|
public void onRevealViewVisible(View revealView, View contentView,
|
|
View allAppsButtonView) {
|
|
// Get the y delta between the center of the page and the center of the all apps
|
|
// button
|
|
mAllAppsToPanelDelta = Utilities.getCenterDeltaInScreenSpace(revealView,
|
|
allAppsButtonView, null);
|
|
}
|
|
@Override
|
|
public float getMaterialRevealViewFinalXDrift(View revealView) {
|
|
return mAllAppsToPanelDelta[0];
|
|
}
|
|
@Override
|
|
public float getMaterialRevealViewFinalYDrift(View revealView) {
|
|
return mAllAppsToPanelDelta[1];
|
|
}
|
|
@Override
|
|
float getMaterialRevealViewFinalAlpha(View revealView) {
|
|
// No alpha anim from all apps
|
|
return 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);
|
|
}
|
|
};
|
|
}
|
|
};
|
|
startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, appsView,
|
|
appsView.getContentView(), appsView.getRevealView(), animated, onCompleteRunnable,
|
|
cb);
|
|
}
|
|
|
|
/**
|
|
* Starts and animation to the workspace from the widgets view.
|
|
*/
|
|
private void startAnimationToWorkspaceFromWidgets(final Launcher.State fromState,
|
|
final Workspace.State toWorkspaceState, final int toWorkspacePage,
|
|
final boolean animated, final Runnable onCompleteRunnable) {
|
|
final WidgetsContainerView widgetsView = mLauncher.getWidgetsView();
|
|
final Resources res = mLauncher.getResources();
|
|
PrivateTransitionCallbacks cb = new PrivateTransitionCallbacks() {
|
|
@Override
|
|
public void onRevealViewVisible(View revealView, View contentView,
|
|
View allAppsButtonView) {
|
|
revealView.setBackground(res.getDrawable(R.drawable.quantum_panel_dark));
|
|
}
|
|
@Override
|
|
public float getMaterialRevealViewFinalYDrift(View revealView) {
|
|
return revealView.getMeasuredHeight() / 2;
|
|
}
|
|
@Override
|
|
float getMaterialRevealViewFinalAlpha(View revealView) {
|
|
return 0.4f;
|
|
}
|
|
@Override
|
|
public AnimatorListenerAdapter getMaterialRevealViewAnimatorListener(
|
|
final View revealView, final View allAppsButtonView) {
|
|
return new AnimatorListenerAdapter() {
|
|
public void onAnimationEnd(Animator animation) {
|
|
// Hide the reveal view
|
|
revealView.setVisibility(View.INVISIBLE);
|
|
}
|
|
};
|
|
}
|
|
};
|
|
startAnimationToWorkspaceFromOverlay(toWorkspaceState, toWorkspacePage, widgetsView,
|
|
widgetsView.getContentView(), widgetsView.getRevealView(), animated,
|
|
onCompleteRunnable, cb);
|
|
}
|
|
|
|
/**
|
|
* Creates and starts a new animation to the workspace.
|
|
*/
|
|
private void startAnimationToWorkspaceFromOverlay(final Workspace.State toWorkspaceState,
|
|
final int toWorkspacePage, final View fromView, final View contentView,
|
|
final View revealView, final boolean animated, final Runnable onCompleteRunnable,
|
|
final PrivateTransitionCallbacks pCb) {
|
|
final Resources res = mLauncher.getResources();
|
|
final boolean material = Utilities.isLmpOrAbove();
|
|
final int revealDuration = res.getInteger(R.integer.config_appsCustomizeRevealTime);
|
|
final int itemsAlphaStagger =
|
|
res.getInteger(R.integer.config_appsCustomizeItemsAlphaStagger);
|
|
|
|
final View allAppsButtonView = mLauncher.getAllAppsButton();
|
|
final View toView = mLauncher.getWorkspace();
|
|
|
|
final HashMap<View, Integer> layerViews = new HashMap<>();
|
|
|
|
// If for some reason our views aren't initialized, don't animate
|
|
boolean initialized = allAppsButtonView != null;
|
|
|
|
// Cancel the current animation
|
|
cancelAnimation();
|
|
|
|
// Create the workspace animation.
|
|
// NOTE: this call apparently also sets the state for the workspace if !animated
|
|
Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState,
|
|
toWorkspacePage, animated, layerViews);
|
|
|
|
if (animated && initialized) {
|
|
mStateAnimation = LauncherAnimUtils.createAnimatorSet();
|
|
|
|
// Play the workspace animation
|
|
if (workspaceAnim != null) {
|
|
mStateAnimation.play(workspaceAnim);
|
|
}
|
|
|
|
// 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);
|
|
pCb.onRevealViewVisible(revealView, contentView, allAppsButtonView);
|
|
|
|
// Calculate the final animation values
|
|
final float revealViewToXDrift;
|
|
final float revealViewToYDrift;
|
|
if (material) {
|
|
revealViewToYDrift = pCb.getMaterialRevealViewFinalYDrift(revealView);
|
|
revealViewToXDrift = pCb.getMaterialRevealViewFinalXDrift(revealView);
|
|
} 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 = LauncherAnimUtils.ofFloat(revealView, "translationY",
|
|
0, revealViewToYDrift);
|
|
panelDriftY.setDuration(revealDuration - SINGLE_FRAME_DELAY);
|
|
panelDriftY.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
|
|
panelDriftY.setInterpolator(decelerateInterpolator);
|
|
mStateAnimation.play(panelDriftY);
|
|
|
|
ObjectAnimator panelDriftX = LauncherAnimUtils.ofFloat(revealView, "translationX",
|
|
0, revealViewToXDrift);
|
|
panelDriftX.setDuration(revealDuration - SINGLE_FRAME_DELAY);
|
|
panelDriftX.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
|
|
panelDriftX.setInterpolator(decelerateInterpolator);
|
|
mStateAnimation.play(panelDriftX);
|
|
|
|
// Setup animation for the reveal panel alpha
|
|
final float revealViewToAlpha = !material ? 0f :
|
|
pCb.getMaterialRevealViewFinalAlpha(revealView);
|
|
if (revealViewToAlpha != 1f) {
|
|
ObjectAnimator panelAlpha = LauncherAnimUtils.ofFloat(revealView, "alpha",
|
|
1f, revealViewToAlpha);
|
|
panelAlpha.setDuration(material ? revealDuration : 150);
|
|
panelAlpha.setStartDelay(material ? 0 : itemsAlphaStagger + SINGLE_FRAME_DELAY);
|
|
panelAlpha.setInterpolator(decelerateInterpolator);
|
|
mStateAnimation.play(panelAlpha);
|
|
}
|
|
|
|
// Setup the animation for the content view
|
|
layerViews.put(contentView, BUILD_AND_SET_LAYER);
|
|
|
|
// Create the individual animators
|
|
ObjectAnimator pageDrift = LauncherAnimUtils.ofFloat(contentView, "translationY",
|
|
0, revealViewToYDrift);
|
|
contentView.setTranslationY(0);
|
|
pageDrift.setDuration(revealDuration - SINGLE_FRAME_DELAY);
|
|
pageDrift.setInterpolator(decelerateInterpolator);
|
|
pageDrift.setStartDelay(itemsAlphaStagger + SINGLE_FRAME_DELAY);
|
|
mStateAnimation.play(pageDrift);
|
|
|
|
contentView.setAlpha(1f);
|
|
ObjectAnimator itemsAlpha = LauncherAnimUtils.ofFloat(contentView, "alpha", 1f, 0f);
|
|
itemsAlpha.setDuration(100);
|
|
itemsAlpha.setInterpolator(decelerateInterpolator);
|
|
mStateAnimation.play(itemsAlpha);
|
|
|
|
if (material) {
|
|
// Animate the all apps button
|
|
float finalRadius = pCb.getMaterialRevealViewStartFinalRadius();
|
|
AnimatorListenerAdapter listener =
|
|
pCb.getMaterialRevealViewAnimatorListener(revealView, allAppsButtonView);
|
|
Animator reveal =
|
|
LauncherAnimUtils.createCircularReveal(revealView, width / 2,
|
|
height / 2, revealRadius, finalRadius);
|
|
reveal.setInterpolator(new LogDecelerateInterpolator(100, 0));
|
|
reveal.setDuration(revealDuration);
|
|
reveal.setStartDelay(itemsAlphaStagger);
|
|
if (listener != null) {
|
|
reveal.addListener(listener);
|
|
}
|
|
mStateAnimation.play(reveal);
|
|
}
|
|
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, true);
|
|
}
|
|
|
|
mStateAnimation.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();
|
|
}
|
|
|
|
// Animation complete callback
|
|
pCb.onAnimationComplete(revealView, contentView, allAppsButtonView);
|
|
|
|
// 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.
|
|
mStateAnimation = null;
|
|
}
|
|
});
|
|
|
|
final AnimatorSet stateAnimation = mStateAnimation;
|
|
final Runnable startAnimRunnable = new Runnable() {
|
|
public void run() {
|
|
// Check that mStateAnimation hasn't changed while
|
|
// we waited for a layout/draw pass
|
|
if (mStateAnimation != stateAnimation)
|
|
return;
|
|
dispatchOnLauncherTransitionStart(fromView, animated, false);
|
|
dispatchOnLauncherTransitionStart(toView, animated, false);
|
|
|
|
// Enable all necessary layers
|
|
boolean isLmpOrAbove = Utilities.isLmpOrAbove();
|
|
for (View v : layerViews.keySet()) {
|
|
if (layerViews.get(v) == BUILD_AND_SET_LAYER) {
|
|
v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
|
}
|
|
if (isLmpOrAbove && Utilities.isViewAttachedToWindow(v)) {
|
|
v.buildLayer();
|
|
}
|
|
}
|
|
mStateAnimation.start();
|
|
}
|
|
};
|
|
fromView.post(startAnimRunnable);
|
|
} else {
|
|
fromView.setVisibility(View.GONE);
|
|
dispatchOnLauncherTransitionPrepare(fromView, animated, true);
|
|
dispatchOnLauncherTransitionStart(fromView, animated, true);
|
|
dispatchOnLauncherTransitionEnd(fromView, animated, true);
|
|
dispatchOnLauncherTransitionPrepare(toView, animated, true);
|
|
dispatchOnLauncherTransitionStart(toView, animated, true);
|
|
dispatchOnLauncherTransitionEnd(toView, animated, true);
|
|
|
|
// Run any queued runnables
|
|
if (onCompleteRunnable != null) {
|
|
onCompleteRunnable.run();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Dispatches the prepare-transition event to suitable views.
|
|
*/
|
|
void dispatchOnLauncherTransitionPrepare(View v, boolean animated, boolean toWorkspace) {
|
|
if (v instanceof LauncherTransitionable) {
|
|
((LauncherTransitionable) v).onLauncherTransitionPrepare(mLauncher, animated,
|
|
toWorkspace);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 (mStateAnimation != null) {
|
|
mStateAnimation.setDuration(0);
|
|
mStateAnimation.cancel();
|
|
mStateAnimation = null;
|
|
}
|
|
}
|
|
} |