Merge "Launcher app close transition." into ub-launcher3-master

This commit is contained in:
Jonathan Miranda 2018-01-23 17:39:28 +00:00 committed by Android (Google) Code Review
commit b57721182c
8 changed files with 324 additions and 55 deletions

View File

@ -34,6 +34,9 @@
<!-- TODO: This can be calculated using other resource values -->
<dimen name="all_apps_search_box_full_height">90dp</dimen>
<dimen name="drag_layer_trans_y">25dp</dimen>
<!-- Launcher app transition -->
<dimen name="content_trans_y">25dp</dimen>
<dimen name="workspace_trans_y">80dp</dimen>
<dimen name="shelf_min_value">-2.857dp</dimen>
</resources>

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3;
import android.animation.AnimatorSet;
import android.os.Handler;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
AnimatorSet mAnimator;
private Launcher mLauncher;
LauncherAnimationRunner(Launcher launcher) {
mLauncher = launcher;
}
@Override
public void onAnimationCancelled() {
postAtFrontOfQueueAsynchronously(mLauncher.getWindow().getDecorView().getHandler(), () -> {
if (mAnimator != null) {
mAnimator.cancel();
}
});
}
}

View File

@ -16,6 +16,7 @@
package com.android.launcher3;
import static com.android.launcher3.views.AllAppsScrim.SCRIM_PROGRESS;
import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
@ -25,10 +26,12 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Surface;
import android.view.View;
@ -39,25 +42,34 @@ import android.widget.ImageView;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.views.AllAppsScrim;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.TransactionCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
/**
* Manages the opening app animations from Launcher.
* Manages the opening and closing app transitions from Launcher.
*/
public class LauncherAppTransitionManager {
private static final String TAG = "LauncherTransition";
private static final int REFRESH_RATE_MS = 16;
private static final int CLOSING_TRANSITION_DURATION_MS = 350;
private final DragLayer mDragLayer;
private final Launcher mLauncher;
private final DeviceProfile mDeviceProfile;
private final float mDragLayerTransY;
private final float mContentTransY;
private final float mWorkspaceTransY;
// The smallest y-value the shelf will reach on screen, before overshooting back down to 0.
private final float mShelfMinValue;
private ImageView mFloatingView;
private boolean mIsRtl;
@ -67,24 +79,30 @@ public class LauncherAppTransitionManager {
mDragLayer = launcher.getDragLayer();
mDeviceProfile = launcher.getDeviceProfile();
mDragLayerTransY =
launcher.getResources().getDimensionPixelSize(R.dimen.drag_layer_trans_y);
mIsRtl = Utilities.isRtl(launcher.getResources());
Resources res = launcher.getResources();
mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
mShelfMinValue = res.getDimensionPixelSize(R.dimen.shelf_min_value);
}
/**
* @return A Bundle with remote animations that controls how the window of the opening
* targets are displayed.
*/
public Bundle getActivityLauncherOptions(View v) {
RemoteAnimationRunnerCompat runner = new RemoteAnimationRunnerCompat() {
RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
@Override
public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
Runnable finishedCallback) {
// Post at front of queue ignoring sync barriers to make sure it gets processed
// before the next frame.
postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
AnimatorSet both = new AnimatorSet();
both.play(getLauncherAnimators(v));
both.play(getAppWindowAnimators(v, targets));
both.addListener(new AnimatorListenerAdapter() {
mAnimator = new AnimatorSet();
mAnimator.play(getLauncherAnimators(v));
mAnimator.play(getWindowAnimators(v, targets));
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Reset launcher to normal state
@ -101,51 +119,62 @@ public class LauncherAppTransitionManager {
finishedCallback.run();
}
});
both.start();
mAnimator.start();
// Because t=0 has the app icon in its original spot, we can skip the first
// frame and have the same movement one frame earlier.
both.setCurrentPlayTime(REFRESH_RATE_MS);
mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
});
}
@Override
public void onAnimationCancelled() {
}
};
return ActivityOptionsCompat.makeRemoteAnimation(
new RemoteAnimationAdapterCompat(runner, 500, 380)).toBundle();
}
/**
* @return Animators that control the movements of the Launcher and icon of the opening target.
*/
private AnimatorSet getLauncherAnimators(View v) {
AnimatorSet launcherAnimators = new AnimatorSet();
launcherAnimators.play(getHideLauncherAnimator());
launcherAnimators.play(getAppIconAnimator(v));
launcherAnimators.play(getLauncherContentAnimator(false /* show */));
launcherAnimators.play(getIconAnimator(v));
return launcherAnimators;
}
private AnimatorSet getHideLauncherAnimator() {
/**
* Content is everything on screen except the background and the floating view (if any).
*
* @param show If true: Animate the content so that it moves upwards and fades in.
* Else: Animate the content so that it moves downwards and fades out.
*/
private AnimatorSet getLauncherContentAnimator(boolean show) {
AnimatorSet hideLauncher = new AnimatorSet();
// Animate the background content so that it moves downwards and fades out.
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
float[] alphas = show
? new float[] {0, 1}
: new float[] {1, 0};
float[] trans = show
? new float[] {mContentTransY, 0,}
: new float[] {0, mContentTransY};
if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) {
// All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
View appsView = mLauncher.getAppsView();
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, 1f, 0f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
alpha.setDuration(217);
alpha.setInterpolator(Interpolators.LINEAR);
ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, 0,
mDragLayerTransY);
ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
transY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
transY.setDuration(350);
hideLauncher.play(alpha);
hideLauncher.play(transY);
} else {
ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, 1f, 0f);
ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, alphas);
dragLayerAlpha.setDuration(217);
dragLayerAlpha.setInterpolator(Interpolators.LINEAR);
ObjectAnimator dragLayerTransY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
0, mDragLayerTransY);
trans);
dragLayerTransY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
dragLayerTransY.setDuration(350);
@ -155,7 +184,10 @@ public class LauncherAppTransitionManager {
return hideLauncher;
}
private AnimatorSet getAppIconAnimator(View v) {
/**
* @return Animator that controls the icon used to launch the target.
*/
private AnimatorSet getIconAnimator(View v) {
boolean isBubbleTextView = v instanceof BubbleTextView;
mFloatingView = new ImageView(mLauncher);
if (isBubbleTextView) {
@ -230,7 +262,10 @@ public class LauncherAppTransitionManager {
return appIconAnimatorSet;
}
private ValueAnimator getAppWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
/**
* @return Animator that controls the window of the opening targets.
*/
private ValueAnimator getWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
Rect bounds = new Rect();
if (v instanceof BubbleTextView) {
((BubbleTextView) v).getIconBounds(bounds);
@ -285,7 +320,7 @@ public class LauncherAppTransitionManager {
// Fade in the app window.
float alphaDelay = 0;
float alphaDuration = 50;
float alpha = getValue(1f, 0f, alphaDelay, alphaDuration,
float alpha = getValue(0f, 1f, alphaDelay, alphaDuration,
appAnimator.getDuration() * percent, Interpolators.AGGRESSIVE_EASE);
// Animate the window crop so that it starts off as a square, and then reveals
@ -318,19 +353,168 @@ public class LauncherAppTransitionManager {
matrix.reset();
isFirstFrame = false;
}
/**
* Helper method that allows us to get interpolated values for embedded
* animations with a delay and/or different duration.
*/
private float getValue(float start, float end, float delay, float duration,
float currentPlayTime, Interpolator i) {
float time = Math.max(0, currentPlayTime - delay);
float newPercent = Math.min(1f, time / duration);
newPercent = i.getInterpolation(newPercent);
return start * newPercent + end * (1 - newPercent);
}
});
return appAnimator;
}
/**
* Registers remote animations used when closing apps to home screen.
*/
public void registerRemoteAnimations() {
RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(), 0,
CLOSING_TRANSITION_DURATION_MS));
// TODO: App controlled transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
}
/**
* @return Runner that plays when user goes to Launcher
* ie. pressing home, swiping up from nav bar.
*/
private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
return new LauncherAnimationRunner(mLauncher) {
@Override
public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
Runnable finishedCallback) {
Handler handler = mLauncher.getWindow().getDecorView().getHandler();
postAtFrontOfQueueAsynchronously(handler, () -> {
// We use a separate transition for Overview mode.
if (mLauncher.isInState(LauncherState.OVERVIEW)) {
finishedCallback.run();
return;
}
mAnimator = new AnimatorSet();
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finishedCallback.run();
}
});
mAnimator.play(getClosingWindowAnimators(targets));
mAnimator.play(getLauncherResumeAnimation());
mAnimator.start();
});
}
};
}
/**
* Animator that controls the transformations of the windows the targets that are closing.
*/
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
Matrix matrix = new Matrix();
float height = mLauncher.getDeviceProfile().heightPx;
float width = mLauncher.getDeviceProfile().widthPx;
float endX = Utilities.isRtl(mLauncher.getResources()) ? -width : width;
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
closingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
boolean isFirstFrame = true;
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float percent = animation.getAnimatedFraction();
float currentPlayTime = percent * closingAnimator.getDuration();
float scale = getValue(1f, 0.8f, 0, 267, currentPlayTime,
Interpolators.AGGRESSIVE_EASE);
matrix.setScale(scale, scale);
float dX = getValue(0, endX, 0, 350, currentPlayTime,
Interpolators.AGGRESSIVE_EASE_IN_OUT);
TransactionCompat t = new TransactionCompat();
for (RemoteAnimationTargetCompat app : targets) {
if (app.mode == RemoteAnimationTargetCompat.MODE_CLOSING) {
t.setAlpha(app.leash, 1f - percent);
float dY = (height - (app.clipRect.height() * scale)) / 2f;
matrix.postTranslate(dX, dY);
t.setMatrix(app.leash, matrix);
}
// TODO: Layer should be set only once, but there is possibly a race condition
// where WindowManager is also calling setLayer.
int layer = app.mode == RemoteAnimationTargetCompat.MODE_CLOSING
? Integer.MAX_VALUE
: app.prefixOrderIndex;
t.setLayer(app.leash, layer);
if (isFirstFrame) {
t.show(app.leash);
}
}
t.apply();
matrix.reset();
isFirstFrame = false;
}
});
return closingAnimator;
}
/**
* @return Animator that modifies Launcher as a result from {@link #getWallpaperOpenRunner}.
*/
private AnimatorSet getLauncherResumeAnimation() {
if (mLauncher.isInState(LauncherState.ALL_APPS)
|| mLauncher.getDeviceProfile().isVerticalBarLayout()) {
return getLauncherContentAnimator(true /* show */);
} else {
AnimatorSet workspaceAnimator = new AnimatorSet();
mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY);
mLauncher.getWorkspace().setAlpha(0f);
workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(),
View.TRANSLATION_Y, mWorkspaceTransY, 0));
workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(), View.ALPHA,
0, 1f));
workspaceAnimator.setStartDelay(150);
workspaceAnimator.setDuration(333);
workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
// Animate the shelf
AllAppsScrim allAppsScrim = mLauncher.findViewById(R.id.all_apps_scrim);
View hotseat = mLauncher.getHotseat();
final float endY = mShelfMinValue;
int startY = hotseat.getMeasuredHeight()
+ (allAppsScrim.getShadowBitmap().getHeight() / 2);
hotseat.setTranslationY(startY);
allAppsScrim.setTranslationY(startY);
AnimatorSet hotseatSlideIn = new AnimatorSet();
hotseatSlideIn.play(ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, startY, endY));
hotseatSlideIn.play(ObjectAnimator.ofFloat(allAppsScrim, SCRIM_PROGRESS, startY, endY));
hotseatSlideIn.setStartDelay(150);
hotseatSlideIn.setDuration(317);
hotseatSlideIn.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
AnimatorSet hotseatOvershoot = new AnimatorSet();
hotseatOvershoot.play(ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, endY, 0));
hotseatOvershoot.play(ObjectAnimator.ofFloat(allAppsScrim, SCRIM_PROGRESS, endY, 0));
hotseatOvershoot.setDuration(153);
hotseatOvershoot.setInterpolator(Interpolators.OVERSHOOT_0);
AnimatorSet resumeLauncherAnimation = new AnimatorSet();
resumeLauncherAnimation.play(workspaceAnimator);
resumeLauncherAnimation.playSequentially(hotseatSlideIn, hotseatOvershoot);
return resumeLauncherAnimation;
}
}
/**
* Helper method that allows us to get interpolated values for embedded
* animations with a delay and/or different duration.
*/
private static float getValue(float start, float end, float delay, float duration,
float currentPlayTime, Interpolator i) {
float time = Math.max(0, currentPlayTime - delay);
float newPercent = Math.min(1f, time / duration);
newPercent = i.getInterpolation(newPercent);
return end * newPercent + start * (1 - newPercent);
}
}

View File

@ -31,7 +31,6 @@ import com.android.launcher3.graphics.BitmapRenderer;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.RecentsView;
import com.android.systemui.shared.recents.view.RecentsTransition;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
public class UiFactory {
@ -89,4 +88,12 @@ public class UiFactory {
return launcher.getDefaultActivityLaunchOptions(v);
}
}
public static void registerRemoteAnimations(Launcher launcher) {
try {
new LauncherAppTransitionManager(launcher).registerRemoteAnimations();
} catch (NoClassDefFoundError e) {
// Gracefully fall back if the user's platform doesn't have the latest changes
}
}
}

View File

@ -403,6 +403,10 @@ public class Launcher extends BaseActivity
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
if (!isInMultiWindowModeCompat()) {
UiFactory.registerRemoteAnimations(this);
}
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
}

View File

@ -20,6 +20,7 @@ import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import android.view.animation.PathInterpolator;
@ -43,6 +44,9 @@ public class Interpolators {
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f);
public static final Interpolator AGGRESSIVE_EASE_IN_OUT = new PathInterpolator(0.8f,0, 0.4f, 1);
public static final Interpolator OVERSHOOT_0 = new OvershootInterpolator(0);
/**
* Inversion of zInterpolate, compounded with an ease-out.

View File

@ -23,6 +23,7 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.util.Property;
import android.view.View;
import com.android.launcher3.DeviceProfile;
@ -60,11 +61,25 @@ public class AllAppsScrim extends View implements OnChangeListener, Insettable {
private final NinePatchDrawHelper mShadowHelper = new NinePatchDrawHelper();
private float mProgress;
private int mFillAlpha;
private float mDrawHeight;
private float mDrawOffsetY;
public static final Property<AllAppsScrim, Float> SCRIM_PROGRESS =
new Property<AllAppsScrim, Float>(Float.class, "allAppsScrimProgress") {
@Override
public Float get(AllAppsScrim allAppsScrim) {
return allAppsScrim.getProgress();
}
@Override
public void set(AllAppsScrim allAppsScrim, Float progress) {
allAppsScrim.setProgress(progress);
}
};
public AllAppsScrim(Context context) {
this(context, null);
}
@ -109,6 +124,10 @@ public class AllAppsScrim extends View implements OnChangeListener, Insettable {
return result;
}
public Bitmap getShadowBitmap() {
return mShadowBitmap;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@ -154,18 +173,24 @@ public class AllAppsScrim extends View implements OnChangeListener, Insettable {
}
public void setProgress(float translateY, float alpha) {
float newAlpha = Math.round(alpha * mAlphaRange + mMinAlpha);
int newAlpha = Math.round(alpha * mAlphaRange + mMinAlpha);
if (newAlpha != mFillAlpha) {
mFillAlpha = newAlpha;
mFillPaint.setAlpha(mFillAlpha);
invalidateDrawRect();
}
setProgress(translateY);
}
public void setProgress(float translateY) {
// Negative translation means the scrim is moving up. For negative translation, we change
// draw offset as it requires redraw (since more area of the scrim needs to be shown). For
// position translation, we simply translate the scrim down as it avoids invalidate and
// hence could be optimized by the platform.
float drawOffsetY = Math.min(translateY, 0);
if (newAlpha != mFillAlpha || drawOffsetY != mDrawOffsetY) {
invalidateDrawRect();
mFillAlpha = Math.round(alpha * mAlphaRange + mMinAlpha);
mFillPaint.setAlpha(mFillAlpha);
if (drawOffsetY != mDrawOffsetY) {
mDrawOffsetY = drawOffsetY;
invalidateDrawRect();
}
@ -173,6 +198,10 @@ public class AllAppsScrim extends View implements OnChangeListener, Insettable {
setTranslationY(Math.max(translateY, 0));
}
public float getProgress() {
return mProgress;
}
private void invalidateDrawRect() {
mDrawRect.top = (int) (getHeight()
+ mDrawOffsetY - mDrawHeight + mPadding.top - mShadowBlur - 0.5f);

View File

@ -18,21 +18,15 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.OVERVIEW;
import android.app.ActivityOptions;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.BitmapRenderer;
import com.android.launcher3.util.TouchController;
@ -71,4 +65,6 @@ public class UiFactory {
public static Bundle getActivityLaunchOptions(Launcher launcher, View v) {
return launcher.getDefaultActivityLaunchOptions(v);
}
public static void registerRemoteAnimations(Launcher launcher) { }
}