Merge "Refactor SplashScreenAnimation so it can be reused in other parts of the code." into tm-qpr-dev
This commit is contained in:
@@ -45,6 +45,9 @@ filegroup {
|
|||||||
"src/com/android/wm/shell/util/**/*.java",
|
"src/com/android/wm/shell/util/**/*.java",
|
||||||
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
|
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
|
||||||
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
|
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
|
||||||
|
"src/com/android/wm/shell/common/TransactionPool.java",
|
||||||
|
"src/com/android/wm/shell/animation/Interpolators.java",
|
||||||
|
"src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
|
||||||
],
|
],
|
||||||
path: "src",
|
path: "src",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,38 +15,20 @@
|
|||||||
*/
|
*/
|
||||||
package com.android.wm.shell.startingsurface;
|
package com.android.wm.shell.startingsurface;
|
||||||
|
|
||||||
import static android.view.Choreographer.CALLBACK_COMMIT;
|
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
|
|
||||||
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
|
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
|
||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.BlendMode;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Matrix;
|
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.RadialGradient;
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.Shader;
|
|
||||||
import android.util.MathUtils;
|
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.view.Choreographer;
|
|
||||||
import android.view.SurfaceControl;
|
import android.view.SurfaceControl;
|
||||||
import android.view.SyncRtSurfaceTransactionApplier;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.view.animation.Interpolator;
|
|
||||||
import android.view.animation.PathInterpolator;
|
|
||||||
import android.window.SplashScreenView;
|
import android.window.SplashScreenView;
|
||||||
|
|
||||||
import com.android.internal.jank.InteractionJankMonitor;
|
import com.android.internal.jank.InteractionJankMonitor;
|
||||||
import com.android.wm.shell.R;
|
import com.android.wm.shell.R;
|
||||||
import com.android.wm.shell.animation.Interpolators;
|
|
||||||
import com.android.wm.shell.common.TransactionPool;
|
import com.android.wm.shell.common.TransactionPool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,14 +37,8 @@ import com.android.wm.shell.common.TransactionPool;
|
|||||||
*/
|
*/
|
||||||
public class SplashScreenExitAnimation implements Animator.AnimatorListener {
|
public class SplashScreenExitAnimation implements Animator.AnimatorListener {
|
||||||
private static final boolean DEBUG_EXIT_ANIMATION = false;
|
private static final boolean DEBUG_EXIT_ANIMATION = false;
|
||||||
private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
|
|
||||||
private static final String TAG = StartingWindowController.TAG;
|
private static final String TAG = StartingWindowController.TAG;
|
||||||
|
|
||||||
private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
|
|
||||||
private static final Interpolator MASK_RADIUS_INTERPOLATOR =
|
|
||||||
new PathInterpolator(0f, 0f, 0.4f, 1f);
|
|
||||||
private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
|
|
||||||
|
|
||||||
private final SurfaceControl mFirstWindowSurface;
|
private final SurfaceControl mFirstWindowSurface;
|
||||||
private final Rect mFirstWindowFrame = new Rect();
|
private final Rect mFirstWindowFrame = new Rect();
|
||||||
private final SplashScreenView mSplashScreenView;
|
private final SplashScreenView mSplashScreenView;
|
||||||
@@ -75,9 +51,6 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
|
|||||||
private final float mBrandingStartAlpha;
|
private final float mBrandingStartAlpha;
|
||||||
private final TransactionPool mTransactionPool;
|
private final TransactionPool mTransactionPool;
|
||||||
|
|
||||||
private ValueAnimator mMainAnimator;
|
|
||||||
private ShiftUpAnimation mShiftUpAnimation;
|
|
||||||
private RadialVanishAnimation mRadialVanishAnimation;
|
|
||||||
private Runnable mFinishCallback;
|
private Runnable mFinishCallback;
|
||||||
|
|
||||||
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
|
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
|
||||||
@@ -121,187 +94,10 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void startAnimations() {
|
void startAnimations() {
|
||||||
mMainAnimator = createAnimator();
|
SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
|
||||||
mMainAnimator.start();
|
mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
|
||||||
}
|
mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
|
||||||
|
mAppRevealDuration, this);
|
||||||
// fade out icon, reveal app, shift up main window
|
|
||||||
private ValueAnimator createAnimator() {
|
|
||||||
// reveal app
|
|
||||||
final float transparentRatio = 0.8f;
|
|
||||||
final int globalHeight = mSplashScreenView.getHeight();
|
|
||||||
final int verticalCircleCenter = 0;
|
|
||||||
final int finalVerticalLength = globalHeight - verticalCircleCenter;
|
|
||||||
final int halfWidth = mSplashScreenView.getWidth() / 2;
|
|
||||||
final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
|
|
||||||
Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
|
|
||||||
final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
|
|
||||||
final float[] stops = {0f, transparentRatio, 1f};
|
|
||||||
|
|
||||||
mRadialVanishAnimation = new RadialVanishAnimation(mSplashScreenView);
|
|
||||||
mRadialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
|
|
||||||
mRadialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
|
|
||||||
mRadialVanishAnimation.setRadialPaintParam(colors, stops);
|
|
||||||
|
|
||||||
if (mFirstWindowSurface != null && mFirstWindowSurface.isValid()) {
|
|
||||||
// shift up main window
|
|
||||||
View occludeHoleView = new View(mSplashScreenView.getContext());
|
|
||||||
if (DEBUG_EXIT_ANIMATION_BLEND) {
|
|
||||||
occludeHoleView.setBackgroundColor(Color.BLUE);
|
|
||||||
} else {
|
|
||||||
occludeHoleView.setBackgroundColor(mSplashScreenView.getInitBackgroundColor());
|
|
||||||
}
|
|
||||||
final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
|
|
||||||
WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
|
|
||||||
mSplashScreenView.addView(occludeHoleView, params);
|
|
||||||
|
|
||||||
mShiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView);
|
|
||||||
}
|
|
||||||
|
|
||||||
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
|
|
||||||
animator.setDuration(mAnimationDuration);
|
|
||||||
animator.setInterpolator(Interpolators.LINEAR);
|
|
||||||
animator.addListener(this);
|
|
||||||
animator.addUpdateListener(a -> onAnimationProgress((float) a.getAnimatedValue()));
|
|
||||||
return animator;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RadialVanishAnimation extends View {
|
|
||||||
private final SplashScreenView mView;
|
|
||||||
private int mInitRadius;
|
|
||||||
private int mFinishRadius;
|
|
||||||
|
|
||||||
private final Point mCircleCenter = new Point();
|
|
||||||
private final Matrix mVanishMatrix = new Matrix();
|
|
||||||
private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
||||||
|
|
||||||
RadialVanishAnimation(SplashScreenView target) {
|
|
||||||
super(target.getContext());
|
|
||||||
mView = target;
|
|
||||||
mView.addView(this);
|
|
||||||
mVanishPaint.setAlpha(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onAnimationProgress(float linearProgress) {
|
|
||||||
if (mVanishPaint.getShader() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
|
|
||||||
final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
|
|
||||||
final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
|
|
||||||
|
|
||||||
mVanishMatrix.setScale(scale, scale);
|
|
||||||
mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
|
|
||||||
mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
|
|
||||||
mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
|
|
||||||
|
|
||||||
postInvalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setRadius(int initRadius, int finishRadius) {
|
|
||||||
if (DEBUG_EXIT_ANIMATION) {
|
|
||||||
Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
|
|
||||||
+ " final " + finishRadius);
|
|
||||||
}
|
|
||||||
mInitRadius = initRadius;
|
|
||||||
mFinishRadius = finishRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setCircleCenter(int x, int y) {
|
|
||||||
if (DEBUG_EXIT_ANIMATION) {
|
|
||||||
Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
|
|
||||||
}
|
|
||||||
mCircleCenter.set(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setRadialPaintParam(int[] colors, float[] stops) {
|
|
||||||
// setup gradient shader
|
|
||||||
final RadialGradient rShader =
|
|
||||||
new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
|
|
||||||
mVanishPaint.setShader(rShader);
|
|
||||||
if (!DEBUG_EXIT_ANIMATION_BLEND) {
|
|
||||||
// We blend the reveal gradient with the splash screen using DST_OUT so that the
|
|
||||||
// splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
|
|
||||||
// fully invisible when radius = finishRadius AND gradient opacity is 1.
|
|
||||||
mVanishPaint.setBlendMode(BlendMode.DST_OUT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas) {
|
|
||||||
super.onDraw(canvas);
|
|
||||||
canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ShiftUpAnimation {
|
|
||||||
private final float mFromYDelta;
|
|
||||||
private final float mToYDelta;
|
|
||||||
private final View mOccludeHoleView;
|
|
||||||
private final SyncRtSurfaceTransactionApplier mApplier;
|
|
||||||
private final Matrix mTmpTransform = new Matrix();
|
|
||||||
|
|
||||||
ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView) {
|
|
||||||
mFromYDelta = fromYDelta;
|
|
||||||
mToYDelta = toYDelta;
|
|
||||||
mOccludeHoleView = occludeHoleView;
|
|
||||||
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onAnimationProgress(float linearProgress) {
|
|
||||||
if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
|
|
||||||
|| !mSplashScreenView.isAttachedToWindow()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
|
|
||||||
final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
|
|
||||||
|
|
||||||
mOccludeHoleView.setTranslationY(dy);
|
|
||||||
mTmpTransform.setTranslate(0 /* dx */, dy);
|
|
||||||
|
|
||||||
// set the vsyncId to ensure the transaction doesn't get applied too early.
|
|
||||||
final SurfaceControl.Transaction tx = mTransactionPool.acquire();
|
|
||||||
tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
|
|
||||||
mTmpTransform.postTranslate(mFirstWindowFrame.left,
|
|
||||||
mFirstWindowFrame.top + mMainWindowShiftLength);
|
|
||||||
|
|
||||||
SyncRtSurfaceTransactionApplier.SurfaceParams
|
|
||||||
params = new SyncRtSurfaceTransactionApplier.SurfaceParams
|
|
||||||
.Builder(mFirstWindowSurface)
|
|
||||||
.withMatrix(mTmpTransform)
|
|
||||||
.withMergeTransaction(tx)
|
|
||||||
.build();
|
|
||||||
mApplier.scheduleApply(params);
|
|
||||||
|
|
||||||
mTransactionPool.release(tx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void finish() {
|
|
||||||
if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final SurfaceControl.Transaction tx = mTransactionPool.acquire();
|
|
||||||
if (mSplashScreenView.isAttachedToWindow()) {
|
|
||||||
tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
|
|
||||||
|
|
||||||
SyncRtSurfaceTransactionApplier.SurfaceParams
|
|
||||||
params = new SyncRtSurfaceTransactionApplier.SurfaceParams
|
|
||||||
.Builder(mFirstWindowSurface)
|
|
||||||
.withWindowCrop(null)
|
|
||||||
.withMergeTransaction(tx)
|
|
||||||
.build();
|
|
||||||
mApplier.scheduleApply(params);
|
|
||||||
} else {
|
|
||||||
tx.setWindowCrop(mFirstWindowSurface, null);
|
|
||||||
tx.apply();
|
|
||||||
}
|
|
||||||
mTransactionPool.release(tx);
|
|
||||||
|
|
||||||
Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
|
|
||||||
mFirstWindowSurface::release, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
@@ -316,9 +112,6 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
|
|||||||
mFinishCallback = null;
|
mFinishCallback = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mShiftUpAnimation != null) {
|
|
||||||
mShiftUpAnimation.finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -342,40 +135,4 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
|
|||||||
public void onAnimationRepeat(Animator animation) {
|
public void onAnimationRepeat(Animator animation) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFadeOutProgress(float linearProgress) {
|
|
||||||
final float iconProgress = ICON_INTERPOLATOR.getInterpolation(
|
|
||||||
getProgress(linearProgress, 0 /* delay */, mIconFadeOutDuration));
|
|
||||||
final View iconView = mSplashScreenView.getIconView();
|
|
||||||
final View brandingView = mSplashScreenView.getBrandingView();
|
|
||||||
if (iconView != null) {
|
|
||||||
iconView.setAlpha(mIconStartAlpha * (1 - iconProgress));
|
|
||||||
}
|
|
||||||
if (brandingView != null) {
|
|
||||||
brandingView.setAlpha(mBrandingStartAlpha * (1 - iconProgress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onAnimationProgress(float linearProgress) {
|
|
||||||
onFadeOutProgress(linearProgress);
|
|
||||||
|
|
||||||
final float revealLinearProgress = getProgress(linearProgress, mAppRevealDelay,
|
|
||||||
mAppRevealDuration);
|
|
||||||
|
|
||||||
if (mRadialVanishAnimation != null) {
|
|
||||||
mRadialVanishAnimation.onAnimationProgress(revealLinearProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mShiftUpAnimation != null) {
|
|
||||||
mShiftUpAnimation.onAnimationProgress(revealLinearProgress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getProgress(float linearProgress, long delay, long duration) {
|
|
||||||
return MathUtils.constrain(
|
|
||||||
(linearProgress * (mAnimationDuration) - delay) / duration,
|
|
||||||
0.0f,
|
|
||||||
1.0f
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,358 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.wm.shell.startingsurface;
|
||||||
|
|
||||||
|
import static android.view.Choreographer.CALLBACK_COMMIT;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.graphics.BlendMode;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.RadialGradient;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
import android.util.MathUtils;
|
||||||
|
import android.util.Slog;
|
||||||
|
import android.view.Choreographer;
|
||||||
|
import android.view.SurfaceControl;
|
||||||
|
import android.view.SyncRtSurfaceTransactionApplier;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
import android.view.animation.PathInterpolator;
|
||||||
|
import android.window.SplashScreenView;
|
||||||
|
|
||||||
|
import com.android.wm.shell.animation.Interpolators;
|
||||||
|
import com.android.wm.shell.common.TransactionPool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for creating the splash screen window animations.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class SplashScreenExitAnimationUtils {
|
||||||
|
private static final boolean DEBUG_EXIT_ANIMATION = false;
|
||||||
|
private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
|
||||||
|
private static final String TAG = "SplashScreenExitAnimationUtils";
|
||||||
|
|
||||||
|
private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
|
||||||
|
private static final Interpolator MASK_RADIUS_INTERPOLATOR =
|
||||||
|
new PathInterpolator(0f, 0f, 0.4f, 1f);
|
||||||
|
private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and starts the animator to fade out the icon, reveal the app, and shift up main
|
||||||
|
* window.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static void startAnimations(ViewGroup splashScreenView,
|
||||||
|
SurfaceControl firstWindowSurface, int mainWindowShiftLength,
|
||||||
|
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
|
||||||
|
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
|
||||||
|
int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
|
||||||
|
ValueAnimator animator =
|
||||||
|
createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
|
||||||
|
transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
|
||||||
|
iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
|
||||||
|
animatorListener);
|
||||||
|
animator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the animator to fade out the icon, reveal the app, and shift up main window.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
private static ValueAnimator createAnimator(ViewGroup splashScreenView,
|
||||||
|
SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
|
||||||
|
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
|
||||||
|
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
|
||||||
|
int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
|
||||||
|
// reveal app
|
||||||
|
final float transparentRatio = 0.8f;
|
||||||
|
final int globalHeight = splashScreenView.getHeight();
|
||||||
|
final int verticalCircleCenter = 0;
|
||||||
|
final int finalVerticalLength = globalHeight - verticalCircleCenter;
|
||||||
|
final int halfWidth = splashScreenView.getWidth() / 2;
|
||||||
|
final int endRadius = (int) (0.5 + (1f / transparentRatio * (int)
|
||||||
|
Math.sqrt(finalVerticalLength * finalVerticalLength + halfWidth * halfWidth)));
|
||||||
|
final int[] colors = {Color.WHITE, Color.WHITE, Color.TRANSPARENT};
|
||||||
|
final float[] stops = {0f, transparentRatio, 1f};
|
||||||
|
|
||||||
|
RadialVanishAnimation radialVanishAnimation = new RadialVanishAnimation(splashScreenView);
|
||||||
|
radialVanishAnimation.setCircleCenter(halfWidth, verticalCircleCenter);
|
||||||
|
radialVanishAnimation.setRadius(0 /* initRadius */, endRadius);
|
||||||
|
radialVanishAnimation.setRadialPaintParam(colors, stops);
|
||||||
|
|
||||||
|
View occludeHoleView = null;
|
||||||
|
ShiftUpAnimation shiftUpAnimation = null;
|
||||||
|
if (firstWindowSurface != null && firstWindowSurface.isValid()) {
|
||||||
|
// shift up main window
|
||||||
|
occludeHoleView = new View(splashScreenView.getContext());
|
||||||
|
if (DEBUG_EXIT_ANIMATION_BLEND) {
|
||||||
|
occludeHoleView.setBackgroundColor(Color.BLUE);
|
||||||
|
} else if (splashScreenView instanceof SplashScreenView) {
|
||||||
|
occludeHoleView.setBackgroundColor(
|
||||||
|
((SplashScreenView) splashScreenView).getInitBackgroundColor());
|
||||||
|
} else {
|
||||||
|
occludeHoleView.setBackgroundColor(
|
||||||
|
isDarkTheme(splashScreenView.getContext()) ? Color.BLACK : Color.WHITE);
|
||||||
|
}
|
||||||
|
final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
|
||||||
|
WindowManager.LayoutParams.MATCH_PARENT, mMainWindowShiftLength);
|
||||||
|
splashScreenView.addView(occludeHoleView, params);
|
||||||
|
|
||||||
|
shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
|
||||||
|
firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
|
||||||
|
mMainWindowShiftLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
|
||||||
|
animator.setDuration(animationDuration);
|
||||||
|
animator.setInterpolator(Interpolators.LINEAR);
|
||||||
|
if (animatorListener != null) {
|
||||||
|
animator.addListener(animatorListener);
|
||||||
|
}
|
||||||
|
View finalOccludeHoleView = occludeHoleView;
|
||||||
|
ShiftUpAnimation finalShiftUpAnimation = shiftUpAnimation;
|
||||||
|
animator.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
super.onAnimationEnd(animation);
|
||||||
|
if (finalShiftUpAnimation != null) {
|
||||||
|
finalShiftUpAnimation.finish();
|
||||||
|
}
|
||||||
|
splashScreenView.removeView(radialVanishAnimation);
|
||||||
|
splashScreenView.removeView(finalOccludeHoleView);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
animator.addUpdateListener(animation -> {
|
||||||
|
float linearProgress = (float) animation.getAnimatedValue();
|
||||||
|
|
||||||
|
// Fade out progress
|
||||||
|
final float iconProgress =
|
||||||
|
ICON_INTERPOLATOR.getInterpolation(getProgress(
|
||||||
|
linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
|
||||||
|
View iconView = null;
|
||||||
|
View brandingView = null;
|
||||||
|
if (splashScreenView instanceof SplashScreenView) {
|
||||||
|
iconView = ((SplashScreenView) splashScreenView).getIconView();
|
||||||
|
brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
|
||||||
|
}
|
||||||
|
if (iconView != null) {
|
||||||
|
iconView.setAlpha(iconStartAlpha * (1 - iconProgress));
|
||||||
|
}
|
||||||
|
if (brandingView != null) {
|
||||||
|
brandingView.setAlpha(brandingStartAlpha * (1 - iconProgress));
|
||||||
|
}
|
||||||
|
|
||||||
|
final float revealLinearProgress = getProgress(linearProgress, appRevealDelay,
|
||||||
|
appRevealDuration, animationDuration);
|
||||||
|
|
||||||
|
radialVanishAnimation.onAnimationProgress(revealLinearProgress);
|
||||||
|
|
||||||
|
if (finalShiftUpAnimation != null) {
|
||||||
|
finalShiftUpAnimation.onAnimationProgress(revealLinearProgress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return animator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float getProgress(float linearProgress, long delay, long duration,
|
||||||
|
int animationDuration) {
|
||||||
|
return MathUtils.constrain(
|
||||||
|
(linearProgress * (animationDuration) - delay) / duration,
|
||||||
|
0.0f,
|
||||||
|
1.0f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDarkTheme(Context context) {
|
||||||
|
Configuration configuration = context.getResources().getConfiguration();
|
||||||
|
int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||||
|
return nightMode == Configuration.UI_MODE_NIGHT_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View which creates a circular reveal of the underlying view.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@SuppressLint("ViewConstructor")
|
||||||
|
public static class RadialVanishAnimation extends View {
|
||||||
|
private final ViewGroup mView;
|
||||||
|
private int mInitRadius;
|
||||||
|
private int mFinishRadius;
|
||||||
|
|
||||||
|
private final Point mCircleCenter = new Point();
|
||||||
|
private final Matrix mVanishMatrix = new Matrix();
|
||||||
|
private final Paint mVanishPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
|
||||||
|
public RadialVanishAnimation(ViewGroup target) {
|
||||||
|
super(target.getContext());
|
||||||
|
mView = target;
|
||||||
|
mView.addView(this);
|
||||||
|
if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
|
||||||
|
((ViewGroup.MarginLayoutParams) getLayoutParams()).setMargins(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
mVanishPaint.setAlpha(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAnimationProgress(float linearProgress) {
|
||||||
|
if (mVanishPaint.getShader() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final float radiusProgress = MASK_RADIUS_INTERPOLATOR.getInterpolation(linearProgress);
|
||||||
|
final float alphaProgress = Interpolators.ALPHA_OUT.getInterpolation(linearProgress);
|
||||||
|
final float scale = mInitRadius + (mFinishRadius - mInitRadius) * radiusProgress;
|
||||||
|
|
||||||
|
mVanishMatrix.setScale(scale, scale);
|
||||||
|
mVanishMatrix.postTranslate(mCircleCenter.x, mCircleCenter.y);
|
||||||
|
mVanishPaint.getShader().setLocalMatrix(mVanishMatrix);
|
||||||
|
mVanishPaint.setAlpha(Math.round(0xFF * alphaProgress));
|
||||||
|
|
||||||
|
postInvalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRadius(int initRadius, int finishRadius) {
|
||||||
|
if (DEBUG_EXIT_ANIMATION) {
|
||||||
|
Slog.v(TAG, "RadialVanishAnimation setRadius init: " + initRadius
|
||||||
|
+ " final " + finishRadius);
|
||||||
|
}
|
||||||
|
mInitRadius = initRadius;
|
||||||
|
mFinishRadius = finishRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCircleCenter(int x, int y) {
|
||||||
|
if (DEBUG_EXIT_ANIMATION) {
|
||||||
|
Slog.v(TAG, "RadialVanishAnimation setCircleCenter x: " + x + " y " + y);
|
||||||
|
}
|
||||||
|
mCircleCenter.set(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRadialPaintParam(int[] colors, float[] stops) {
|
||||||
|
// setup gradient shader
|
||||||
|
final RadialGradient rShader =
|
||||||
|
new RadialGradient(0, 0, 1, colors, stops, Shader.TileMode.CLAMP);
|
||||||
|
mVanishPaint.setShader(rShader);
|
||||||
|
if (!DEBUG_EXIT_ANIMATION_BLEND) {
|
||||||
|
// We blend the reveal gradient with the splash screen using DST_OUT so that the
|
||||||
|
// splash screen is fully visible when radius = 0 (or gradient opacity is 0) and
|
||||||
|
// fully invisible when radius = finishRadius AND gradient opacity is 1.
|
||||||
|
mVanishPaint.setBlendMode(BlendMode.DST_OUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
canvas.drawRect(0, 0, mView.getWidth(), mView.getHeight(), mVanishPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shifts up the main window.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final class ShiftUpAnimation {
|
||||||
|
private final float mFromYDelta;
|
||||||
|
private final float mToYDelta;
|
||||||
|
private final View mOccludeHoleView;
|
||||||
|
private final SyncRtSurfaceTransactionApplier mApplier;
|
||||||
|
private final Matrix mTmpTransform = new Matrix();
|
||||||
|
private final SurfaceControl mFirstWindowSurface;
|
||||||
|
private final ViewGroup mSplashScreenView;
|
||||||
|
private final TransactionPool mTransactionPool;
|
||||||
|
private final Rect mFirstWindowFrame;
|
||||||
|
private final int mMainWindowShiftLength;
|
||||||
|
|
||||||
|
public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
|
||||||
|
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
|
||||||
|
TransactionPool transactionPool, Rect firstWindowFrame,
|
||||||
|
int mainWindowShiftLength) {
|
||||||
|
mFromYDelta = fromYDelta;
|
||||||
|
mToYDelta = toYDelta;
|
||||||
|
mOccludeHoleView = occludeHoleView;
|
||||||
|
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
|
||||||
|
mFirstWindowSurface = firstWindowSurface;
|
||||||
|
mSplashScreenView = splashScreenView;
|
||||||
|
mTransactionPool = transactionPool;
|
||||||
|
mFirstWindowFrame = firstWindowFrame;
|
||||||
|
mMainWindowShiftLength = mainWindowShiftLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAnimationProgress(float linearProgress) {
|
||||||
|
if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()
|
||||||
|
|| !mSplashScreenView.isAttachedToWindow()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final float progress = SHIFT_UP_INTERPOLATOR.getInterpolation(linearProgress);
|
||||||
|
final float dy = mFromYDelta + (mToYDelta - mFromYDelta) * progress;
|
||||||
|
|
||||||
|
mOccludeHoleView.setTranslationY(dy);
|
||||||
|
mTmpTransform.setTranslate(0 /* dx */, dy);
|
||||||
|
|
||||||
|
// set the vsyncId to ensure the transaction doesn't get applied too early.
|
||||||
|
final SurfaceControl.Transaction tx = mTransactionPool.acquire();
|
||||||
|
tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
|
||||||
|
mTmpTransform.postTranslate(mFirstWindowFrame.left,
|
||||||
|
mFirstWindowFrame.top + mMainWindowShiftLength);
|
||||||
|
|
||||||
|
SyncRtSurfaceTransactionApplier.SurfaceParams
|
||||||
|
params = new SyncRtSurfaceTransactionApplier.SurfaceParams
|
||||||
|
.Builder(mFirstWindowSurface)
|
||||||
|
.withMatrix(mTmpTransform)
|
||||||
|
.withMergeTransaction(tx)
|
||||||
|
.build();
|
||||||
|
mApplier.scheduleApply(params);
|
||||||
|
|
||||||
|
mTransactionPool.release(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish() {
|
||||||
|
if (mFirstWindowSurface == null || !mFirstWindowSurface.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final SurfaceControl.Transaction tx = mTransactionPool.acquire();
|
||||||
|
if (mSplashScreenView.isAttachedToWindow()) {
|
||||||
|
tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
|
||||||
|
|
||||||
|
SyncRtSurfaceTransactionApplier.SurfaceParams
|
||||||
|
params = new SyncRtSurfaceTransactionApplier.SurfaceParams
|
||||||
|
.Builder(mFirstWindowSurface)
|
||||||
|
.withWindowCrop(null)
|
||||||
|
.withMergeTransaction(tx)
|
||||||
|
.build();
|
||||||
|
mApplier.scheduleApply(params);
|
||||||
|
} else {
|
||||||
|
tx.setWindowCrop(mFirstWindowSurface, null);
|
||||||
|
tx.apply();
|
||||||
|
}
|
||||||
|
mTransactionPool.release(tx);
|
||||||
|
|
||||||
|
Choreographer.getSfInstance().postCallback(CALLBACK_COMMIT,
|
||||||
|
mFirstWindowSurface::release, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user