Format Kotlin code in SysUI Animation lib using ktfmt
Test: Manual Bug: 235461679 Change-Id: Id14bee4bdecc02f3ed6cc4788282a4903d4b7149
This commit is contained in:
@@ -59,31 +59,31 @@ class ActivityLaunchAnimator(
|
||||
companion object {
|
||||
/** The timings when animating a View into an app. */
|
||||
@JvmField
|
||||
val TIMINGS = LaunchAnimator.Timings(
|
||||
totalDuration = 500L,
|
||||
contentBeforeFadeOutDelay = 0L,
|
||||
contentBeforeFadeOutDuration = 150L,
|
||||
contentAfterFadeInDelay = 150L,
|
||||
contentAfterFadeInDuration = 183L
|
||||
)
|
||||
val TIMINGS =
|
||||
LaunchAnimator.Timings(
|
||||
totalDuration = 500L,
|
||||
contentBeforeFadeOutDelay = 0L,
|
||||
contentBeforeFadeOutDuration = 150L,
|
||||
contentAfterFadeInDelay = 150L,
|
||||
contentAfterFadeInDuration = 183L
|
||||
)
|
||||
|
||||
/**
|
||||
* The timings when animating a Dialog into an app. We need to wait at least 200ms before
|
||||
* showing the app (which is under the dialog window) so that the dialog window dim is fully
|
||||
* faded out, to avoid flicker.
|
||||
*/
|
||||
val DIALOG_TIMINGS = TIMINGS.copy(
|
||||
contentBeforeFadeOutDuration = 200L,
|
||||
contentAfterFadeInDelay = 200L
|
||||
)
|
||||
val DIALOG_TIMINGS =
|
||||
TIMINGS.copy(contentBeforeFadeOutDuration = 200L, contentAfterFadeInDelay = 200L)
|
||||
|
||||
/** The interpolators when animating a View or a dialog into an app. */
|
||||
val INTERPOLATORS = LaunchAnimator.Interpolators(
|
||||
positionInterpolator = Interpolators.EMPHASIZED,
|
||||
positionXInterpolator = createPositionXInterpolator(),
|
||||
contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
|
||||
contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
|
||||
)
|
||||
val INTERPOLATORS =
|
||||
LaunchAnimator.Interpolators(
|
||||
positionInterpolator = Interpolators.EMPHASIZED,
|
||||
positionXInterpolator = createPositionXInterpolator(),
|
||||
contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
|
||||
contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
|
||||
)
|
||||
|
||||
/** Durations & interpolators for the navigation bar fading in & out. */
|
||||
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
|
||||
@@ -98,11 +98,12 @@ class ActivityLaunchAnimator(
|
||||
private const val LAUNCH_TIMEOUT = 1000L
|
||||
|
||||
private fun createPositionXInterpolator(): Interpolator {
|
||||
val path = Path().apply {
|
||||
moveTo(0f, 0f)
|
||||
cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f)
|
||||
cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f)
|
||||
}
|
||||
val path =
|
||||
Path().apply {
|
||||
moveTo(0f, 0f)
|
||||
cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f)
|
||||
cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f)
|
||||
}
|
||||
return PathInterpolator(path)
|
||||
}
|
||||
}
|
||||
@@ -150,29 +151,37 @@ class ActivityLaunchAnimator(
|
||||
return
|
||||
}
|
||||
|
||||
val callback = this.callback ?: throw IllegalStateException(
|
||||
"ActivityLaunchAnimator.callback must be set before using this animator")
|
||||
val callback =
|
||||
this.callback
|
||||
?: throw IllegalStateException(
|
||||
"ActivityLaunchAnimator.callback must be set before using this animator"
|
||||
)
|
||||
val runner = Runner(controller)
|
||||
val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
|
||||
|
||||
// Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the
|
||||
// keyguard with the animation
|
||||
val animationAdapter = if (!hideKeyguardWithAnimation) {
|
||||
RemoteAnimationAdapter(
|
||||
runner,
|
||||
TIMINGS.totalDuration,
|
||||
TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val animationAdapter =
|
||||
if (!hideKeyguardWithAnimation) {
|
||||
RemoteAnimationAdapter(
|
||||
runner,
|
||||
TIMINGS.totalDuration,
|
||||
TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Register the remote animation for the given package to also animate trampoline
|
||||
// activity launches.
|
||||
if (packageName != null && animationAdapter != null) {
|
||||
try {
|
||||
ActivityTaskManager.getService().registerRemoteAnimationForNextActivityStart(
|
||||
packageName, animationAdapter, null /* launchCookie */)
|
||||
ActivityTaskManager.getService()
|
||||
.registerRemoteAnimationForNextActivityStart(
|
||||
packageName,
|
||||
animationAdapter,
|
||||
null /* launchCookie */
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
Log.w(TAG, "Unable to register the remote animation", e)
|
||||
}
|
||||
@@ -184,12 +193,15 @@ class ActivityLaunchAnimator(
|
||||
// keyguard.
|
||||
val willAnimate =
|
||||
launchResult == ActivityManager.START_TASK_TO_FRONT ||
|
||||
launchResult == ActivityManager.START_SUCCESS ||
|
||||
(launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
|
||||
hideKeyguardWithAnimation)
|
||||
launchResult == ActivityManager.START_SUCCESS ||
|
||||
(launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
|
||||
hideKeyguardWithAnimation)
|
||||
|
||||
Log.i(TAG, "launchResult=$launchResult willAnimate=$willAnimate " +
|
||||
"hideKeyguardWithAnimation=$hideKeyguardWithAnimation")
|
||||
Log.i(
|
||||
TAG,
|
||||
"launchResult=$launchResult willAnimate=$willAnimate " +
|
||||
"hideKeyguardWithAnimation=$hideKeyguardWithAnimation"
|
||||
)
|
||||
controller.callOnIntentStartedOnMainThread(willAnimate)
|
||||
|
||||
// If we expect an animation, post a timeout to cancel it in case the remote animation is
|
||||
@@ -206,9 +218,7 @@ class ActivityLaunchAnimator(
|
||||
|
||||
private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
this.launchContainer.context.mainExecutor.execute {
|
||||
this.onIntentStarted(willAnimate)
|
||||
}
|
||||
this.launchContainer.context.mainExecutor.execute { this.onIntentStarted(willAnimate) }
|
||||
} else {
|
||||
this.onIntentStarted(willAnimate)
|
||||
}
|
||||
@@ -246,8 +256,7 @@ class ActivityLaunchAnimator(
|
||||
}
|
||||
|
||||
/** Create a new animation [Runner] controlled by [controller]. */
|
||||
@VisibleForTesting
|
||||
fun createRunner(controller: Controller): Runner = Runner(controller)
|
||||
@VisibleForTesting fun createRunner(controller: Controller): Runner = Runner(controller)
|
||||
|
||||
interface PendingIntentStarter {
|
||||
/**
|
||||
@@ -271,19 +280,16 @@ class ActivityLaunchAnimator(
|
||||
|
||||
interface Listener {
|
||||
/** Called when an activity launch animation started. */
|
||||
@JvmDefault
|
||||
fun onLaunchAnimationStart() {}
|
||||
@JvmDefault fun onLaunchAnimationStart() {}
|
||||
|
||||
/**
|
||||
* Called when an activity launch animation is finished. This will be called if and only if
|
||||
* [onLaunchAnimationStart] was called earlier.
|
||||
*/
|
||||
@JvmDefault
|
||||
fun onLaunchAnimationEnd() {}
|
||||
@JvmDefault fun onLaunchAnimationEnd() {}
|
||||
|
||||
/** Called when an activity launch animation made progress. */
|
||||
@JvmDefault
|
||||
fun onLaunchAnimationProgress(linearProgress: Float) {}
|
||||
@JvmDefault fun onLaunchAnimationProgress(linearProgress: Float) {}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,9 +400,7 @@ class ActivityLaunchAnimator(
|
||||
return
|
||||
}
|
||||
|
||||
context.mainExecutor.execute {
|
||||
startAnimation(apps, nonApps, iCallback)
|
||||
}
|
||||
context.mainExecutor.execute { startAnimation(apps, nonApps, iCallback) }
|
||||
}
|
||||
|
||||
private fun startAnimation(
|
||||
@@ -408,9 +412,7 @@ class ActivityLaunchAnimator(
|
||||
Log.d(TAG, "Remote animation started")
|
||||
}
|
||||
|
||||
val window = apps?.firstOrNull {
|
||||
it.mode == RemoteAnimationTarget.MODE_OPENING
|
||||
}
|
||||
val window = apps?.firstOrNull { it.mode == RemoteAnimationTarget.MODE_OPENING }
|
||||
|
||||
if (window == null) {
|
||||
Log.i(TAG, "Aborting the animation as no window is opening")
|
||||
@@ -420,77 +422,88 @@ class ActivityLaunchAnimator(
|
||||
return
|
||||
}
|
||||
|
||||
val navigationBar = nonApps?.firstOrNull {
|
||||
it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
|
||||
}
|
||||
val navigationBar =
|
||||
nonApps?.firstOrNull {
|
||||
it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
|
||||
}
|
||||
|
||||
val windowBounds = window.screenSpaceBounds
|
||||
val endState = LaunchAnimator.State(
|
||||
top = windowBounds.top,
|
||||
bottom = windowBounds.bottom,
|
||||
left = windowBounds.left,
|
||||
right = windowBounds.right
|
||||
)
|
||||
val endState =
|
||||
LaunchAnimator.State(
|
||||
top = windowBounds.top,
|
||||
bottom = windowBounds.bottom,
|
||||
left = windowBounds.left,
|
||||
right = windowBounds.right
|
||||
)
|
||||
val callback = this@ActivityLaunchAnimator.callback!!
|
||||
val windowBackgroundColor = window.taskInfo?.let { callback.getBackgroundColor(it) }
|
||||
?: window.backgroundColor
|
||||
val windowBackgroundColor =
|
||||
window.taskInfo?.let { callback.getBackgroundColor(it) } ?: window.backgroundColor
|
||||
|
||||
// Make sure we use the modified timings when animating a dialog into an app.
|
||||
val launchAnimator = if (controller.isDialogLaunch) {
|
||||
dialogToAppAnimator
|
||||
} else {
|
||||
launchAnimator
|
||||
}
|
||||
val launchAnimator =
|
||||
if (controller.isDialogLaunch) {
|
||||
dialogToAppAnimator
|
||||
} else {
|
||||
launchAnimator
|
||||
}
|
||||
|
||||
// TODO(b/184121838): We should somehow get the top and bottom radius of the window
|
||||
// instead of recomputing isExpandingFullyAbove here.
|
||||
val isExpandingFullyAbove =
|
||||
launchAnimator.isExpandingFullyAbove(controller.launchContainer, endState)
|
||||
val endRadius = if (isExpandingFullyAbove) {
|
||||
// Most of the time, expanding fully above the root view means expanding in full
|
||||
// screen.
|
||||
ScreenDecorationsUtils.getWindowCornerRadius(context)
|
||||
} else {
|
||||
// This usually means we are in split screen mode, so 2 out of 4 corners will have
|
||||
// a radius of 0.
|
||||
0f
|
||||
}
|
||||
val endRadius =
|
||||
if (isExpandingFullyAbove) {
|
||||
// Most of the time, expanding fully above the root view means expanding in full
|
||||
// screen.
|
||||
ScreenDecorationsUtils.getWindowCornerRadius(context)
|
||||
} else {
|
||||
// This usually means we are in split screen mode, so 2 out of 4 corners will
|
||||
// have
|
||||
// a radius of 0.
|
||||
0f
|
||||
}
|
||||
endState.topCornerRadius = endRadius
|
||||
endState.bottomCornerRadius = endRadius
|
||||
|
||||
// We animate the opening window and delegate the view expansion to [this.controller].
|
||||
val delegate = this.controller
|
||||
val controller = object : LaunchAnimator.Controller by delegate {
|
||||
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
|
||||
listeners.forEach { it.onLaunchAnimationStart() }
|
||||
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
|
||||
listeners.forEach { it.onLaunchAnimationEnd() }
|
||||
iCallback?.invoke()
|
||||
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationProgress(
|
||||
state: LaunchAnimator.State,
|
||||
progress: Float,
|
||||
linearProgress: Float
|
||||
) {
|
||||
// Apply the state to the window only if it is visible, i.e. when the expanding
|
||||
// view is *not* visible.
|
||||
if (!state.visible) {
|
||||
applyStateToWindow(window, state)
|
||||
val controller =
|
||||
object : LaunchAnimator.Controller by delegate {
|
||||
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
|
||||
listeners.forEach { it.onLaunchAnimationStart() }
|
||||
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||
}
|
||||
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
|
||||
|
||||
listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
|
||||
delegate.onLaunchAnimationProgress(state, progress, linearProgress)
|
||||
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
|
||||
listeners.forEach { it.onLaunchAnimationEnd() }
|
||||
iCallback?.invoke()
|
||||
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationProgress(
|
||||
state: LaunchAnimator.State,
|
||||
progress: Float,
|
||||
linearProgress: Float
|
||||
) {
|
||||
// Apply the state to the window only if it is visible, i.e. when the
|
||||
// expanding view is *not* visible.
|
||||
if (!state.visible) {
|
||||
applyStateToWindow(window, state)
|
||||
}
|
||||
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
|
||||
|
||||
listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
|
||||
delegate.onLaunchAnimationProgress(state, progress, linearProgress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animation = launchAnimator.startAnimation(
|
||||
controller, endState, windowBackgroundColor, drawHole = true)
|
||||
animation =
|
||||
launchAnimator.startAnimation(
|
||||
controller,
|
||||
endState,
|
||||
windowBackgroundColor,
|
||||
drawHole = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun applyStateToWindow(window: RemoteAnimationTarget, state: LaunchAnimator.State) {
|
||||
@@ -541,13 +554,14 @@ class ActivityLaunchAnimator(
|
||||
// especially important for lock screen animations, where the window is not clipped by
|
||||
// the shade.
|
||||
val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale
|
||||
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
|
||||
.withAlpha(1f)
|
||||
.withMatrix(matrix)
|
||||
.withWindowCrop(windowCrop)
|
||||
.withCornerRadius(cornerRadius)
|
||||
.withVisibility(true)
|
||||
.build()
|
||||
val params =
|
||||
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
|
||||
.withAlpha(1f)
|
||||
.withMatrix(matrix)
|
||||
.withWindowCrop(windowCrop)
|
||||
.withCornerRadius(cornerRadius)
|
||||
.withVisibility(true)
|
||||
.build()
|
||||
|
||||
transactionApplier.scheduleApply(params)
|
||||
}
|
||||
@@ -563,14 +577,21 @@ class ActivityLaunchAnimator(
|
||||
return
|
||||
}
|
||||
|
||||
val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress,
|
||||
ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
|
||||
val fadeInProgress =
|
||||
LaunchAnimator.getProgress(
|
||||
TIMINGS,
|
||||
linearProgress,
|
||||
ANIMATION_DELAY_NAV_FADE_IN,
|
||||
ANIMATION_DURATION_NAV_FADE_OUT
|
||||
)
|
||||
|
||||
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
|
||||
if (fadeInProgress > 0) {
|
||||
matrix.reset()
|
||||
matrix.setTranslate(
|
||||
0f, (state.top - navigationBar.sourceContainerBounds.top).toFloat())
|
||||
0f,
|
||||
(state.top - navigationBar.sourceContainerBounds.top).toFloat()
|
||||
)
|
||||
windowCrop.set(state.left, 0, state.right, state.height)
|
||||
params
|
||||
.withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
|
||||
@@ -578,8 +599,13 @@ class ActivityLaunchAnimator(
|
||||
.withWindowCrop(windowCrop)
|
||||
.withVisibility(true)
|
||||
} else {
|
||||
val fadeOutProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, 0,
|
||||
ANIMATION_DURATION_NAV_FADE_OUT)
|
||||
val fadeOutProgress =
|
||||
LaunchAnimator.getProgress(
|
||||
TIMINGS,
|
||||
linearProgress,
|
||||
0,
|
||||
ANIMATION_DURATION_NAV_FADE_OUT
|
||||
)
|
||||
params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
|
||||
}
|
||||
|
||||
|
||||
@@ -23,4 +23,4 @@ package com.android.systemui.animation
|
||||
*/
|
||||
open class DelegateLaunchAnimatorController(
|
||||
protected val delegate: ActivityLaunchAnimator.Controller
|
||||
) : ActivityLaunchAnimator.Controller by delegate
|
||||
) : ActivityLaunchAnimator.Controller by delegate
|
||||
|
||||
@@ -51,7 +51,9 @@ private const val TAG = "DialogLaunchAnimator"
|
||||
* @see showFromDialog
|
||||
* @see createActivityLaunchController
|
||||
*/
|
||||
class DialogLaunchAnimator @JvmOverloads constructor(
|
||||
class DialogLaunchAnimator
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
private val dreamManager: IDreamManager,
|
||||
private val interactionJankMonitor: InteractionJankMonitor,
|
||||
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
|
||||
@@ -62,9 +64,10 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
||||
|
||||
// We use the same interpolator for X and Y axis to make sure the dialog does not move out
|
||||
// of the screen bounds during the animation.
|
||||
private val INTERPOLATORS = ActivityLaunchAnimator.INTERPOLATORS.copy(
|
||||
positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
|
||||
)
|
||||
private val INTERPOLATORS =
|
||||
ActivityLaunchAnimator.INTERPOLATORS.copy(
|
||||
positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
|
||||
)
|
||||
|
||||
private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.tag_launch_animation_running
|
||||
}
|
||||
@@ -105,8 +108,10 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
||||
|
||||
// If the view we are launching from belongs to another dialog, then this means the caller
|
||||
// intent is to launch a dialog from another dialog.
|
||||
val animatedParent = openedDialogs
|
||||
.firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl }
|
||||
val animatedParent =
|
||||
openedDialogs.firstOrNull {
|
||||
it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
|
||||
}
|
||||
val animateFrom = animatedParent?.dialogContentWithBackground ?: view
|
||||
|
||||
// Make sure we don't run the launch animation from the same view twice at the same time.
|
||||
@@ -118,18 +123,19 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
||||
|
||||
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
|
||||
|
||||
val animatedDialog = AnimatedDialog(
|
||||
launchAnimator,
|
||||
dreamManager,
|
||||
interactionJankMonitor,
|
||||
animateFrom,
|
||||
onDialogDismissed = { openedDialogs.remove(it) },
|
||||
dialog = dialog,
|
||||
animateBackgroundBoundsChange,
|
||||
animatedParent,
|
||||
isForTesting,
|
||||
cuj
|
||||
)
|
||||
val animatedDialog =
|
||||
AnimatedDialog(
|
||||
launchAnimator,
|
||||
dreamManager,
|
||||
interactionJankMonitor,
|
||||
animateFrom,
|
||||
onDialogDismissed = { openedDialogs.remove(it) },
|
||||
dialog = dialog,
|
||||
animateBackgroundBoundsChange,
|
||||
animatedParent,
|
||||
isForTesting,
|
||||
cuj
|
||||
)
|
||||
|
||||
openedDialogs.add(animatedDialog)
|
||||
animatedDialog.start()
|
||||
@@ -146,13 +152,12 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
||||
animateFrom: Dialog,
|
||||
animateBackgroundBoundsChange: Boolean = false
|
||||
) {
|
||||
val view = openedDialogs
|
||||
.firstOrNull { it.dialog == animateFrom }
|
||||
?.dialogContentWithBackground
|
||||
?: throw IllegalStateException(
|
||||
"The animateFrom dialog was not animated using " +
|
||||
"DialogLaunchAnimator.showFrom(View|Dialog)"
|
||||
)
|
||||
val view =
|
||||
openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground
|
||||
?: throw IllegalStateException(
|
||||
"The animateFrom dialog was not animated using " +
|
||||
"DialogLaunchAnimator.showFrom(View|Dialog)"
|
||||
)
|
||||
showFromView(dialog, view, animateBackgroundBoundsChange = animateBackgroundBoundsChange)
|
||||
}
|
||||
|
||||
@@ -175,9 +180,11 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
||||
view: View,
|
||||
cujType: Int? = null
|
||||
): ActivityLaunchAnimator.Controller? {
|
||||
val animatedDialog = openedDialogs
|
||||
.firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl }
|
||||
?: return null
|
||||
val animatedDialog =
|
||||
openedDialogs.firstOrNull {
|
||||
it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
|
||||
}
|
||||
?: return null
|
||||
|
||||
// At this point, we know that the intent of the caller is to dismiss the dialog to show
|
||||
// an app, so we disable the exit animation into the touch surface because we will never
|
||||
@@ -240,7 +247,7 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun disableDialogDismiss() {
|
||||
dialog.setDismissOverride { /* Do nothing */ }
|
||||
dialog.setDismissOverride { /* Do nothing */}
|
||||
}
|
||||
|
||||
private fun enableDialogDismiss() {
|
||||
@@ -257,9 +264,9 @@ class DialogLaunchAnimator @JvmOverloads constructor(
|
||||
* Ensure that all dialogs currently shown won't animate into their touch surface when
|
||||
* dismissed.
|
||||
*
|
||||
* This is a temporary API meant to be called right before we both dismiss a dialog and start
|
||||
* an activity, which currently does not look good if we animate the dialog into the touch
|
||||
* surface at the same time as the activity starts.
|
||||
* This is a temporary API meant to be called right before we both dismiss a dialog and start an
|
||||
* activity, which currently does not look good if we animate the dialog into the touch surface
|
||||
* at the same time as the activity starts.
|
||||
*
|
||||
* TODO(b/193634619): Remove this function and animate dialog into opening activity instead.
|
||||
*/
|
||||
@@ -295,8 +302,8 @@ private class AnimatedDialog(
|
||||
var touchSurface: View,
|
||||
|
||||
/**
|
||||
* A callback that will be called with this [AnimatedDialog] after the dialog was
|
||||
* dismissed and the exit animation is done.
|
||||
* A callback that will be called with this [AnimatedDialog] after the dialog was dismissed and
|
||||
* the exit animation is done.
|
||||
*/
|
||||
private val onDialogDismissed: (AnimatedDialog) -> Unit,
|
||||
|
||||
@@ -333,9 +340,7 @@ private class AnimatedDialog(
|
||||
*/
|
||||
var dialogContentWithBackground: ViewGroup? = null
|
||||
|
||||
/**
|
||||
* The background color of [dialog], taking into consideration its window background color.
|
||||
*/
|
||||
/** The background color of [dialog], taking into consideration its window background color. */
|
||||
private var originalDialogBackgroundColor = Color.BLACK
|
||||
|
||||
/**
|
||||
@@ -353,11 +358,12 @@ private class AnimatedDialog(
|
||||
private var isOriginalDialogViewLaidOut = false
|
||||
|
||||
/** A layout listener to animate the dialog height change. */
|
||||
private val backgroundLayoutListener = if (animateBackgroundBoundsChange) {
|
||||
AnimatedBoundsLayoutListener()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
private val backgroundLayoutListener =
|
||||
if (animateBackgroundBoundsChange) {
|
||||
AnimatedBoundsLayoutListener()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
/*
|
||||
* A layout listener in case the dialog (window) size changes (for instance because of a
|
||||
@@ -381,100 +387,117 @@ private class AnimatedDialog(
|
||||
val window = dialog.window!!
|
||||
val isWindowFullScreen =
|
||||
window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT
|
||||
val dialogContentWithBackground = if (isWindowFullScreen) {
|
||||
// If the dialog window is already fullscreen, then we look for the first ViewGroup that
|
||||
// has a background (and is not the DecorView, which always has a background) and
|
||||
// animate towards that ViewGroup given that this is probably what represents the actual
|
||||
// dialog view.
|
||||
var viewGroupWithBackground: ViewGroup? = null
|
||||
for (i in 0 until decorView.childCount) {
|
||||
viewGroupWithBackground = findFirstViewGroupWithBackground(decorView.getChildAt(i))
|
||||
if (viewGroupWithBackground != null) {
|
||||
break
|
||||
val dialogContentWithBackground =
|
||||
if (isWindowFullScreen) {
|
||||
// If the dialog window is already fullscreen, then we look for the first ViewGroup
|
||||
// that has a background (and is not the DecorView, which always has a background)
|
||||
// and animate towards that ViewGroup given that this is probably what represents
|
||||
// the actual dialog view.
|
||||
var viewGroupWithBackground: ViewGroup? = null
|
||||
for (i in 0 until decorView.childCount) {
|
||||
viewGroupWithBackground =
|
||||
findFirstViewGroupWithBackground(decorView.getChildAt(i))
|
||||
if (viewGroupWithBackground != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animate that view with the background. Throw if we didn't find one, because otherwise
|
||||
// it's not clear what we should animate.
|
||||
viewGroupWithBackground
|
||||
?: throw IllegalStateException("Unable to find ViewGroup with background")
|
||||
} else {
|
||||
// We will make the dialog window (and therefore its DecorView) fullscreen to make it
|
||||
// possible to animate outside its bounds.
|
||||
//
|
||||
// Before that, we add a new View as a child of the DecorView with the same size and
|
||||
// gravity as that DecorView, then we add all original children of the DecorView to that
|
||||
// new View. Finally we remove the background of the DecorView and add it to the new
|
||||
// View, then we make the DecorView fullscreen. This new View now acts as a fake (non
|
||||
// fullscreen) window.
|
||||
//
|
||||
// On top of that, we also add a fullscreen transparent background between the DecorView
|
||||
// and the view that we added so that we can dismiss the dialog when this view is
|
||||
// clicked. This is necessary because DecorView overrides onTouchEvent and therefore we
|
||||
// can't set the click listener directly on the (now fullscreen) DecorView.
|
||||
val fullscreenTransparentBackground = FrameLayout(dialog.context)
|
||||
decorView.addView(
|
||||
fullscreenTransparentBackground,
|
||||
0 /* index */,
|
||||
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
)
|
||||
|
||||
val dialogContentWithBackground = FrameLayout(dialog.context)
|
||||
dialogContentWithBackground.background = decorView.background
|
||||
|
||||
// Make the window background transparent. Note that setting the window (or DecorView)
|
||||
// background drawable to null leads to issues with background color (not being
|
||||
// transparent) or with insets that are not refreshed. Therefore we need to set it to
|
||||
// something not null, hence we are using android.R.color.transparent here.
|
||||
window.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
|
||||
// Close the dialog when clicking outside of it.
|
||||
fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
|
||||
dialogContentWithBackground.isClickable = true
|
||||
|
||||
// Make sure the transparent and dialog backgrounds are not focusable by accessibility
|
||||
// features.
|
||||
fullscreenTransparentBackground.importantForAccessibility =
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
dialogContentWithBackground.importantForAccessibility =
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
|
||||
fullscreenTransparentBackground.addView(
|
||||
dialogContentWithBackground,
|
||||
FrameLayout.LayoutParams(
|
||||
window.attributes.width,
|
||||
window.attributes.height,
|
||||
window.attributes.gravity
|
||||
// Animate that view with the background. Throw if we didn't find one, because
|
||||
// otherwise
|
||||
// it's not clear what we should animate.
|
||||
viewGroupWithBackground
|
||||
?: throw IllegalStateException("Unable to find ViewGroup with background")
|
||||
} else {
|
||||
// We will make the dialog window (and therefore its DecorView) fullscreen to make
|
||||
// it possible to animate outside its bounds.
|
||||
//
|
||||
// Before that, we add a new View as a child of the DecorView with the same size and
|
||||
// gravity as that DecorView, then we add all original children of the DecorView to
|
||||
// that new View. Finally we remove the background of the DecorView and add it to
|
||||
// the new View, then we make the DecorView fullscreen. This new View now acts as a
|
||||
// fake (non fullscreen) window.
|
||||
//
|
||||
// On top of that, we also add a fullscreen transparent background between the
|
||||
// DecorView and the view that we added so that we can dismiss the dialog when this
|
||||
// view is clicked. This is necessary because DecorView overrides onTouchEvent and
|
||||
// therefore we can't set the click listener directly on the (now fullscreen)
|
||||
// DecorView.
|
||||
val fullscreenTransparentBackground = FrameLayout(dialog.context)
|
||||
decorView.addView(
|
||||
fullscreenTransparentBackground,
|
||||
0 /* index */,
|
||||
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
)
|
||||
)
|
||||
|
||||
// Move all original children of the DecorView to the new View we just added.
|
||||
for (i in 1 until decorView.childCount) {
|
||||
val view = decorView.getChildAt(1)
|
||||
decorView.removeViewAt(1)
|
||||
dialogContentWithBackground.addView(view)
|
||||
}
|
||||
val dialogContentWithBackground = FrameLayout(dialog.context)
|
||||
dialogContentWithBackground.background = decorView.background
|
||||
|
||||
// Make the window fullscreen and add a layout listener to ensure it stays fullscreen.
|
||||
window.setLayout(MATCH_PARENT, MATCH_PARENT)
|
||||
decorViewLayoutListener = View.OnLayoutChangeListener {
|
||||
v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
|
||||
if (window.attributes.width != MATCH_PARENT ||
|
||||
window.attributes.height != MATCH_PARENT
|
||||
) {
|
||||
// The dialog size changed, copy its size to dialogContentWithBackground and
|
||||
// make the dialog window full screen again.
|
||||
val layoutParams = dialogContentWithBackground.layoutParams
|
||||
layoutParams.width = window.attributes.width
|
||||
layoutParams.height = window.attributes.height
|
||||
dialogContentWithBackground.layoutParams = layoutParams
|
||||
window.setLayout(MATCH_PARENT, MATCH_PARENT)
|
||||
// Make the window background transparent. Note that setting the window (or
|
||||
// DecorView) background drawable to null leads to issues with background color (not
|
||||
// being transparent) or with insets that are not refreshed. Therefore we need to
|
||||
// set it to something not null, hence we are using android.R.color.transparent
|
||||
// here.
|
||||
window.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
|
||||
// Close the dialog when clicking outside of it.
|
||||
fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
|
||||
dialogContentWithBackground.isClickable = true
|
||||
|
||||
// Make sure the transparent and dialog backgrounds are not focusable by
|
||||
// accessibility
|
||||
// features.
|
||||
fullscreenTransparentBackground.importantForAccessibility =
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
dialogContentWithBackground.importantForAccessibility =
|
||||
View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
||||
|
||||
fullscreenTransparentBackground.addView(
|
||||
dialogContentWithBackground,
|
||||
FrameLayout.LayoutParams(
|
||||
window.attributes.width,
|
||||
window.attributes.height,
|
||||
window.attributes.gravity
|
||||
)
|
||||
)
|
||||
|
||||
// Move all original children of the DecorView to the new View we just added.
|
||||
for (i in 1 until decorView.childCount) {
|
||||
val view = decorView.getChildAt(1)
|
||||
decorView.removeViewAt(1)
|
||||
dialogContentWithBackground.addView(view)
|
||||
}
|
||||
}
|
||||
decorView.addOnLayoutChangeListener(decorViewLayoutListener)
|
||||
|
||||
dialogContentWithBackground
|
||||
}
|
||||
// Make the window fullscreen and add a layout listener to ensure it stays
|
||||
// fullscreen.
|
||||
window.setLayout(MATCH_PARENT, MATCH_PARENT)
|
||||
decorViewLayoutListener =
|
||||
View.OnLayoutChangeListener {
|
||||
v,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
oldLeft,
|
||||
oldTop,
|
||||
oldRight,
|
||||
oldBottom ->
|
||||
if (
|
||||
window.attributes.width != MATCH_PARENT ||
|
||||
window.attributes.height != MATCH_PARENT
|
||||
) {
|
||||
// The dialog size changed, copy its size to dialogContentWithBackground
|
||||
// and make the dialog window full screen again.
|
||||
val layoutParams = dialogContentWithBackground.layoutParams
|
||||
layoutParams.width = window.attributes.width
|
||||
layoutParams.height = window.attributes.height
|
||||
dialogContentWithBackground.layoutParams = layoutParams
|
||||
window.setLayout(MATCH_PARENT, MATCH_PARENT)
|
||||
}
|
||||
}
|
||||
decorView.addOnLayoutChangeListener(decorViewLayoutListener)
|
||||
|
||||
dialogContentWithBackground
|
||||
}
|
||||
this.dialogContentWithBackground = dialogContentWithBackground
|
||||
dialogContentWithBackground.setTag(R.id.tag_dialog_background, true)
|
||||
|
||||
@@ -482,7 +505,8 @@ private class AnimatedDialog(
|
||||
originalDialogBackgroundColor =
|
||||
GhostedViewLaunchAnimatorController.findGradientDrawable(background)
|
||||
?.color
|
||||
?.defaultColor ?: Color.BLACK
|
||||
?.defaultColor
|
||||
?: Color.BLACK
|
||||
|
||||
// Make the background view invisible until we start the animation. We use the transition
|
||||
// visibility like GhostView does so that we don't mess up with the accessibility tree (see
|
||||
@@ -508,24 +532,26 @@ private class AnimatedDialog(
|
||||
}
|
||||
|
||||
// Start the animation once the background view is properly laid out.
|
||||
dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
|
||||
override fun onLayoutChange(
|
||||
v: View,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int
|
||||
) {
|
||||
dialogContentWithBackground.removeOnLayoutChangeListener(this)
|
||||
dialogContentWithBackground.addOnLayoutChangeListener(
|
||||
object : View.OnLayoutChangeListener {
|
||||
override fun onLayoutChange(
|
||||
v: View,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int
|
||||
) {
|
||||
dialogContentWithBackground.removeOnLayoutChangeListener(this)
|
||||
|
||||
isOriginalDialogViewLaidOut = true
|
||||
maybeStartLaunchAnimation()
|
||||
isOriginalDialogViewLaidOut = true
|
||||
maybeStartLaunchAnimation()
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Disable the dim. We will enable it once we start the animation.
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
@@ -551,10 +577,12 @@ private class AnimatedDialog(
|
||||
// Create a ghost of the touch surface (which will make the touch surface invisible) and add
|
||||
// it to the host dialog. We trigger a one off synchronization to make sure that this is
|
||||
// done in sync between the two different windows.
|
||||
synchronizeNextDraw(then = {
|
||||
isTouchSurfaceGhostDrawn = true
|
||||
maybeStartLaunchAnimation()
|
||||
})
|
||||
synchronizeNextDraw(
|
||||
then = {
|
||||
isTouchSurfaceGhostDrawn = true
|
||||
maybeStartLaunchAnimation()
|
||||
}
|
||||
)
|
||||
GhostView.addGhost(touchSurface, decorView)
|
||||
|
||||
// The ghost of the touch surface was just created, so the touch surface is currently
|
||||
@@ -616,14 +644,13 @@ private class AnimatedDialog(
|
||||
onLaunchAnimationEnd = {
|
||||
touchSurface.setTag(R.id.tag_launch_animation_running, null)
|
||||
|
||||
// We hide the touch surface when the dialog is showing. We will make this
|
||||
// view visible again when dismissing the dialog.
|
||||
// We hide the touch surface when the dialog is showing. We will make this view
|
||||
// visible again when dismissing the dialog.
|
||||
touchSurface.visibility = View.INVISIBLE
|
||||
|
||||
isLaunching = false
|
||||
|
||||
// dismiss was called during the animation, dismiss again now to actually
|
||||
// dismiss.
|
||||
// dismiss was called during the animation, dismiss again now to actually dismiss.
|
||||
if (dismissRequested) {
|
||||
dialog.dismiss()
|
||||
}
|
||||
@@ -632,8 +659,9 @@ private class AnimatedDialog(
|
||||
// at the end of the launch animation, because the lauch animation already correctly
|
||||
// handles bounds changes.
|
||||
if (backgroundLayoutListener != null) {
|
||||
dialogContentWithBackground!!
|
||||
.addOnLayoutChangeListener(backgroundLayoutListener)
|
||||
dialogContentWithBackground!!.addOnLayoutChangeListener(
|
||||
backgroundLayoutListener
|
||||
)
|
||||
}
|
||||
cuj?.run { interactionJankMonitor.end(cujType) }
|
||||
}
|
||||
@@ -711,16 +739,19 @@ private class AnimatedDialog(
|
||||
dialogContentWithBackground.visibility = View.INVISIBLE
|
||||
|
||||
if (backgroundLayoutListener != null) {
|
||||
dialogContentWithBackground
|
||||
.removeOnLayoutChangeListener(backgroundLayoutListener)
|
||||
dialogContentWithBackground.removeOnLayoutChangeListener(
|
||||
backgroundLayoutListener
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure that the removal of the ghost and making the touch surface visible is
|
||||
// done at the same time.
|
||||
synchronizeNextDraw(then = {
|
||||
onAnimationFinished(true /* instantDismiss */)
|
||||
onDialogDismissed(this@AnimatedDialog)
|
||||
})
|
||||
synchronizeNextDraw(
|
||||
then = {
|
||||
onAnimationFinished(true /* instantDismiss */)
|
||||
onDialogDismissed(this@AnimatedDialog)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -740,56 +771,57 @@ private class AnimatedDialog(
|
||||
endViewController.launchContainer = decorView
|
||||
|
||||
val endState = endViewController.createAnimatorState()
|
||||
val controller = object : LaunchAnimator.Controller {
|
||||
override var launchContainer: ViewGroup
|
||||
get() = startViewController.launchContainer
|
||||
set(value) {
|
||||
startViewController.launchContainer = value
|
||||
endViewController.launchContainer = value
|
||||
val controller =
|
||||
object : LaunchAnimator.Controller {
|
||||
override var launchContainer: ViewGroup
|
||||
get() = startViewController.launchContainer
|
||||
set(value) {
|
||||
startViewController.launchContainer = value
|
||||
endViewController.launchContainer = value
|
||||
}
|
||||
|
||||
override fun createAnimatorState(): LaunchAnimator.State {
|
||||
return startViewController.createAnimatorState()
|
||||
}
|
||||
|
||||
override fun createAnimatorState(): LaunchAnimator.State {
|
||||
return startViewController.createAnimatorState()
|
||||
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
|
||||
// During launch, onLaunchAnimationStart will be used to remove the temporary
|
||||
// touch surface ghost so it is important to call this before calling
|
||||
// onLaunchAnimationStart on the controller (which will create its own ghost).
|
||||
onLaunchAnimationStart()
|
||||
|
||||
startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||
endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
|
||||
startViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
endViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
|
||||
onLaunchAnimationEnd()
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationProgress(
|
||||
state: LaunchAnimator.State,
|
||||
progress: Float,
|
||||
linearProgress: Float
|
||||
) {
|
||||
startViewController.onLaunchAnimationProgress(state, progress, linearProgress)
|
||||
|
||||
// The end view is visible only iff the starting view is not visible.
|
||||
state.visible = !state.visible
|
||||
endViewController.onLaunchAnimationProgress(state, progress, linearProgress)
|
||||
|
||||
// If the dialog content is complex, its dimension might change during the
|
||||
// launch animation. The animation end position might also change during the
|
||||
// exit animation, for instance when locking the phone when the dialog is open.
|
||||
// Therefore we update the end state to the new position/size. Usually the
|
||||
// dialog dimension or position will change in the early frames, so changing the
|
||||
// end state shouldn't really be noticeable.
|
||||
endViewController.fillGhostedViewState(endState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
|
||||
// During launch, onLaunchAnimationStart will be used to remove the temporary touch
|
||||
// surface ghost so it is important to call this before calling
|
||||
// onLaunchAnimationStart on the controller (which will create its own ghost).
|
||||
onLaunchAnimationStart()
|
||||
|
||||
startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||
endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
|
||||
startViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
endViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
|
||||
onLaunchAnimationEnd()
|
||||
}
|
||||
|
||||
override fun onLaunchAnimationProgress(
|
||||
state: LaunchAnimator.State,
|
||||
progress: Float,
|
||||
linearProgress: Float
|
||||
) {
|
||||
startViewController.onLaunchAnimationProgress(state, progress, linearProgress)
|
||||
|
||||
// The end view is visible only iff the starting view is not visible.
|
||||
state.visible = !state.visible
|
||||
endViewController.onLaunchAnimationProgress(state, progress, linearProgress)
|
||||
|
||||
// If the dialog content is complex, its dimension might change during the launch
|
||||
// animation. The animation end position might also change during the exit
|
||||
// animation, for instance when locking the phone when the dialog is open. Therefore
|
||||
// we update the end state to the new position/size. Usually the dialog dimension or
|
||||
// position will change in the early frames, so changing the end state shouldn't
|
||||
// really be noticeable.
|
||||
endViewController.fillGhostedViewState(endState)
|
||||
}
|
||||
}
|
||||
|
||||
launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
|
||||
}
|
||||
|
||||
@@ -821,7 +853,7 @@ private class AnimatedDialog(
|
||||
return (touchSurface.parent as? View)?.isShown ?: true
|
||||
}
|
||||
|
||||
/** A layout listener to animate the change of bounds of the dialog background. */
|
||||
/** A layout listener to animate the change of bounds of the dialog background. */
|
||||
class AnimatedBoundsLayoutListener : View.OnLayoutChangeListener {
|
||||
companion object {
|
||||
private const val ANIMATION_DURATION = 500L
|
||||
@@ -866,32 +898,35 @@ private class AnimatedDialog(
|
||||
currentAnimator?.cancel()
|
||||
currentAnimator = null
|
||||
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
duration = ANIMATION_DURATION
|
||||
interpolator = Interpolators.STANDARD
|
||||
val animator =
|
||||
ValueAnimator.ofFloat(0f, 1f).apply {
|
||||
duration = ANIMATION_DURATION
|
||||
interpolator = Interpolators.STANDARD
|
||||
|
||||
addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
currentAnimator = null
|
||||
addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
currentAnimator = null
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addUpdateListener { animatedValue ->
|
||||
val progress = animatedValue.animatedFraction
|
||||
|
||||
// Compute new bounds.
|
||||
bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt()
|
||||
bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt()
|
||||
bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt()
|
||||
bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt()
|
||||
|
||||
// Set the new bounds.
|
||||
view.left = bounds.left
|
||||
view.top = bounds.top
|
||||
view.right = bounds.right
|
||||
view.bottom = bounds.bottom
|
||||
}
|
||||
})
|
||||
|
||||
addUpdateListener { animatedValue ->
|
||||
val progress = animatedValue.animatedFraction
|
||||
|
||||
// Compute new bounds.
|
||||
bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt()
|
||||
bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt()
|
||||
bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt()
|
||||
bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt()
|
||||
|
||||
// Set the new bounds.
|
||||
view.left = bounds.left
|
||||
view.top = bounds.top
|
||||
view.right = bounds.right
|
||||
view.bottom = bounds.bottom
|
||||
}
|
||||
}
|
||||
|
||||
currentAnimator = animator
|
||||
animator.start()
|
||||
|
||||
@@ -105,9 +105,7 @@ open class GhostedViewLaunchAnimatorController(
|
||||
}
|
||||
|
||||
// Perform a BFS to find the largest View with background.
|
||||
val views = LinkedList<View>().apply {
|
||||
add(view)
|
||||
}
|
||||
val views = LinkedList<View>().apply { add(view) }
|
||||
|
||||
while (views.isNotEmpty()) {
|
||||
val v = views.removeFirst()
|
||||
@@ -161,10 +159,11 @@ open class GhostedViewLaunchAnimatorController(
|
||||
}
|
||||
|
||||
override fun createAnimatorState(): LaunchAnimator.State {
|
||||
val state = LaunchAnimator.State(
|
||||
topCornerRadius = getCurrentTopCornerRadius(),
|
||||
bottomCornerRadius = getCurrentBottomCornerRadius()
|
||||
)
|
||||
val state =
|
||||
LaunchAnimator.State(
|
||||
topCornerRadius = getCurrentTopCornerRadius(),
|
||||
bottomCornerRadius = getCurrentBottomCornerRadius()
|
||||
)
|
||||
fillGhostedViewState(state)
|
||||
return state
|
||||
}
|
||||
@@ -255,13 +254,14 @@ open class GhostedViewLaunchAnimatorController(
|
||||
|
||||
launchContainer.getLocationOnScreen(launchContainerLocation)
|
||||
ghostViewMatrix.postScale(
|
||||
scale, scale,
|
||||
scale,
|
||||
scale,
|
||||
ghostedViewState.centerX - launchContainerLocation[0],
|
||||
ghostedViewState.centerY - launchContainerLocation[1]
|
||||
)
|
||||
ghostViewMatrix.postTranslate(
|
||||
(leftChange + rightChange) / 2f,
|
||||
(topChange + bottomChange) / 2f
|
||||
(leftChange + rightChange) / 2f,
|
||||
(topChange + bottomChange) / 2f
|
||||
)
|
||||
ghostView.animationMatrix = ghostViewMatrix
|
||||
|
||||
|
||||
@@ -34,10 +34,7 @@ import kotlin.math.roundToInt
|
||||
private const val TAG = "LaunchAnimator"
|
||||
|
||||
/** A base class to animate a window launch (activity or dialog) from a view . */
|
||||
class LaunchAnimator(
|
||||
private val timings: Timings,
|
||||
private val interpolators: Interpolators
|
||||
) {
|
||||
class LaunchAnimator(private val timings: Timings, private val interpolators: Interpolators) {
|
||||
companion object {
|
||||
internal const val DEBUG = false
|
||||
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
|
||||
@@ -75,10 +72,10 @@ class LaunchAnimator(
|
||||
* with the opening window.
|
||||
*
|
||||
* This will be used to:
|
||||
* - Get the associated [Context].
|
||||
* - Compute whether we are expanding fully above the launch container.
|
||||
* - Get to overlay to which we initially put the window background layer, until the
|
||||
* opening window is made visible (see [openingWindowSyncView]).
|
||||
* - Get the associated [Context].
|
||||
* - Compute whether we are expanding fully above the launch container.
|
||||
* - Get to overlay to which we initially put the window background layer, until the opening
|
||||
* window is made visible (see [openingWindowSyncView]).
|
||||
*
|
||||
* This container can be changed to force this [Controller] to animate the expanding view
|
||||
* inside a different location, for instance to ensure correct layering during the
|
||||
@@ -132,7 +129,6 @@ class LaunchAnimator(
|
||||
var bottom: Int = 0,
|
||||
var left: Int = 0,
|
||||
var right: Int = 0,
|
||||
|
||||
var topCornerRadius: Float = 0f,
|
||||
var bottomCornerRadius: Float = 0f
|
||||
) {
|
||||
@@ -202,10 +198,10 @@ class LaunchAnimator(
|
||||
)
|
||||
|
||||
/**
|
||||
* Start a launch animation controlled by [controller] towards [endState]. An intermediary
|
||||
* layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and
|
||||
* should be the same background color as the opening (or closing) window. If [drawHole] is
|
||||
* true, then this intermediary layer will be drawn with SRC blending mode while it fades out.
|
||||
* Start a launch animation controlled by [controller] towards [endState]. An intermediary layer
|
||||
* with [windowBackgroundColor] will fade in then fade out above the expanding view, and should
|
||||
* be the same background color as the opening (or closing) window. If [drawHole] is true, then
|
||||
* this intermediary layer will be drawn with SRC blending mode while it fades out.
|
||||
*
|
||||
* TODO(b/184121838): Remove [drawHole] and instead make the StatusBar draw this hole instead.
|
||||
*/
|
||||
@@ -238,8 +234,12 @@ class LaunchAnimator(
|
||||
val endBottomCornerRadius = endState.bottomCornerRadius
|
||||
|
||||
fun maybeUpdateEndState() {
|
||||
if (endTop != endState.top || endBottom != endState.bottom ||
|
||||
endLeft != endState.left || endRight != endState.right) {
|
||||
if (
|
||||
endTop != endState.top ||
|
||||
endBottom != endState.bottom ||
|
||||
endLeft != endState.left ||
|
||||
endRight != endState.right
|
||||
) {
|
||||
endTop = endState.top
|
||||
endBottom = endState.bottom
|
||||
endLeft = endState.left
|
||||
@@ -256,10 +256,11 @@ class LaunchAnimator(
|
||||
// color, which is usually the same color of the app background. We first fade in this layer
|
||||
// to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
|
||||
// launch container and reveal the opening window.
|
||||
val windowBackgroundLayer = GradientDrawable().apply {
|
||||
setColor(windowBackgroundColor)
|
||||
alpha = 0
|
||||
}
|
||||
val windowBackgroundLayer =
|
||||
GradientDrawable().apply {
|
||||
setColor(windowBackgroundColor)
|
||||
alpha = 0
|
||||
}
|
||||
|
||||
// Update state.
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f)
|
||||
@@ -270,38 +271,41 @@ class LaunchAnimator(
|
||||
// [Controller.openingWindowSyncView] once the opening app window starts to be visible.
|
||||
val openingWindowSyncView = controller.openingWindowSyncView
|
||||
val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
|
||||
val moveBackgroundLayerWhenAppIsVisible = openingWindowSyncView != null &&
|
||||
openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl
|
||||
val moveBackgroundLayerWhenAppIsVisible =
|
||||
openingWindowSyncView != null &&
|
||||
openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl
|
||||
|
||||
val launchContainerOverlay = launchContainer.overlay
|
||||
var cancelled = false
|
||||
var movedBackgroundLayer = false
|
||||
|
||||
animator.addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Animation started")
|
||||
animator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Animation started")
|
||||
}
|
||||
controller.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||
|
||||
// Add the drawable to the launch container overlay. Overlays always draw
|
||||
// drawables after views, so we know that it will be drawn above any view added
|
||||
// by the controller.
|
||||
launchContainerOverlay.add(windowBackgroundLayer)
|
||||
}
|
||||
controller.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||
|
||||
// Add the drawable to the launch container overlay. Overlays always draw
|
||||
// drawables after views, so we know that it will be drawn above any view added
|
||||
// by the controller.
|
||||
launchContainerOverlay.add(windowBackgroundLayer)
|
||||
}
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Animation ended")
|
||||
}
|
||||
controller.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
launchContainerOverlay.remove(windowBackgroundLayer)
|
||||
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Animation ended")
|
||||
}
|
||||
controller.onLaunchAnimationEnd(isExpandingFullyAbove)
|
||||
launchContainerOverlay.remove(windowBackgroundLayer)
|
||||
|
||||
if (moveBackgroundLayerWhenAppIsVisible) {
|
||||
openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
|
||||
if (moveBackgroundLayerWhenAppIsVisible) {
|
||||
openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
animator.addUpdateListener { animation ->
|
||||
if (cancelled) {
|
||||
@@ -333,12 +337,13 @@ class LaunchAnimator(
|
||||
|
||||
// The expanding view can/should be hidden once it is completely covered by the opening
|
||||
// window.
|
||||
state.visible = getProgress(
|
||||
timings,
|
||||
linearProgress,
|
||||
timings.contentBeforeFadeOutDelay,
|
||||
timings.contentBeforeFadeOutDuration
|
||||
) < 1
|
||||
state.visible =
|
||||
getProgress(
|
||||
timings,
|
||||
linearProgress,
|
||||
timings.contentBeforeFadeOutDelay,
|
||||
timings.contentBeforeFadeOutDuration
|
||||
) < 1
|
||||
|
||||
if (moveBackgroundLayerWhenAppIsVisible && !state.visible && !movedBackgroundLayer) {
|
||||
// The expanding view is not visible, so the opening app is visible. If this is the
|
||||
@@ -352,11 +357,12 @@ class LaunchAnimator(
|
||||
ViewRootSync.synchronizeNextDraw(launchContainer, openingWindowSyncView, then = {})
|
||||
}
|
||||
|
||||
val container = if (movedBackgroundLayer) {
|
||||
openingWindowSyncView!!
|
||||
} else {
|
||||
controller.launchContainer
|
||||
}
|
||||
val container =
|
||||
if (movedBackgroundLayer) {
|
||||
openingWindowSyncView!!
|
||||
} else {
|
||||
controller.launchContainer
|
||||
}
|
||||
|
||||
applyStateToWindowBackgroundLayer(
|
||||
windowBackgroundLayer,
|
||||
@@ -415,23 +421,25 @@ class LaunchAnimator(
|
||||
|
||||
// We first fade in the background layer to hide the expanding view, then fade it out
|
||||
// with SRC mode to draw a hole punch in the status bar and reveal the opening window.
|
||||
val fadeInProgress = getProgress(
|
||||
timings,
|
||||
linearProgress,
|
||||
timings.contentBeforeFadeOutDelay,
|
||||
timings.contentBeforeFadeOutDuration
|
||||
)
|
||||
val fadeInProgress =
|
||||
getProgress(
|
||||
timings,
|
||||
linearProgress,
|
||||
timings.contentBeforeFadeOutDelay,
|
||||
timings.contentBeforeFadeOutDuration
|
||||
)
|
||||
if (fadeInProgress < 1) {
|
||||
val alpha =
|
||||
interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
|
||||
drawable.alpha = (alpha * 0xFF).roundToInt()
|
||||
} else {
|
||||
val fadeOutProgress = getProgress(
|
||||
timings,
|
||||
linearProgress,
|
||||
timings.contentAfterFadeInDelay,
|
||||
timings.contentAfterFadeInDuration
|
||||
)
|
||||
val fadeOutProgress =
|
||||
getProgress(
|
||||
timings,
|
||||
linearProgress,
|
||||
timings.contentAfterFadeInDelay,
|
||||
timings.contentAfterFadeInDuration
|
||||
)
|
||||
val alpha =
|
||||
1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress)
|
||||
drawable.alpha = (alpha * 0xFF).roundToInt()
|
||||
|
||||
@@ -27,4 +27,4 @@ interface LaunchableView {
|
||||
* [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
|
||||
*/
|
||||
fun setShouldBlockVisibilityChanges(block: Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ class RemoteTransitionAdapter {
|
||||
companion object {
|
||||
/**
|
||||
* Almost a copy of Transitions#setupStartState.
|
||||
*
|
||||
* TODO: remove when there is proper cross-process transaction sync.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
@@ -50,7 +51,8 @@ class RemoteTransitionAdapter {
|
||||
info: TransitionInfo,
|
||||
t: SurfaceControl.Transaction
|
||||
) {
|
||||
val isOpening = info.type == WindowManager.TRANSIT_OPEN ||
|
||||
val isOpening =
|
||||
info.type == WindowManager.TRANSIT_OPEN ||
|
||||
info.type == WindowManager.TRANSIT_TO_FRONT
|
||||
// Put animating stuff above this line and put static stuff below it.
|
||||
val zSplitLine = info.changes.size
|
||||
@@ -59,15 +61,19 @@ class RemoteTransitionAdapter {
|
||||
|
||||
// Launcher animates leaf tasks directly, so always reparent all task leashes to root.
|
||||
t.reparent(leash, info.rootLeash)
|
||||
t.setPosition(leash, (change.startAbsBounds.left - info.rootOffset.x).toFloat(), (
|
||||
change.startAbsBounds.top - info.rootOffset.y).toFloat())
|
||||
t.setPosition(
|
||||
leash,
|
||||
(change.startAbsBounds.left - info.rootOffset.x).toFloat(),
|
||||
(change.startAbsBounds.top - info.rootOffset.y).toFloat()
|
||||
)
|
||||
t.show(leash)
|
||||
// Put all the OPEN/SHOW on top
|
||||
if (mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT) {
|
||||
if (isOpening) {
|
||||
t.setLayer(leash, zSplitLine + info.changes.size - layer)
|
||||
if (change.flags
|
||||
and TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT == 0) {
|
||||
if (
|
||||
change.flags and TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT == 0
|
||||
) {
|
||||
// if transferred, it should be left visible.
|
||||
t.setAlpha(leash, 0f)
|
||||
}
|
||||
@@ -75,8 +81,9 @@ class RemoteTransitionAdapter {
|
||||
// put on bottom and leave it visible
|
||||
t.setLayer(leash, zSplitLine - layer)
|
||||
}
|
||||
} else if (mode == WindowManager.TRANSIT_CLOSE ||
|
||||
mode == WindowManager.TRANSIT_TO_BACK) {
|
||||
} else if (
|
||||
mode == WindowManager.TRANSIT_CLOSE || mode == WindowManager.TRANSIT_TO_BACK
|
||||
) {
|
||||
if (isOpening) {
|
||||
// put on bottom and leave visible
|
||||
t.setLayer(leash, zSplitLine - layer)
|
||||
@@ -102,10 +109,15 @@ class RemoteTransitionAdapter {
|
||||
// making leashes means we have to handle them specially.
|
||||
return change.leash
|
||||
}
|
||||
val leashSurface = SurfaceControl.Builder()
|
||||
val leashSurface =
|
||||
SurfaceControl.Builder()
|
||||
.setName(change.leash.toString() + "_transition-leash")
|
||||
.setContainerLayer().setParent(if (change.parent == null)
|
||||
info.rootLeash else info.getChange(change.parent!!)!!.leash).build()
|
||||
.setContainerLayer()
|
||||
.setParent(
|
||||
if (change.parent == null) info.rootLeash
|
||||
else info.getChange(change.parent!!)!!.leash
|
||||
)
|
||||
.build()
|
||||
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
|
||||
setupLeash(leashSurface, change, info.changes.size - order, info, t)
|
||||
t.reparent(change.leash, leashSurface)
|
||||
@@ -118,10 +130,10 @@ class RemoteTransitionAdapter {
|
||||
|
||||
private fun newModeToLegacyMode(newMode: Int): Int {
|
||||
return when (newMode) {
|
||||
WindowManager.TRANSIT_OPEN, WindowManager.TRANSIT_TO_FRONT
|
||||
-> RemoteAnimationTarget.MODE_OPENING
|
||||
WindowManager.TRANSIT_CLOSE, WindowManager.TRANSIT_TO_BACK
|
||||
-> RemoteAnimationTarget.MODE_CLOSING
|
||||
WindowManager.TRANSIT_OPEN,
|
||||
WindowManager.TRANSIT_TO_FRONT -> RemoteAnimationTarget.MODE_OPENING
|
||||
WindowManager.TRANSIT_CLOSE,
|
||||
WindowManager.TRANSIT_TO_BACK -> RemoteAnimationTarget.MODE_CLOSING
|
||||
else -> RemoteAnimationTarget.MODE_CHANGING
|
||||
}
|
||||
}
|
||||
@@ -138,12 +150,13 @@ class RemoteTransitionAdapter {
|
||||
info: TransitionInfo,
|
||||
t: SurfaceControl.Transaction
|
||||
): RemoteAnimationTarget {
|
||||
val target = RemoteAnimationTarget(
|
||||
val target =
|
||||
RemoteAnimationTarget(
|
||||
/* taskId */ if (change.taskInfo != null) change.taskInfo!!.taskId else -1,
|
||||
/* mode */ newModeToLegacyMode(change.mode),
|
||||
/* leash */ createLeash(info, change, order, t),
|
||||
/* isTranslucent */ (change.flags and TransitionInfo.FLAG_TRANSLUCENT != 0 ||
|
||||
change.flags and TransitionInfo.FLAG_SHOW_WALLPAPER != 0),
|
||||
change.flags and TransitionInfo.FLAG_SHOW_WALLPAPER != 0),
|
||||
/* clipRect */ null,
|
||||
/* contentInsets */ Rect(0, 0, 0, 0),
|
||||
/* prefixOrderIndex */ order,
|
||||
@@ -151,15 +164,16 @@ class RemoteTransitionAdapter {
|
||||
/* localBounds */ rectOffsetTo(change.endAbsBounds, change.endRelOffset),
|
||||
/* screenSpaceBounds */ Rect(change.endAbsBounds),
|
||||
/* windowConfig */ if (change.taskInfo != null)
|
||||
change.taskInfo!!.configuration.windowConfiguration else
|
||||
WindowConfiguration(),
|
||||
/* isNotInRecents */ if (change.taskInfo != null)
|
||||
!change.taskInfo!!.isRunning else true,
|
||||
change.taskInfo!!.configuration.windowConfiguration
|
||||
else WindowConfiguration(),
|
||||
/* isNotInRecents */ if (change.taskInfo != null) !change.taskInfo!!.isRunning
|
||||
else true,
|
||||
/* startLeash */ null,
|
||||
/* startBounds */ Rect(change.startAbsBounds),
|
||||
/* taskInfo */ change.taskInfo,
|
||||
/* allowEnterPip */ change.allowEnterPip,
|
||||
/* windowType */ WindowManager.LayoutParams.INVALID_WINDOW_TYPE)
|
||||
/* windowType */ WindowManager.LayoutParams.INVALID_WINDOW_TYPE
|
||||
)
|
||||
target.backgroundColor = change.backgroundColor
|
||||
return target
|
||||
}
|
||||
@@ -192,9 +206,7 @@ class RemoteTransitionAdapter {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun adaptRemoteRunner(
|
||||
runner: IRemoteAnimationRunner
|
||||
): IRemoteTransition.Stub {
|
||||
fun adaptRemoteRunner(runner: IRemoteAnimationRunner): IRemoteTransition.Stub {
|
||||
return object : IRemoteTransition.Stub() {
|
||||
override fun startAnimation(
|
||||
token: IBinder,
|
||||
@@ -218,18 +230,24 @@ class RemoteTransitionAdapter {
|
||||
var displayH = 0f
|
||||
for (i in info.changes.indices.reversed()) {
|
||||
val change = info.changes[i]
|
||||
if (change.taskInfo != null &&
|
||||
change.taskInfo!!.activityType
|
||||
== WindowConfiguration.ACTIVITY_TYPE_HOME) {
|
||||
isReturnToHome = (change.mode == WindowManager.TRANSIT_OPEN ||
|
||||
if (
|
||||
change.taskInfo != null &&
|
||||
change.taskInfo!!.activityType ==
|
||||
WindowConfiguration.ACTIVITY_TYPE_HOME
|
||||
) {
|
||||
isReturnToHome =
|
||||
(change.mode == WindowManager.TRANSIT_OPEN ||
|
||||
change.mode == WindowManager.TRANSIT_TO_FRONT)
|
||||
launcherTask = change
|
||||
launcherLayer = info.changes.size - i
|
||||
} else if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) {
|
||||
wallpaper = change
|
||||
}
|
||||
if (change.parent == null && change.endRotation >= 0 &&
|
||||
change.endRotation != change.startRotation) {
|
||||
if (
|
||||
change.parent == null &&
|
||||
change.endRotation >= 0 &&
|
||||
change.endRotation != change.startRotation
|
||||
) {
|
||||
rotateDelta = change.endRotation - change.startRotation
|
||||
displayW = change.endAbsBounds.width().toFloat()
|
||||
displayH = change.endAbsBounds.height().toFloat()
|
||||
@@ -240,8 +258,13 @@ class RemoteTransitionAdapter {
|
||||
val counterLauncher = CounterRotator()
|
||||
val counterWallpaper = CounterRotator()
|
||||
if (launcherTask != null && rotateDelta != 0 && launcherTask.parent != null) {
|
||||
counterLauncher.setup(t, info.getChange(launcherTask.parent!!)!!.leash,
|
||||
rotateDelta, displayW, displayH)
|
||||
counterLauncher.setup(
|
||||
t,
|
||||
info.getChange(launcherTask.parent!!)!!.leash,
|
||||
rotateDelta,
|
||||
displayW,
|
||||
displayH
|
||||
)
|
||||
if (counterLauncher.surface != null) {
|
||||
t.setLayer(counterLauncher.surface!!, launcherLayer)
|
||||
}
|
||||
@@ -257,8 +280,10 @@ class RemoteTransitionAdapter {
|
||||
val mode = info.changes[i].mode
|
||||
// Only deal with independent layers
|
||||
if (!TransitionInfo.isIndependent(change, info)) continue
|
||||
if (mode == WindowManager.TRANSIT_CLOSE ||
|
||||
mode == WindowManager.TRANSIT_TO_BACK) {
|
||||
if (
|
||||
mode == WindowManager.TRANSIT_CLOSE ||
|
||||
mode == WindowManager.TRANSIT_TO_BACK
|
||||
) {
|
||||
t.setLayer(leash!!, info.changes.size * 3 - i)
|
||||
counterLauncher.addChild(t, leash)
|
||||
}
|
||||
@@ -273,8 +298,13 @@ class RemoteTransitionAdapter {
|
||||
counterLauncher.addChild(t, leashMap[launcherTask.leash])
|
||||
}
|
||||
if (wallpaper != null && rotateDelta != 0 && wallpaper.parent != null) {
|
||||
counterWallpaper.setup(t, info.getChange(wallpaper.parent!!)!!.leash,
|
||||
rotateDelta, displayW, displayH)
|
||||
counterWallpaper.setup(
|
||||
t,
|
||||
info.getChange(wallpaper.parent!!)!!.leash,
|
||||
rotateDelta,
|
||||
displayW,
|
||||
displayH
|
||||
)
|
||||
if (counterWallpaper.surface != null) {
|
||||
t.setLayer(counterWallpaper.surface!!, -1)
|
||||
counterWallpaper.addChild(t, leashMap[wallpaper.leash])
|
||||
@@ -282,37 +312,47 @@ class RemoteTransitionAdapter {
|
||||
}
|
||||
}
|
||||
t.apply()
|
||||
val animationFinishedCallback = object : IRemoteAnimationFinishedCallback {
|
||||
override fun onAnimationFinished() {
|
||||
val finishTransaction = SurfaceControl.Transaction()
|
||||
counterLauncher.cleanUp(finishTransaction)
|
||||
counterWallpaper.cleanUp(finishTransaction)
|
||||
// Release surface references now. This is apparently to free GPU memory
|
||||
// while doing quick operations (eg. during CTS).
|
||||
for (i in info.changes.indices.reversed()) {
|
||||
info.changes[i].leash.release()
|
||||
val animationFinishedCallback =
|
||||
object : IRemoteAnimationFinishedCallback {
|
||||
override fun onAnimationFinished() {
|
||||
val finishTransaction = SurfaceControl.Transaction()
|
||||
counterLauncher.cleanUp(finishTransaction)
|
||||
counterWallpaper.cleanUp(finishTransaction)
|
||||
// Release surface references now. This is apparently to free GPU
|
||||
// memory while doing quick operations (eg. during CTS).
|
||||
for (i in info.changes.indices.reversed()) {
|
||||
info.changes[i].leash.release()
|
||||
}
|
||||
for (i in leashMap.size - 1 downTo 0) {
|
||||
leashMap.valueAt(i).release()
|
||||
}
|
||||
try {
|
||||
finishCallback.onTransitionFinished(
|
||||
null /* wct */,
|
||||
finishTransaction
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(
|
||||
"ActivityOptionsCompat",
|
||||
"Failed to call app controlled" +
|
||||
" animation finished callback",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
for (i in leashMap.size - 1 downTo 0) {
|
||||
leashMap.valueAt(i).release()
|
||||
}
|
||||
try {
|
||||
finishCallback.onTransitionFinished(null /* wct */,
|
||||
finishTransaction)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e("ActivityOptionsCompat", "Failed to call app controlled" +
|
||||
" animation finished callback", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun asBinder(): IBinder? {
|
||||
return null
|
||||
override fun asBinder(): IBinder? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(bc-unlcok): Pass correct transit type.
|
||||
runner.onAnimationStart(
|
||||
WindowManager.TRANSIT_OLD_NONE,
|
||||
appsCompat, wallpapersCompat, nonAppsCompat,
|
||||
animationFinishedCallback)
|
||||
WindowManager.TRANSIT_OLD_NONE,
|
||||
appsCompat,
|
||||
wallpapersCompat,
|
||||
nonAppsCompat,
|
||||
animationFinishedCallback
|
||||
)
|
||||
}
|
||||
|
||||
override fun mergeAnimation(
|
||||
@@ -329,18 +369,14 @@ class RemoteTransitionAdapter {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun adaptRemoteAnimation(
|
||||
adapter: RemoteAnimationAdapter
|
||||
): RemoteTransition {
|
||||
fun adaptRemoteAnimation(adapter: RemoteAnimationAdapter): RemoteTransition {
|
||||
return RemoteTransition(adaptRemoteRunner(adapter.runner), adapter.callingApplication)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class that takes care of counter-rotating surfaces during a transition animation.
|
||||
*/
|
||||
/** Utility class that takes care of counter-rotating surfaces during a transition animation. */
|
||||
class CounterRotator {
|
||||
/** Gets the surface with the counter-rotation. */
|
||||
/** Gets the surface with the counter-rotation. */
|
||||
var surface: SurfaceControl? = null
|
||||
private set
|
||||
|
||||
@@ -358,7 +394,8 @@ class RemoteTransitionAdapter {
|
||||
parentH: Float
|
||||
) {
|
||||
if (rotateDelta == 0) return
|
||||
val surface = SurfaceControl.Builder()
|
||||
val surface =
|
||||
SurfaceControl.Builder()
|
||||
.setName("Transition Unrotate")
|
||||
.setContainerLayer()
|
||||
.setParent(parent)
|
||||
@@ -378,21 +415,19 @@ class RemoteTransitionAdapter {
|
||||
t.show(surface)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a surface that needs to be counter-rotate.
|
||||
*/
|
||||
/** Adds a surface that needs to be counter-rotate. */
|
||||
fun addChild(t: SurfaceControl.Transaction, child: SurfaceControl?) {
|
||||
if (surface == null) return
|
||||
t.reparent(child!!, surface)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean-up. Since finishTransaction should reset all change leashes, we only need to remove the
|
||||
* counter rotation surface.
|
||||
* Clean-up. Since finishTransaction should reset all change leashes, we only need to remove
|
||||
* the counter rotation surface.
|
||||
*/
|
||||
fun cleanUp(finishTransaction: SurfaceControl.Transaction) {
|
||||
if (surface == null) return
|
||||
finishTransaction.remove(surface!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ object ShadeInterpolation {
|
||||
} else {
|
||||
val oneMinusFrac = 1f - mappedFraction
|
||||
(1f - 0.5f * (1f - Math.cos((3.14159f * oneMinusFrac * oneMinusFrac).toDouble())))
|
||||
.toFloat()
|
||||
.toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,12 +41,13 @@ class ViewHierarchyAnimator {
|
||||
private val DEFAULT_REMOVAL_INTERPOLATOR = Interpolators.STANDARD_ACCELERATE
|
||||
|
||||
/** The properties used to animate the view bounds. */
|
||||
private val PROPERTIES = mapOf(
|
||||
Bound.LEFT to createViewProperty(Bound.LEFT),
|
||||
Bound.TOP to createViewProperty(Bound.TOP),
|
||||
Bound.RIGHT to createViewProperty(Bound.RIGHT),
|
||||
Bound.BOTTOM to createViewProperty(Bound.BOTTOM)
|
||||
)
|
||||
private val PROPERTIES =
|
||||
mapOf(
|
||||
Bound.LEFT to createViewProperty(Bound.LEFT),
|
||||
Bound.TOP to createViewProperty(Bound.TOP),
|
||||
Bound.RIGHT to createViewProperty(Bound.RIGHT),
|
||||
Bound.BOTTOM to createViewProperty(Bound.BOTTOM)
|
||||
)
|
||||
|
||||
private fun createViewProperty(bound: Bound): IntProperty<View> {
|
||||
return object : IntProperty<View>(bound.label) {
|
||||
@@ -103,7 +104,8 @@ class ViewHierarchyAnimator {
|
||||
duration: Long,
|
||||
ephemeral: Boolean
|
||||
): Boolean {
|
||||
if (!isVisible(
|
||||
if (
|
||||
!isVisible(
|
||||
rootView.visibility,
|
||||
rootView.left,
|
||||
rootView.top,
|
||||
@@ -131,11 +133,7 @@ class ViewHierarchyAnimator {
|
||||
duration: Long,
|
||||
ephemeral: Boolean
|
||||
): View.OnLayoutChangeListener {
|
||||
return createListener(
|
||||
interpolator,
|
||||
duration,
|
||||
ephemeral
|
||||
)
|
||||
return createListener(interpolator, duration, ephemeral)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +169,8 @@ class ViewHierarchyAnimator {
|
||||
duration: Long = DEFAULT_DURATION,
|
||||
includeMargins: Boolean = false
|
||||
): Boolean {
|
||||
if (isVisible(
|
||||
if (
|
||||
isVisible(
|
||||
rootView.visibility,
|
||||
rootView.left,
|
||||
rootView.top,
|
||||
@@ -182,9 +181,13 @@ class ViewHierarchyAnimator {
|
||||
return false
|
||||
}
|
||||
|
||||
val listener = createAdditionListener(
|
||||
origin, interpolator, duration, ignorePreviousValues = !includeMargins
|
||||
)
|
||||
val listener =
|
||||
createAdditionListener(
|
||||
origin,
|
||||
interpolator,
|
||||
duration,
|
||||
ignorePreviousValues = !includeMargins
|
||||
)
|
||||
addListener(rootView, listener, recursive = true)
|
||||
return true
|
||||
}
|
||||
@@ -257,24 +260,26 @@ class ViewHierarchyAnimator {
|
||||
return
|
||||
}
|
||||
|
||||
val startValues = processStartValues(
|
||||
origin,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
startLeft,
|
||||
startTop,
|
||||
startRight,
|
||||
startBottom,
|
||||
ignorePreviousValues
|
||||
)
|
||||
val endValues = mapOf(
|
||||
Bound.LEFT to left,
|
||||
Bound.TOP to top,
|
||||
Bound.RIGHT to right,
|
||||
Bound.BOTTOM to bottom
|
||||
)
|
||||
val startValues =
|
||||
processStartValues(
|
||||
origin,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
startLeft,
|
||||
startTop,
|
||||
startRight,
|
||||
startBottom,
|
||||
ignorePreviousValues
|
||||
)
|
||||
val endValues =
|
||||
mapOf(
|
||||
Bound.LEFT to left,
|
||||
Bound.TOP to top,
|
||||
Bound.RIGHT to right,
|
||||
Bound.BOTTOM to bottom
|
||||
)
|
||||
|
||||
val boundsToAnimate = mutableSetOf<Bound>()
|
||||
if (startValues.getValue(Bound.LEFT) != left) boundsToAnimate.add(Bound.LEFT)
|
||||
@@ -313,7 +318,8 @@ class ViewHierarchyAnimator {
|
||||
interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
|
||||
duration: Long = DEFAULT_DURATION
|
||||
): Boolean {
|
||||
if (!isVisible(
|
||||
if (
|
||||
!isVisible(
|
||||
rootView.visibility,
|
||||
rootView.left,
|
||||
rootView.top,
|
||||
@@ -327,11 +333,7 @@ class ViewHierarchyAnimator {
|
||||
val parent = rootView.parent as ViewGroup
|
||||
|
||||
// Ensure that rootView's siblings animate nicely around the removal.
|
||||
val listener = createUpdateListener(
|
||||
interpolator,
|
||||
duration,
|
||||
ephemeral = true
|
||||
)
|
||||
val listener = createUpdateListener(interpolator, duration, ephemeral = true)
|
||||
for (i in 0 until parent.childCount) {
|
||||
val child = parent.getChildAt(i)
|
||||
if (child == rootView) continue
|
||||
@@ -346,19 +348,21 @@ class ViewHierarchyAnimator {
|
||||
// them manually during the animation.
|
||||
parent.overlay.add(rootView)
|
||||
|
||||
val startValues = mapOf(
|
||||
Bound.LEFT to rootView.left,
|
||||
Bound.TOP to rootView.top,
|
||||
Bound.RIGHT to rootView.right,
|
||||
Bound.BOTTOM to rootView.bottom
|
||||
)
|
||||
val endValues = processEndValuesForRemoval(
|
||||
destination,
|
||||
rootView.left,
|
||||
rootView.top,
|
||||
rootView.right,
|
||||
rootView.bottom
|
||||
)
|
||||
val startValues =
|
||||
mapOf(
|
||||
Bound.LEFT to rootView.left,
|
||||
Bound.TOP to rootView.top,
|
||||
Bound.RIGHT to rootView.right,
|
||||
Bound.BOTTOM to rootView.bottom
|
||||
)
|
||||
val endValues =
|
||||
processEndValuesForRemoval(
|
||||
destination,
|
||||
rootView.left,
|
||||
rootView.top,
|
||||
rootView.right,
|
||||
rootView.bottom
|
||||
)
|
||||
|
||||
val boundsToAnimate = mutableSetOf<Bound>()
|
||||
if (rootView.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT)
|
||||
@@ -400,20 +404,24 @@ class ViewHierarchyAnimator {
|
||||
(animation.animatedValue as Float) * startAlphas[i]
|
||||
}
|
||||
}
|
||||
animator.addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
rootView.animate()
|
||||
.alpha(0f)
|
||||
.setInterpolator(Interpolators.ALPHA_OUT)
|
||||
.setDuration(duration / 2)
|
||||
.withEndAction { parent.overlay.remove(rootView) }
|
||||
.start()
|
||||
animator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
rootView
|
||||
.animate()
|
||||
.alpha(0f)
|
||||
.setInterpolator(Interpolators.ALPHA_OUT)
|
||||
.setDuration(duration / 2)
|
||||
.withEndAction { parent.overlay.remove(rootView) }
|
||||
.start()
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
animator.start()
|
||||
} else {
|
||||
// Fade out the view during the second half of the removal.
|
||||
rootView.animate()
|
||||
rootView
|
||||
.animate()
|
||||
.alpha(0f)
|
||||
.setInterpolator(Interpolators.ALPHA_OUT)
|
||||
.setDuration(duration / 2)
|
||||
@@ -440,21 +448,23 @@ class ViewHierarchyAnimator {
|
||||
) {
|
||||
for (i in 0 until rootView.childCount) {
|
||||
val child = rootView.getChildAt(i)
|
||||
val childStartValues = mapOf(
|
||||
Bound.LEFT to child.left,
|
||||
Bound.TOP to child.top,
|
||||
Bound.RIGHT to child.right,
|
||||
Bound.BOTTOM to child.bottom
|
||||
)
|
||||
val childEndValues = processChildEndValuesForRemoval(
|
||||
destination,
|
||||
child.left,
|
||||
child.top,
|
||||
child.right,
|
||||
child.bottom,
|
||||
endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT),
|
||||
endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP)
|
||||
)
|
||||
val childStartValues =
|
||||
mapOf(
|
||||
Bound.LEFT to child.left,
|
||||
Bound.TOP to child.top,
|
||||
Bound.RIGHT to child.right,
|
||||
Bound.BOTTOM to child.bottom
|
||||
)
|
||||
val childEndValues =
|
||||
processChildEndValuesForRemoval(
|
||||
destination,
|
||||
child.left,
|
||||
child.top,
|
||||
child.right,
|
||||
child.bottom,
|
||||
endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT),
|
||||
endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP)
|
||||
)
|
||||
|
||||
val boundsToAnimate = mutableSetOf<Bound>()
|
||||
if (child.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT)
|
||||
@@ -500,6 +510,7 @@ class ViewHierarchyAnimator {
|
||||
* not newly introduced margins are included.
|
||||
*
|
||||
* Base case
|
||||
* ```
|
||||
* 1) origin=TOP
|
||||
* x---------x x---------x x---------x x---------x x---------x
|
||||
* x---------x | | | | | |
|
||||
@@ -518,11 +529,11 @@ class ViewHierarchyAnimator {
|
||||
* x -> x---x -> | | -> | | -> | |
|
||||
* x-----x x-------x | |
|
||||
* x---------x
|
||||
*
|
||||
* ```
|
||||
* In case the start and end values differ in the direction of the origin, and
|
||||
* [ignorePreviousValues] is false, the previous values are used and a translation is
|
||||
* included in addition to the view expansion.
|
||||
*
|
||||
* ```
|
||||
* origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70)
|
||||
* x
|
||||
* x--x
|
||||
@@ -531,6 +542,7 @@ class ViewHierarchyAnimator {
|
||||
* x----x | |
|
||||
* | |
|
||||
* x------x
|
||||
* ```
|
||||
*/
|
||||
private fun processStartValues(
|
||||
origin: Hotspot?,
|
||||
@@ -555,42 +567,54 @@ class ViewHierarchyAnimator {
|
||||
var bottom = startBottom
|
||||
|
||||
if (origin != null) {
|
||||
left = when (origin) {
|
||||
Hotspot.CENTER -> (newLeft + newRight) / 2
|
||||
Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> min(startLeft, newLeft)
|
||||
Hotspot.TOP, Hotspot.BOTTOM -> newLeft
|
||||
Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> max(
|
||||
startRight,
|
||||
newRight
|
||||
)
|
||||
}
|
||||
top = when (origin) {
|
||||
Hotspot.CENTER -> (newTop + newBottom) / 2
|
||||
Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> min(startTop, newTop)
|
||||
Hotspot.LEFT, Hotspot.RIGHT -> newTop
|
||||
Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> max(
|
||||
startBottom,
|
||||
newBottom
|
||||
)
|
||||
}
|
||||
right = when (origin) {
|
||||
Hotspot.CENTER -> (newLeft + newRight) / 2
|
||||
Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> max(
|
||||
startRight,
|
||||
newRight
|
||||
)
|
||||
Hotspot.TOP, Hotspot.BOTTOM -> newRight
|
||||
Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> min(startLeft, newLeft)
|
||||
}
|
||||
bottom = when (origin) {
|
||||
Hotspot.CENTER -> (newTop + newBottom) / 2
|
||||
Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> max(
|
||||
startBottom,
|
||||
newBottom
|
||||
)
|
||||
Hotspot.LEFT, Hotspot.RIGHT -> newBottom
|
||||
Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> min(startTop, newTop)
|
||||
}
|
||||
left =
|
||||
when (origin) {
|
||||
Hotspot.CENTER -> (newLeft + newRight) / 2
|
||||
Hotspot.BOTTOM_LEFT,
|
||||
Hotspot.LEFT,
|
||||
Hotspot.TOP_LEFT -> min(startLeft, newLeft)
|
||||
Hotspot.TOP,
|
||||
Hotspot.BOTTOM -> newLeft
|
||||
Hotspot.TOP_RIGHT,
|
||||
Hotspot.RIGHT,
|
||||
Hotspot.BOTTOM_RIGHT -> max(startRight, newRight)
|
||||
}
|
||||
top =
|
||||
when (origin) {
|
||||
Hotspot.CENTER -> (newTop + newBottom) / 2
|
||||
Hotspot.TOP_LEFT,
|
||||
Hotspot.TOP,
|
||||
Hotspot.TOP_RIGHT -> min(startTop, newTop)
|
||||
Hotspot.LEFT,
|
||||
Hotspot.RIGHT -> newTop
|
||||
Hotspot.BOTTOM_RIGHT,
|
||||
Hotspot.BOTTOM,
|
||||
Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom)
|
||||
}
|
||||
right =
|
||||
when (origin) {
|
||||
Hotspot.CENTER -> (newLeft + newRight) / 2
|
||||
Hotspot.TOP_RIGHT,
|
||||
Hotspot.RIGHT,
|
||||
Hotspot.BOTTOM_RIGHT -> max(startRight, newRight)
|
||||
Hotspot.TOP,
|
||||
Hotspot.BOTTOM -> newRight
|
||||
Hotspot.BOTTOM_LEFT,
|
||||
Hotspot.LEFT,
|
||||
Hotspot.TOP_LEFT -> min(startLeft, newLeft)
|
||||
}
|
||||
bottom =
|
||||
when (origin) {
|
||||
Hotspot.CENTER -> (newTop + newBottom) / 2
|
||||
Hotspot.BOTTOM_RIGHT,
|
||||
Hotspot.BOTTOM,
|
||||
Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom)
|
||||
Hotspot.LEFT,
|
||||
Hotspot.RIGHT -> newBottom
|
||||
Hotspot.TOP_LEFT,
|
||||
Hotspot.TOP,
|
||||
Hotspot.TOP_RIGHT -> min(startTop, newTop)
|
||||
}
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
@@ -606,6 +630,7 @@ class ViewHierarchyAnimator {
|
||||
* view's starting bounds.
|
||||
*
|
||||
* Examples:
|
||||
* ```
|
||||
* 1) destination=TOP
|
||||
* x---------x x---------x x---------x x---------x x---------x
|
||||
* | | | | | | x---------x
|
||||
@@ -624,6 +649,7 @@ class ViewHierarchyAnimator {
|
||||
* | | -> | | -> | | -> x---x -> x
|
||||
* | | x-------x x-----x
|
||||
* x---------x
|
||||
* ```
|
||||
*/
|
||||
private fun processEndValuesForRemoval(
|
||||
destination: Hotspot,
|
||||
@@ -632,32 +658,54 @@ class ViewHierarchyAnimator {
|
||||
right: Int,
|
||||
bottom: Int
|
||||
): Map<Bound, Int> {
|
||||
val endLeft = when (destination) {
|
||||
Hotspot.CENTER -> (left + right) / 2
|
||||
Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT, Hotspot.TOP ->
|
||||
left
|
||||
Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> right
|
||||
}
|
||||
val endTop = when (destination) {
|
||||
Hotspot.CENTER -> (top + bottom) / 2
|
||||
Hotspot.LEFT, Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT, Hotspot.RIGHT ->
|
||||
top
|
||||
Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> bottom
|
||||
}
|
||||
val endRight = when (destination) {
|
||||
Hotspot.CENTER -> (left + right) / 2
|
||||
Hotspot.TOP, Hotspot.TOP_RIGHT, Hotspot.RIGHT,
|
||||
Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM ->
|
||||
right
|
||||
Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> left
|
||||
}
|
||||
val endBottom = when (destination) {
|
||||
Hotspot.CENTER -> (top + bottom) / 2
|
||||
Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM,
|
||||
Hotspot.BOTTOM_LEFT, Hotspot.LEFT ->
|
||||
bottom
|
||||
Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> top
|
||||
}
|
||||
val endLeft =
|
||||
when (destination) {
|
||||
Hotspot.CENTER -> (left + right) / 2
|
||||
Hotspot.BOTTOM,
|
||||
Hotspot.BOTTOM_LEFT,
|
||||
Hotspot.LEFT,
|
||||
Hotspot.TOP_LEFT,
|
||||
Hotspot.TOP -> left
|
||||
Hotspot.TOP_RIGHT,
|
||||
Hotspot.RIGHT,
|
||||
Hotspot.BOTTOM_RIGHT -> right
|
||||
}
|
||||
val endTop =
|
||||
when (destination) {
|
||||
Hotspot.CENTER -> (top + bottom) / 2
|
||||
Hotspot.LEFT,
|
||||
Hotspot.TOP_LEFT,
|
||||
Hotspot.TOP,
|
||||
Hotspot.TOP_RIGHT,
|
||||
Hotspot.RIGHT -> top
|
||||
Hotspot.BOTTOM_RIGHT,
|
||||
Hotspot.BOTTOM,
|
||||
Hotspot.BOTTOM_LEFT -> bottom
|
||||
}
|
||||
val endRight =
|
||||
when (destination) {
|
||||
Hotspot.CENTER -> (left + right) / 2
|
||||
Hotspot.TOP,
|
||||
Hotspot.TOP_RIGHT,
|
||||
Hotspot.RIGHT,
|
||||
Hotspot.BOTTOM_RIGHT,
|
||||
Hotspot.BOTTOM -> right
|
||||
Hotspot.BOTTOM_LEFT,
|
||||
Hotspot.LEFT,
|
||||
Hotspot.TOP_LEFT -> left
|
||||
}
|
||||
val endBottom =
|
||||
when (destination) {
|
||||
Hotspot.CENTER -> (top + bottom) / 2
|
||||
Hotspot.RIGHT,
|
||||
Hotspot.BOTTOM_RIGHT,
|
||||
Hotspot.BOTTOM,
|
||||
Hotspot.BOTTOM_LEFT,
|
||||
Hotspot.LEFT -> bottom
|
||||
Hotspot.TOP_LEFT,
|
||||
Hotspot.TOP,
|
||||
Hotspot.TOP_RIGHT -> top
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
Bound.LEFT to endLeft,
|
||||
@@ -675,6 +723,7 @@ class ViewHierarchyAnimator {
|
||||
* its center is at the [destination].
|
||||
*
|
||||
* Examples:
|
||||
* ```
|
||||
* 1) destination=TOP
|
||||
* The child maintains its left and right positions, but is shifted up so that its
|
||||
* center is on the parent's end top edge.
|
||||
@@ -682,6 +731,7 @@ class ViewHierarchyAnimator {
|
||||
* The child shifts so that its center is on the parent's end bottom left corner.
|
||||
* 3) destination=CENTER
|
||||
* The child shifts so that its own center is on the parent's end center.
|
||||
* ```
|
||||
*/
|
||||
private fun processChildEndValuesForRemoval(
|
||||
destination: Hotspot,
|
||||
@@ -695,32 +745,54 @@ class ViewHierarchyAnimator {
|
||||
val halfWidth = (right - left) / 2
|
||||
val halfHeight = (bottom - top) / 2
|
||||
|
||||
val endLeft = when (destination) {
|
||||
Hotspot.CENTER -> (parentWidth / 2) - halfWidth
|
||||
Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> -halfWidth
|
||||
Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> parentWidth - halfWidth
|
||||
Hotspot.TOP, Hotspot.BOTTOM -> left
|
||||
}
|
||||
val endTop = when (destination) {
|
||||
Hotspot.CENTER -> (parentHeight / 2) - halfHeight
|
||||
Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> -halfHeight
|
||||
Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT ->
|
||||
parentHeight - halfHeight
|
||||
Hotspot.LEFT, Hotspot.RIGHT -> top
|
||||
}
|
||||
val endRight = when (destination) {
|
||||
Hotspot.CENTER -> (parentWidth / 2) + halfWidth
|
||||
Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> parentWidth + halfWidth
|
||||
Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> halfWidth
|
||||
Hotspot.TOP, Hotspot.BOTTOM -> right
|
||||
}
|
||||
val endBottom = when (destination) {
|
||||
Hotspot.CENTER -> (parentHeight / 2) + halfHeight
|
||||
Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT ->
|
||||
parentHeight + halfHeight
|
||||
Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> halfHeight
|
||||
Hotspot.LEFT, Hotspot.RIGHT -> bottom
|
||||
}
|
||||
val endLeft =
|
||||
when (destination) {
|
||||
Hotspot.CENTER -> (parentWidth / 2) - halfWidth
|
||||
Hotspot.BOTTOM_LEFT,
|
||||
Hotspot.LEFT,
|
||||
Hotspot.TOP_LEFT -> -halfWidth
|
||||
Hotspot.TOP_RIGHT,
|
||||
Hotspot.RIGHT,
|
||||
Hotspot.BOTTOM_RIGHT -> parentWidth - halfWidth
|
||||
Hotspot.TOP,
|
||||
Hotspot.BOTTOM -> left
|
||||
}
|
||||
val endTop =
|
||||
when (destination) {
|
||||
Hotspot.CENTER -> (parentHeight / 2) - halfHeight
|
||||
Hotspot.TOP_LEFT,
|
||||
Hotspot.TOP,
|
||||
Hotspot.TOP_RIGHT -> -halfHeight
|
||||
Hotspot.BOTTOM_RIGHT,
|
||||
Hotspot.BOTTOM,
|
||||
Hotspot.BOTTOM_LEFT -> parentHeight - halfHeight
|
||||
Hotspot.LEFT,
|
||||
Hotspot.RIGHT -> top
|
||||
}
|
||||
val endRight =
|
||||
when (destination) {
|
||||
Hotspot.CENTER -> (parentWidth / 2) + halfWidth
|
||||
Hotspot.TOP_RIGHT,
|
||||
Hotspot.RIGHT,
|
||||
Hotspot.BOTTOM_RIGHT -> parentWidth + halfWidth
|
||||
Hotspot.BOTTOM_LEFT,
|
||||
Hotspot.LEFT,
|
||||
Hotspot.TOP_LEFT -> halfWidth
|
||||
Hotspot.TOP,
|
||||
Hotspot.BOTTOM -> right
|
||||
}
|
||||
val endBottom =
|
||||
when (destination) {
|
||||
Hotspot.CENTER -> (parentHeight / 2) + halfHeight
|
||||
Hotspot.BOTTOM_RIGHT,
|
||||
Hotspot.BOTTOM,
|
||||
Hotspot.BOTTOM_LEFT -> parentHeight + halfHeight
|
||||
Hotspot.TOP_LEFT,
|
||||
Hotspot.TOP,
|
||||
Hotspot.TOP_RIGHT -> halfHeight
|
||||
Hotspot.LEFT,
|
||||
Hotspot.RIGHT -> bottom
|
||||
}
|
||||
|
||||
return mapOf(
|
||||
Bound.LEFT to endLeft,
|
||||
@@ -790,44 +862,49 @@ class ViewHierarchyAnimator {
|
||||
duration: Long,
|
||||
ephemeral: Boolean
|
||||
) {
|
||||
val propertyValuesHolders = buildList {
|
||||
bounds.forEach { bound ->
|
||||
add(
|
||||
PropertyValuesHolder.ofInt(
|
||||
PROPERTIES[bound],
|
||||
startValues.getValue(bound),
|
||||
endValues.getValue(bound)
|
||||
)
|
||||
)
|
||||
}
|
||||
}.toTypedArray()
|
||||
val propertyValuesHolders =
|
||||
buildList {
|
||||
bounds.forEach { bound ->
|
||||
add(
|
||||
PropertyValuesHolder.ofInt(
|
||||
PROPERTIES[bound],
|
||||
startValues.getValue(bound),
|
||||
endValues.getValue(bound)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.toTypedArray()
|
||||
|
||||
(view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel()
|
||||
|
||||
val animator = ObjectAnimator.ofPropertyValuesHolder(view, *propertyValuesHolders)
|
||||
animator.interpolator = interpolator
|
||||
animator.duration = duration
|
||||
animator.addListener(object : AnimatorListenerAdapter() {
|
||||
var cancelled = false
|
||||
animator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
var cancelled = false
|
||||
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
view.setTag(R.id.tag_animator, null /* tag */)
|
||||
bounds.forEach { view.setTag(it.overrideTag, null /* tag */) }
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
view.setTag(R.id.tag_animator, null /* tag */)
|
||||
bounds.forEach { view.setTag(it.overrideTag, null /* tag */) }
|
||||
|
||||
// When an animation is cancelled, a new one might be taking over. We shouldn't
|
||||
// unregister the listener yet.
|
||||
if (ephemeral && !cancelled) {
|
||||
// The duration is the same for the whole hierarchy, so it's safe to remove
|
||||
// the listener recursively. We do this because some descendant views might
|
||||
// not change bounds, and therefore not animate and leak the listener.
|
||||
recursivelyRemoveListener(view)
|
||||
// When an animation is cancelled, a new one might be taking over. We
|
||||
// shouldn't unregister the listener yet.
|
||||
if (ephemeral && !cancelled) {
|
||||
// The duration is the same for the whole hierarchy, so it's safe to
|
||||
// remove the listener recursively. We do this because some descendant
|
||||
// views might not change bounds, and therefore not animate and leak the
|
||||
// listener.
|
||||
recursivelyRemoveListener(view)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator?) {
|
||||
cancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animation: Animator?) {
|
||||
cancelled = true
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
bounds.forEach { bound -> setBound(view, bound, startValues.getValue(bound)) }
|
||||
|
||||
@@ -838,7 +915,15 @@ class ViewHierarchyAnimator {
|
||||
|
||||
/** An enum used to determine the origin of addition animations. */
|
||||
enum class Hotspot {
|
||||
CENTER, LEFT, TOP_LEFT, TOP, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT
|
||||
CENTER,
|
||||
LEFT,
|
||||
TOP_LEFT,
|
||||
TOP,
|
||||
TOP_RIGHT,
|
||||
RIGHT,
|
||||
BOTTOM_RIGHT,
|
||||
BOTTOM,
|
||||
BOTTOM_LEFT
|
||||
}
|
||||
|
||||
private enum class Bound(val label: String, val overrideTag: Int) {
|
||||
|
||||
@@ -11,37 +11,36 @@ object ViewRootSync {
|
||||
/**
|
||||
* Synchronize the next draw between the view roots of [view] and [otherView], then run [then].
|
||||
*
|
||||
* Note that in some cases, the synchronization might not be possible (e.g. WM consumed the
|
||||
* next transactions) or disabled (temporarily, on low ram devices). In this case, [then] will
|
||||
* be called without synchronizing.
|
||||
* Note that in some cases, the synchronization might not be possible (e.g. WM consumed the next
|
||||
* transactions) or disabled (temporarily, on low ram devices). In this case, [then] will be
|
||||
* called without synchronizing.
|
||||
*/
|
||||
fun synchronizeNextDraw(
|
||||
view: View,
|
||||
otherView: View,
|
||||
then: () -> Unit
|
||||
) {
|
||||
if (!view.isAttachedToWindow || view.viewRootImpl == null ||
|
||||
!otherView.isAttachedToWindow || otherView.viewRootImpl == null ||
|
||||
view.viewRootImpl == otherView.viewRootImpl) {
|
||||
fun synchronizeNextDraw(view: View, otherView: View, then: () -> Unit) {
|
||||
if (
|
||||
!view.isAttachedToWindow ||
|
||||
view.viewRootImpl == null ||
|
||||
!otherView.isAttachedToWindow ||
|
||||
otherView.viewRootImpl == null ||
|
||||
view.viewRootImpl == otherView.viewRootImpl
|
||||
) {
|
||||
// No need to synchronize if either the touch surface or dialog view is not attached
|
||||
// to a window.
|
||||
then()
|
||||
return
|
||||
}
|
||||
|
||||
surfaceSyncer = SurfaceSyncer().apply {
|
||||
val syncId = setupSync(Runnable { then() })
|
||||
addToSync(syncId, view)
|
||||
addToSync(syncId, otherView)
|
||||
markSyncReady(syncId)
|
||||
}
|
||||
surfaceSyncer =
|
||||
SurfaceSyncer().apply {
|
||||
val syncId = setupSync(Runnable { then() })
|
||||
addToSync(syncId, view)
|
||||
addToSync(syncId, otherView)
|
||||
markSyncReady(syncId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Java-friendly API for [synchronizeNextDraw].
|
||||
*/
|
||||
/** A Java-friendly API for [synchronizeNextDraw]. */
|
||||
@JvmStatic
|
||||
fun synchronizeNextDraw(view: View, otherView: View, then: Runnable) {
|
||||
synchronizeNextDraw(view, otherView, then::run)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user