Merge changes Ia5accb74,Ic4d7def4,I30bd56fc into sc-v2-dev am: 7339a87bf3
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16357603 Change-Id: I31d6bbad7f8c03f58ba8b6193aba944f9cdccbbf
This commit is contained in:
committed by
Automerger Merge Worker
commit
2b18ddbd1b
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright (C) 2021 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:pathData="M 0, 0 C 0.1217, 0.0462, 0.15, 0.4686, 0.1667, 0.66 C 0.1834, 0.8878, 0.1667, 1, 1, 1" />
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright (C) 2021 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1" />
|
|
||||||
@@ -21,6 +21,7 @@ import android.app.ActivityTaskManager
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.TaskInfo
|
import android.app.TaskInfo
|
||||||
import android.graphics.Matrix
|
import android.graphics.Matrix
|
||||||
|
import android.graphics.Path
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
@@ -34,6 +35,7 @@ import android.view.SyncRtSurfaceTransactionApplier
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.view.animation.Interpolator
|
||||||
import android.view.animation.PathInterpolator
|
import android.view.animation.PathInterpolator
|
||||||
import com.android.internal.annotations.VisibleForTesting
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
import com.android.internal.policy.ScreenDecorationsUtils
|
import com.android.internal.policy.ScreenDecorationsUtils
|
||||||
@@ -45,16 +47,46 @@ private const val TAG = "ActivityLaunchAnimator"
|
|||||||
* A class that allows activities to be started in a seamless way from a view that is transforming
|
* A class that allows activities to be started in a seamless way from a view that is transforming
|
||||||
* nicely into the starting window.
|
* nicely into the starting window.
|
||||||
*/
|
*/
|
||||||
class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
|
class ActivityLaunchAnimator(
|
||||||
|
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS)
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val TIMINGS = LaunchAnimator.Timings(
|
||||||
|
totalDuration = 500L,
|
||||||
|
contentBeforeFadeOutDelay = 0L,
|
||||||
|
contentBeforeFadeOutDuration = 150L,
|
||||||
|
contentAfterFadeInDelay = 150L,
|
||||||
|
contentAfterFadeInDuration = 183L
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
|
||||||
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
|
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
|
||||||
private const val ANIMATION_DELAY_NAV_FADE_IN =
|
private val ANIMATION_DELAY_NAV_FADE_IN =
|
||||||
LaunchAnimator.ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
|
TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN
|
||||||
|
|
||||||
|
private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE
|
||||||
|
private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
|
||||||
|
|
||||||
|
/** The time we wait before timing out the remote animation after starting the intent. */
|
||||||
private const val LAUNCH_TIMEOUT = 1000L
|
private const val LAUNCH_TIMEOUT = 1000L
|
||||||
|
|
||||||
private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
|
private fun createPositionXInterpolator(): Interpolator {
|
||||||
private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,8 +139,8 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
|
|||||||
val animationAdapter = if (!hideKeyguardWithAnimation) {
|
val animationAdapter = if (!hideKeyguardWithAnimation) {
|
||||||
RemoteAnimationAdapter(
|
RemoteAnimationAdapter(
|
||||||
runner,
|
runner,
|
||||||
LaunchAnimator.ANIMATION_DURATION,
|
TIMINGS.totalDuration,
|
||||||
LaunchAnimator.ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
|
TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
@@ -448,7 +480,7 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
|
|||||||
state: LaunchAnimator.State,
|
state: LaunchAnimator.State,
|
||||||
linearProgress: Float
|
linearProgress: Float
|
||||||
) {
|
) {
|
||||||
val fadeInProgress = LaunchAnimator.getProgress(linearProgress,
|
val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress,
|
||||||
ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
|
ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
|
||||||
|
|
||||||
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
|
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
|
||||||
@@ -463,7 +495,7 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
|
|||||||
.withWindowCrop(windowCrop)
|
.withWindowCrop(windowCrop)
|
||||||
.withVisibility(true)
|
.withVisibility(true)
|
||||||
} else {
|
} else {
|
||||||
val fadeOutProgress = LaunchAnimator.getProgress(linearProgress, 0,
|
val fadeOutProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, 0,
|
||||||
ANIMATION_DURATION_NAV_FADE_OUT)
|
ANIMATION_DURATION_NAV_FADE_OUT)
|
||||||
params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
|
params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import android.animation.Animator
|
|||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
@@ -28,10 +27,11 @@ import android.service.dreams.IDreamManager
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.MathUtils
|
import android.util.MathUtils
|
||||||
import android.view.GhostView
|
import android.view.GhostView
|
||||||
|
import android.view.SurfaceControl
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
import android.view.ViewTreeObserver.OnPreDrawListener
|
import android.view.ViewRootImpl
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -42,12 +42,20 @@ private const val TAG = "DialogLaunchAnimator"
|
|||||||
* A class that allows dialogs to be started in a seamless way from a view that is transforming
|
* A class that allows dialogs to be started in a seamless way from a view that is transforming
|
||||||
* nicely into the starting dialog.
|
* nicely into the starting dialog.
|
||||||
*/
|
*/
|
||||||
class DialogLaunchAnimator(
|
class DialogLaunchAnimator @JvmOverloads constructor(
|
||||||
private val context: Context,
|
private val dreamManager: IDreamManager,
|
||||||
private val launchAnimator: LaunchAnimator,
|
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
|
||||||
private val dreamManager: IDreamManager
|
private var isForTesting: Boolean = false
|
||||||
) {
|
) {
|
||||||
private companion object {
|
private companion object {
|
||||||
|
private val TIMINGS = ActivityLaunchAnimator.TIMINGS
|
||||||
|
|
||||||
|
// 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 TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running
|
private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,14 +104,14 @@ class DialogLaunchAnimator(
|
|||||||
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
|
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
|
||||||
|
|
||||||
val animatedDialog = AnimatedDialog(
|
val animatedDialog = AnimatedDialog(
|
||||||
context,
|
|
||||||
launchAnimator,
|
launchAnimator,
|
||||||
dreamManager,
|
dreamManager,
|
||||||
animateFrom,
|
animateFrom,
|
||||||
onDialogDismissed = { openedDialogs.remove(it) },
|
onDialogDismissed = { openedDialogs.remove(it) },
|
||||||
dialog = dialog,
|
dialog = dialog,
|
||||||
animateBackgroundBoundsChange,
|
animateBackgroundBoundsChange,
|
||||||
animatedParent
|
animatedParent,
|
||||||
|
isForTesting
|
||||||
)
|
)
|
||||||
|
|
||||||
openedDialogs.add(animatedDialog)
|
openedDialogs.add(animatedDialog)
|
||||||
@@ -157,7 +165,6 @@ class DialogLaunchAnimator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class AnimatedDialog(
|
private class AnimatedDialog(
|
||||||
private val context: Context,
|
|
||||||
private val launchAnimator: LaunchAnimator,
|
private val launchAnimator: LaunchAnimator,
|
||||||
private val dreamManager: IDreamManager,
|
private val dreamManager: IDreamManager,
|
||||||
|
|
||||||
@@ -174,10 +181,16 @@ private class AnimatedDialog(
|
|||||||
val dialog: Dialog,
|
val dialog: Dialog,
|
||||||
|
|
||||||
/** Whether we should animate the dialog background when its bounds change. */
|
/** Whether we should animate the dialog background when its bounds change. */
|
||||||
private val animateBackgroundBoundsChange: Boolean,
|
animateBackgroundBoundsChange: Boolean,
|
||||||
|
|
||||||
/** Launch animation corresponding to the parent [AnimatedDialog]. */
|
/** Launch animation corresponding to the parent [AnimatedDialog]. */
|
||||||
private val parentAnimatedDialog: AnimatedDialog? = null
|
private val parentAnimatedDialog: AnimatedDialog? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether we are currently running in a test, in which case we need to disable
|
||||||
|
* synchronization.
|
||||||
|
*/
|
||||||
|
private val isForTesting: Boolean
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* The DecorView of this dialog window.
|
* The DecorView of this dialog window.
|
||||||
@@ -266,14 +279,14 @@ private class AnimatedDialog(
|
|||||||
// and the view that we added so that we can dismiss the dialog when this view is
|
// 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
|
// clicked. This is necessary because DecorView overrides onTouchEvent and therefore we
|
||||||
// can't set the click listener directly on the (now fullscreen) DecorView.
|
// can't set the click listener directly on the (now fullscreen) DecorView.
|
||||||
val fullscreenTransparentBackground = FrameLayout(context)
|
val fullscreenTransparentBackground = FrameLayout(dialog.context)
|
||||||
decorView.addView(
|
decorView.addView(
|
||||||
fullscreenTransparentBackground,
|
fullscreenTransparentBackground,
|
||||||
0 /* index */,
|
0 /* index */,
|
||||||
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||||
)
|
)
|
||||||
|
|
||||||
val dialogContentWithBackground = FrameLayout(context)
|
val dialogContentWithBackground = FrameLayout(dialog.context)
|
||||||
dialogContentWithBackground.background = decorView.background
|
dialogContentWithBackground.background = decorView.background
|
||||||
|
|
||||||
// Make the window background transparent. Note that setting the window (or DecorView)
|
// Make the window background transparent. Note that setting the window (or DecorView)
|
||||||
@@ -365,59 +378,77 @@ private class AnimatedDialog(
|
|||||||
// Show the dialog.
|
// Show the dialog.
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
// Add a temporary touch surface ghost as soon as the window is ready to draw. This
|
addTouchSurfaceGhost()
|
||||||
// temporary ghost will be drawn together with the touch surface, but in the dialog
|
}
|
||||||
// window. Once it is drawn, we will make the touch surface invisible, and then start the
|
|
||||||
// animation. We do all this synchronization to avoid flicker that would occur if we made
|
private fun addTouchSurfaceGhost() {
|
||||||
// the touch surface invisible too early (before its ghost is drawn), leading to one or more
|
if (decorView.viewRootImpl == null) {
|
||||||
// frames with a hole instead of the touch surface (or its ghost).
|
// Make sure that we have access to the dialog view root to synchronize the creation of
|
||||||
decorView.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
|
// the ghost.
|
||||||
override fun onPreDraw(): Boolean {
|
decorView.post(::addTouchSurfaceGhost)
|
||||||
decorView.viewTreeObserver.removeOnPreDrawListener(this)
|
return
|
||||||
addTemporaryTouchSurfaceGhost()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
decorView.invalidate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addTemporaryTouchSurfaceGhost() {
|
|
||||||
// Create a ghost of the touch surface (which will make the touch surface invisible) and add
|
// Create a ghost of the touch surface (which will make the touch surface invisible) and add
|
||||||
// it to the dialog. We will wait for this ghost to be drawn before starting the animation.
|
// it to the host dialog. We trigger a one off synchronization to make sure that this is
|
||||||
val ghost = GhostView.addGhost(touchSurface, decorView)
|
// done in sync between the two different windows.
|
||||||
|
synchronizeNextDraw(then = {
|
||||||
// The ghost of the touch surface was just created, so the touch surface was made invisible.
|
|
||||||
// We make it visible again until the ghost is actually drawn.
|
|
||||||
touchSurface.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
// Wait for the ghost to be drawn before continuing.
|
|
||||||
ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
|
|
||||||
override fun onPreDraw(): Boolean {
|
|
||||||
ghost.viewTreeObserver.removeOnPreDrawListener(this)
|
|
||||||
onTouchSurfaceGhostDrawn()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
ghost.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onTouchSurfaceGhostDrawn() {
|
|
||||||
// Make the touch surface invisible and make sure that it stays invisible as long as the
|
|
||||||
// dialog is shown or animating.
|
|
||||||
touchSurface.visibility = View.INVISIBLE
|
|
||||||
(touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
|
|
||||||
|
|
||||||
// Add a pre draw listener to (maybe) start the animation once the touch surface is
|
|
||||||
// actually invisible.
|
|
||||||
touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
|
|
||||||
override fun onPreDraw(): Boolean {
|
|
||||||
touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
|
|
||||||
isTouchSurfaceGhostDrawn = true
|
isTouchSurfaceGhostDrawn = true
|
||||||
maybeStartLaunchAnimation()
|
maybeStartLaunchAnimation()
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
touchSurface.invalidate()
|
GhostView.addGhost(touchSurface, decorView)
|
||||||
|
|
||||||
|
// The ghost of the touch surface was just created, so the touch surface is currently
|
||||||
|
// invisible. We need to make sure that it stays invisible as long as the dialog is shown or
|
||||||
|
// animating.
|
||||||
|
(touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize the next draw of the touch surface and dialog view roots so that they are
|
||||||
|
* performed at the same time, in the same transaction. This is necessary to make sure that the
|
||||||
|
* ghost of the touch surface is drawn at the same time as the touch surface is made invisible
|
||||||
|
* (or inversely, removed from the UI when the touch surface is made visible).
|
||||||
|
*/
|
||||||
|
private fun synchronizeNextDraw(then: () -> Unit) {
|
||||||
|
if (isForTesting || !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null ||
|
||||||
|
!decorView.isAttachedToWindow || decorView.viewRootImpl == null) {
|
||||||
|
// No need to synchronize if either the touch surface or dialog view is not attached
|
||||||
|
// to a window.
|
||||||
|
then()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the next frames of both view roots to make sure the ghost view is drawn at
|
||||||
|
// exactly the same time as when the touch surface is made invisible.
|
||||||
|
var remainingTransactions = 0
|
||||||
|
val mergedTransactions = SurfaceControl.Transaction()
|
||||||
|
|
||||||
|
fun onTransaction(transaction: SurfaceControl.Transaction?) {
|
||||||
|
remainingTransactions--
|
||||||
|
transaction?.let { mergedTransactions.merge(it) }
|
||||||
|
|
||||||
|
if (remainingTransactions == 0) {
|
||||||
|
mergedTransactions.apply()
|
||||||
|
then()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun consumeNextDraw(viewRootImpl: ViewRootImpl) {
|
||||||
|
if (viewRootImpl.consumeNextDraw(::onTransaction)) {
|
||||||
|
remainingTransactions++
|
||||||
|
|
||||||
|
// Make sure we trigger a traversal.
|
||||||
|
viewRootImpl.view.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeNextDraw(touchSurface.viewRootImpl)
|
||||||
|
consumeNextDraw(decorView.viewRootImpl)
|
||||||
|
|
||||||
|
if (remainingTransactions == 0) {
|
||||||
|
then()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
|
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
|
||||||
@@ -483,7 +514,7 @@ private class AnimatedDialog(
|
|||||||
|
|
||||||
private fun onDialogDismissed() {
|
private fun onDialogDismissed() {
|
||||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
context.mainExecutor.execute { onDialogDismissed() }
|
dialog.context.mainExecutor.execute { onDialogDismissed() }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,25 +587,12 @@ private class AnimatedDialog(
|
|||||||
.removeOnLayoutChangeListener(backgroundLayoutListener)
|
.removeOnLayoutChangeListener(backgroundLayoutListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The animated ghost was just removed. We create a temporary ghost that will be
|
// Make sure that the removal of the ghost and making the touch surface visible is
|
||||||
// removed only once we draw the touch surface, to avoid flickering that would
|
// done at the same time.
|
||||||
// happen when removing the ghost too early (before the touch surface is drawn).
|
synchronizeNextDraw(then = {
|
||||||
GhostView.addGhost(touchSurface, decorView)
|
|
||||||
|
|
||||||
touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
|
|
||||||
override fun onPreDraw(): Boolean {
|
|
||||||
touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
|
|
||||||
|
|
||||||
// Now that the touch surface was drawn, we can remove the temporary ghost
|
|
||||||
// and instantly dismiss the dialog.
|
|
||||||
GhostView.removeGhost(touchSurface)
|
|
||||||
onAnimationFinished(true /* instantDismiss */)
|
onAnimationFinished(true /* instantDismiss */)
|
||||||
onDialogDismissed(this@AnimatedDialog)
|
onDialogDismissed(this@AnimatedDialog)
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
touchSurface.invalidate()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,25 +27,19 @@ import android.util.Log
|
|||||||
import android.util.MathUtils
|
import android.util.MathUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.animation.AnimationUtils
|
import android.view.animation.Interpolator
|
||||||
import android.view.animation.PathInterpolator
|
import com.android.systemui.animation.Interpolators.LINEAR
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private const val TAG = "LaunchAnimator"
|
private const val TAG = "LaunchAnimator"
|
||||||
|
|
||||||
/** A base class to animate a window launch (activity or dialog) from a view . */
|
/** A base class to animate a window launch (activity or dialog) from a view . */
|
||||||
class LaunchAnimator @JvmOverloads constructor(
|
class LaunchAnimator(
|
||||||
context: Context,
|
private val timings: Timings,
|
||||||
private val isForTesting: Boolean = false
|
private val interpolators: Interpolators
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
internal const val DEBUG = false
|
internal const val DEBUG = false
|
||||||
const val ANIMATION_DURATION = 500L
|
|
||||||
private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
|
|
||||||
private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
|
|
||||||
private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
|
|
||||||
|
|
||||||
private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f)
|
|
||||||
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
|
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,23 +47,20 @@ class LaunchAnimator @JvmOverloads constructor(
|
|||||||
* sub-animation starting [delay] ms after the launch animation and that lasts [duration].
|
* sub-animation starting [delay] ms after the launch animation and that lasts [duration].
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float {
|
fun getProgress(
|
||||||
|
timings: Timings,
|
||||||
|
linearProgress: Float,
|
||||||
|
delay: Long,
|
||||||
|
duration: Long
|
||||||
|
): Float {
|
||||||
return MathUtils.constrain(
|
return MathUtils.constrain(
|
||||||
(linearProgress * ANIMATION_DURATION - delay) / duration,
|
(linearProgress * timings.totalDuration - delay) / duration,
|
||||||
0.0f,
|
0.0f,
|
||||||
1.0f
|
1.0f
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The interpolator used for the width, height, Y position and corner radius. */
|
|
||||||
private val animationInterpolator = AnimationUtils.loadInterpolator(context,
|
|
||||||
R.interpolator.launch_animation_interpolator_y)
|
|
||||||
|
|
||||||
/** The interpolator used for the X position. */
|
|
||||||
private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
|
|
||||||
R.interpolator.launch_animation_interpolator_x)
|
|
||||||
|
|
||||||
private val launchContainerLocation = IntArray(2)
|
private val launchContainerLocation = IntArray(2)
|
||||||
private val cornerRadii = FloatArray(8)
|
private val cornerRadii = FloatArray(8)
|
||||||
|
|
||||||
@@ -159,6 +150,45 @@ class LaunchAnimator @JvmOverloads constructor(
|
|||||||
fun cancel()
|
fun cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The timings (durations and delays) used by this animator. */
|
||||||
|
class Timings(
|
||||||
|
/** The total duration of the animation. */
|
||||||
|
val totalDuration: Long,
|
||||||
|
|
||||||
|
/** The time to wait before fading out the expanding content. */
|
||||||
|
val contentBeforeFadeOutDelay: Long,
|
||||||
|
|
||||||
|
/** The duration of the expanding content fade out. */
|
||||||
|
val contentBeforeFadeOutDuration: Long,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time to wait before fading in the expanded content (usually an activity or dialog
|
||||||
|
* window).
|
||||||
|
*/
|
||||||
|
val contentAfterFadeInDelay: Long,
|
||||||
|
|
||||||
|
/** The duration of the expanded content fade in. */
|
||||||
|
val contentAfterFadeInDuration: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
/** The interpolators used by this animator. */
|
||||||
|
data class Interpolators(
|
||||||
|
/** The interpolator used for the Y position, width, height and corner radius. */
|
||||||
|
val positionInterpolator: Interpolator,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interpolator used for the X position. This can be different than
|
||||||
|
* [positionInterpolator] to create an arc-path during the animation.
|
||||||
|
*/
|
||||||
|
val positionXInterpolator: Interpolator = positionInterpolator,
|
||||||
|
|
||||||
|
/** The interpolator used when fading out the expanding content. */
|
||||||
|
val contentBeforeFadeOutInterpolator: Interpolator,
|
||||||
|
|
||||||
|
/** The interpolator used when fading in the expanded content. */
|
||||||
|
val contentAfterFadeInInterpolator: Interpolator
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a launch animation controlled by [controller] towards [endState]. An intermediary
|
* 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
|
* layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and
|
||||||
@@ -221,8 +251,8 @@ class LaunchAnimator @JvmOverloads constructor(
|
|||||||
|
|
||||||
// Update state.
|
// Update state.
|
||||||
val animator = ValueAnimator.ofFloat(0f, 1f)
|
val animator = ValueAnimator.ofFloat(0f, 1f)
|
||||||
animator.duration = if (isForTesting) 0 else ANIMATION_DURATION
|
animator.duration = timings.totalDuration
|
||||||
animator.interpolator = Interpolators.LINEAR
|
animator.interpolator = LINEAR
|
||||||
|
|
||||||
val launchContainerOverlay = launchContainer.overlay
|
val launchContainerOverlay = launchContainer.overlay
|
||||||
var cancelled = false
|
var cancelled = false
|
||||||
@@ -260,8 +290,8 @@ class LaunchAnimator @JvmOverloads constructor(
|
|||||||
// TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non
|
// TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non
|
||||||
// reversed animation.
|
// reversed animation.
|
||||||
val linearProgress = animation.animatedFraction
|
val linearProgress = animation.animatedFraction
|
||||||
val progress = animationInterpolator.getInterpolation(linearProgress)
|
val progress = interpolators.positionInterpolator.getInterpolation(linearProgress)
|
||||||
val xProgress = animationInterpolatorX.getInterpolation(linearProgress)
|
val xProgress = interpolators.positionXInterpolator.getInterpolation(linearProgress)
|
||||||
|
|
||||||
val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress)
|
val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress)
|
||||||
val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f
|
val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f
|
||||||
@@ -278,7 +308,12 @@ class LaunchAnimator @JvmOverloads constructor(
|
|||||||
|
|
||||||
// The expanding view can/should be hidden once it is completely covered by the opening
|
// The expanding view can/should be hidden once it is completely covered by the opening
|
||||||
// window.
|
// window.
|
||||||
state.visible = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
|
state.visible = getProgress(
|
||||||
|
timings,
|
||||||
|
linearProgress,
|
||||||
|
timings.contentBeforeFadeOutDelay,
|
||||||
|
timings.contentBeforeFadeOutDuration
|
||||||
|
) < 1
|
||||||
|
|
||||||
applyStateToWindowBackgroundLayer(
|
applyStateToWindowBackgroundLayer(
|
||||||
windowBackgroundLayer,
|
windowBackgroundLayer,
|
||||||
@@ -337,14 +372,25 @@ class LaunchAnimator @JvmOverloads constructor(
|
|||||||
|
|
||||||
// We first fade in the background layer to hide the expanding view, then fade it out
|
// 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.
|
// with SRC mode to draw a hole punch in the status bar and reveal the opening window.
|
||||||
val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
|
val fadeInProgress = getProgress(
|
||||||
|
timings,
|
||||||
|
linearProgress,
|
||||||
|
timings.contentBeforeFadeOutDelay,
|
||||||
|
timings.contentBeforeFadeOutDuration
|
||||||
|
)
|
||||||
if (fadeInProgress < 1) {
|
if (fadeInProgress < 1) {
|
||||||
val alpha = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(fadeInProgress)
|
val alpha =
|
||||||
|
interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
|
||||||
drawable.alpha = (alpha * 0xFF).roundToInt()
|
drawable.alpha = (alpha * 0xFF).roundToInt()
|
||||||
} else {
|
} else {
|
||||||
val fadeOutProgress = getProgress(
|
val fadeOutProgress = getProgress(
|
||||||
linearProgress, ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
|
timings,
|
||||||
val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
|
linearProgress,
|
||||||
|
timings.contentAfterFadeInDelay,
|
||||||
|
timings.contentAfterFadeInDuration
|
||||||
|
)
|
||||||
|
val alpha =
|
||||||
|
1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress)
|
||||||
drawable.alpha = (alpha * 0xFF).roundToInt()
|
drawable.alpha = (alpha * 0xFF).roundToInt()
|
||||||
|
|
||||||
if (drawHole) {
|
if (drawHole) {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import android.service.dreams.IDreamManager;
|
|||||||
import com.android.internal.statusbar.IStatusBarService;
|
import com.android.internal.statusbar.IStatusBarService;
|
||||||
import com.android.systemui.animation.ActivityLaunchAnimator;
|
import com.android.systemui.animation.ActivityLaunchAnimator;
|
||||||
import com.android.systemui.animation.DialogLaunchAnimator;
|
import com.android.systemui.animation.DialogLaunchAnimator;
|
||||||
import com.android.systemui.animation.LaunchAnimator;
|
|
||||||
import com.android.systemui.dagger.SysUISingleton;
|
import com.android.systemui.dagger.SysUISingleton;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
import com.android.systemui.dump.DumpManager;
|
import com.android.systemui.dump.DumpManager;
|
||||||
@@ -301,24 +300,15 @@ public interface StatusBarDependenciesModule {
|
|||||||
*/
|
*/
|
||||||
@Provides
|
@Provides
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
static LaunchAnimator provideLaunchAnimator(Context context) {
|
static ActivityLaunchAnimator provideActivityLaunchAnimator() {
|
||||||
return new LaunchAnimator(context);
|
return new ActivityLaunchAnimator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@Provides
|
@Provides
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
static ActivityLaunchAnimator provideActivityLaunchAnimator(LaunchAnimator launchAnimator) {
|
static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager) {
|
||||||
return new ActivityLaunchAnimator(launchAnimator);
|
return new DialogLaunchAnimator(dreamManager);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
@Provides
|
|
||||||
@SysUISingleton
|
|
||||||
static DialogLaunchAnimator provideDialogLaunchAnimator(Context context,
|
|
||||||
LaunchAnimator launchAnimator, IDreamManager dreamManager) {
|
|
||||||
return new DialogLaunchAnimator(context, launchAnimator, dreamManager);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification
|
|||||||
|
|
||||||
import android.util.MathUtils
|
import android.util.MathUtils
|
||||||
import com.android.internal.annotations.VisibleForTesting
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
import com.android.systemui.animation.Interpolators
|
import com.android.systemui.animation.Interpolators
|
||||||
import com.android.systemui.animation.LaunchAnimator
|
import com.android.systemui.animation.LaunchAnimator
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -55,6 +56,7 @@ class ExpandAnimationParameters(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getProgress(delay: Long, duration: Long): Float {
|
fun getProgress(delay: Long, duration: Long): Float {
|
||||||
return LaunchAnimator.getProgress(linearProgress, delay, duration)
|
return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
|
||||||
|
duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,6 +110,7 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
|
|||||||
import com.android.systemui.DejankUtils;
|
import com.android.systemui.DejankUtils;
|
||||||
import com.android.systemui.Dependency;
|
import com.android.systemui.Dependency;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator;
|
||||||
import com.android.systemui.animation.Interpolators;
|
import com.android.systemui.animation.Interpolators;
|
||||||
import com.android.systemui.animation.LaunchAnimator;
|
import com.android.systemui.animation.LaunchAnimator;
|
||||||
import com.android.systemui.biometrics.AuthController;
|
import com.android.systemui.biometrics.AuthController;
|
||||||
@@ -225,7 +226,8 @@ public class NotificationPanelViewController extends PanelViewController {
|
|||||||
*/
|
*/
|
||||||
private static final int FLING_HIDE = 2;
|
private static final int FLING_HIDE = 2;
|
||||||
private static final long ANIMATION_DELAY_ICON_FADE_IN =
|
private static final long ANIMATION_DELAY_ICON_FADE_IN =
|
||||||
LaunchAnimator.ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION
|
ActivityLaunchAnimator.TIMINGS.getTotalDuration()
|
||||||
|
- CollapsedStatusBarFragment.FADE_IN_DURATION
|
||||||
- CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
|
- CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
|
||||||
|
|
||||||
private final DozeParameters mDozeParameters;
|
private final DozeParameters mDozeParameters;
|
||||||
@@ -3629,8 +3631,8 @@ public class NotificationPanelViewController extends PanelViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void applyLaunchAnimationProgress(float linearProgress) {
|
public void applyLaunchAnimationProgress(float linearProgress) {
|
||||||
boolean hideIcons = LaunchAnimator.getProgress(linearProgress,
|
boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
|
||||||
ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
|
linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
|
||||||
if (hideIcons != mHideIconsDuringLaunchAnimation) {
|
if (hideIcons != mHideIconsDuringLaunchAnimation) {
|
||||||
mHideIconsDuringLaunchAnimation = hideIcons;
|
mHideIconsDuringLaunchAnimation = hideIcons;
|
||||||
if (!hideIcons) {
|
if (!hideIcons) {
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class StatusBarLaunchAnimatorController(
|
|||||||
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
|
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
|
||||||
statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true)
|
statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true)
|
||||||
if (!isExpandingFullyAbove) {
|
if (!isExpandingFullyAbove) {
|
||||||
statusBar.collapsePanelWithDuration(LaunchAnimator.ANIMATION_DURATION.toInt())
|
statusBar.collapsePanelWithDuration(
|
||||||
|
ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ import org.mockito.junit.MockitoJUnit
|
|||||||
@RunWithLooper
|
@RunWithLooper
|
||||||
class ActivityLaunchAnimatorTest : SysuiTestCase() {
|
class ActivityLaunchAnimatorTest : SysuiTestCase() {
|
||||||
private val launchContainer = LinearLayout(mContext)
|
private val launchContainer = LinearLayout(mContext)
|
||||||
private val launchAnimator = LaunchAnimator(mContext, isForTesting = true)
|
private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
|
||||||
@Mock lateinit var callback: ActivityLaunchAnimator.Callback
|
@Mock lateinit var callback: ActivityLaunchAnimator.Callback
|
||||||
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
|
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
|
||||||
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
|
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import org.mockito.junit.MockitoJUnit
|
|||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@TestableLooper.RunWithLooper
|
@TestableLooper.RunWithLooper
|
||||||
class DialogLaunchAnimatorTest : SysuiTestCase() {
|
class DialogLaunchAnimatorTest : SysuiTestCase() {
|
||||||
private val launchAnimator = LaunchAnimator(context, isForTesting = true)
|
private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
|
||||||
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
|
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
|
||||||
private val attachedViews = mutableSetOf<View>()
|
private val attachedViews = mutableSetOf<View>()
|
||||||
|
|
||||||
@@ -42,7 +42,8 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
dialogLaunchAnimator = DialogLaunchAnimator(context, launchAnimator, dreamManager)
|
dialogLaunchAnimator = DialogLaunchAnimator(
|
||||||
|
dreamManager, launchAnimator, isForTesting = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.android.systemui.animation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [LaunchAnimator.Timings] to be used in tests.
|
||||||
|
*
|
||||||
|
* Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions
|
||||||
|
* when computing the progress of a sub-animation (the contents fade in/out).
|
||||||
|
*/
|
||||||
|
val TEST_TIMINGS = LaunchAnimator.Timings(
|
||||||
|
totalDuration = 0L,
|
||||||
|
contentBeforeFadeOutDelay = 1L,
|
||||||
|
contentBeforeFadeOutDuration = 1L,
|
||||||
|
contentAfterFadeInDelay = 1L,
|
||||||
|
contentAfterFadeInDuration = 1L
|
||||||
|
)
|
||||||
|
|
||||||
|
/** A [LaunchAnimator.Interpolators] to be used in tests. */
|
||||||
|
val TEST_INTERPOLATORS = LaunchAnimator.Interpolators(
|
||||||
|
positionInterpolator = Interpolators.STANDARD,
|
||||||
|
positionXInterpolator = Interpolators.STANDARD,
|
||||||
|
contentBeforeFadeOutInterpolator = Interpolators.STANDARD,
|
||||||
|
contentAfterFadeInInterpolator = Interpolators.STANDARD
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user