Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 425cd9ea authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Automerger Merge Worker
Browse files

Merge "Revisit how launch animations handle View visibility" into tm-qpr-dev...

Merge "Revisit how launch animations handle View visibility" into tm-qpr-dev am: 9758d71b am: 494beb04

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20946885



Change-Id: I303fde333547277a888a122c92b77b90872f40ac
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 5a9f5b60 494beb04
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -791,13 +791,13 @@ private class AnimatedDialog(
        // Move the drawing of the source in the overlay of this dialog, then animate. We trigger a
        // one-off synchronization to make sure that this is done in sync between the two different
        // windows.
        controller.startDrawingInOverlayOf(decorView)
        synchronizeNextDraw(
            then = {
                isSourceDrawnInDialog = true
                maybeStartLaunchAnimation()
            }
        )
        controller.startDrawingInOverlayOf(decorView)
    }

    /**
+17 −10
Original line number Diff line number Diff line
@@ -195,14 +195,16 @@ open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
        backgroundDrawable = WrappedDrawable(background)
        backgroundView?.background = backgroundDrawable

        // Delay the calls to `ghostedView.setVisibility()` during the animation. This must be
        // called before `GhostView.addGhost()` is called because the latter will change the
        // *transition* visibility, which won't be blocked and will affect the normal View
        // visibility that is saved by `setShouldBlockVisibilityChanges()` for a later restoration.
        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)

        // Create a ghost of the view that will be moving and fading out. This allows to fade out
        // the content before fading out the background.
        ghostView = GhostView.addGhost(ghostedView, launchContainer)

        // The ghost was just created, so ghostedView is currently invisible. We need to make sure
        // that it stays invisible as long as we are animating.
        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(true)

        val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
        matrix.getValues(initialGhostViewMatrixValues)

@@ -297,15 +299,20 @@ open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
        backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha

        GhostView.removeGhost(ghostedView)
        (ghostedView as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
        launchContainerOverlay.remove(backgroundView)

        // Make sure that the view is considered VISIBLE by accessibility by first making it
        // INVISIBLE then VISIBLE (see b/204944038#comment17 for more info).
        if (ghostedView is LaunchableView) {
            // Restore the ghosted view visibility.
            ghostedView.setShouldBlockVisibilityChanges(false)
        } else {
            // Make the ghosted view visible. We ensure that the view is considered VISIBLE by
            // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
            // for more info).
            ghostedView.visibility = View.INVISIBLE
            ghostedView.visibility = View.VISIBLE
            ghostedView.invalidate()
        }
    }

    companion object {
        private const val CORNER_RADIUS_TOP_INDEX = 0
+21 −25
Original line number Diff line number Diff line
@@ -21,15 +21,19 @@ import android.view.View
/** A view that can expand/launch into an app or a dialog. */
interface LaunchableView {
    /**
     * Set whether this view should block/postpone all visibility changes. This ensures that this
     * view:
     * Set whether this view should block/postpone all calls to [View.setVisibility]. This ensures
     * that this view:
     * - remains invisible during the launch animation given that it is ghosted and already drawn
     * somewhere else.
     * - remains invisible as long as a dialog expanded from it is shown.
     * - restores its expected visibility once the dialog expanded from it is dismissed.
     *
     * Note that when this is set to true, both the [normal][android.view.View.setVisibility] and
     * [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
     * When `setShouldBlockVisibilityChanges(false)` is called, then visibility of the View should
     * be restored to its expected value, i.e. it should have the visibility of the last call to
     * `View.setVisibility()` that was made after `setShouldBlockVisibilityChanges(true)`, if any,
     * or the original view visibility otherwise.
     *
     * Note that calls to [View.setTransitionVisibility] shouldn't be blocked.
     *
     * @param block whether we should block/postpone all calls to `setVisibility` and
     * `setTransitionVisibility`.
@@ -46,29 +50,33 @@ class LaunchableViewDelegate(
     * super.setVisibility(visibility).
     */
    private val superSetVisibility: (Int) -> Unit,

    /**
     * The lambda that should set the actual transition visibility of [view], usually by calling
     * super.setTransitionVisibility(visibility).
     */
    private val superSetTransitionVisibility: (Int) -> Unit,
) {
) : LaunchableView {
    private var blockVisibilityChanges = false
    private var lastVisibility = view.visibility

    /** Call this when [LaunchableView.setShouldBlockVisibilityChanges] is called. */
    fun setShouldBlockVisibilityChanges(block: Boolean) {
    override fun setShouldBlockVisibilityChanges(block: Boolean) {
        if (block == blockVisibilityChanges) {
            return
        }

        blockVisibilityChanges = block
        if (block) {
            // Save the current visibility for later.
            lastVisibility = view.visibility
        } else {
            // Restore the visibility. To avoid accessibility issues, we change the visibility twice
            // which makes sure that we trigger a visibility flag change (see b/204944038#comment17
            // for more info).
            if (lastVisibility == View.VISIBLE) {
                superSetVisibility(View.INVISIBLE)
                superSetVisibility(View.VISIBLE)
            } else {
                superSetVisibility(View.VISIBLE)
                superSetVisibility(lastVisibility)
            }
        }
    }

    /** Call this when [View.setVisibility] is called. */
    fun setVisibility(visibility: Int) {
@@ -79,16 +87,4 @@ class LaunchableViewDelegate(

        superSetVisibility(visibility)
    }

    /** Call this when [View.setTransitionVisibility] is called. */
    fun setTransitionVisibility(visibility: Int) {
        if (blockVisibilityChanges) {
            // View.setTransitionVisibility just sets the visibility flag, so we don't have to save
            // the transition visibility separately from the normal visibility.
            lastVisibility = visibility
            return
        }

        superSetTransitionVisibility(visibility)
    }
}
+33 −21
Original line number Diff line number Diff line
@@ -34,24 +34,30 @@ internal constructor(
    override val sourceIdentity: Any = source

    override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
        // Delay the calls to `source.setVisibility()` during the animation. This must be called
        // before `GhostView.addGhost()` is called because the latter will change the *transition*
        // visibility, which won't be blocked and will affect the normal View visibility that is
        // saved by `setShouldBlockVisibilityChanges()` for a later restoration.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)

        // Create a temporary ghost of the source (which will make it invisible) and add it
        // to the host dialog.
        GhostView.addGhost(source, viewGroup)

        // The ghost of the source was just created, so the source is currently invisible.
        // We need to make sure that it stays invisible as long as the dialog is shown or
        // animating.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
    }

    override fun stopDrawingInOverlay() {
        // Note: here we should remove the ghost from the overlay, but in practice this is
        // already done by the launch controllers created below.

        // Make sure we allow the source to change its visibility again.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
        // already done by the launch controller created below.

        if (source is LaunchableView) {
            // Make sure we allow the source to change its visibility again and restore its previous
            // value.
            source.setShouldBlockVisibilityChanges(false)
        } else {
            // We made the source invisible earlier, so let's make it visible again.
            source.visibility = View.VISIBLE
        }
    }

    override fun createLaunchController(): LaunchAnimator.Controller {
        val delegate = GhostedViewLaunchAnimatorController(source)
@@ -67,13 +73,17 @@ internal constructor(
            override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
                delegate.onLaunchAnimationEnd(isExpandingFullyAbove)

                // We hide the source when the dialog is showing. We will make this view
                // visible again when dismissing the dialog. This does nothing if the source
                // implements [LaunchableView], as it's already INVISIBLE in that case.
                // At this point the view visibility is restored by the delegate, so we delay the
                // visibility changes again and make it invisible while the dialog is shown.
                if (source is LaunchableView) {
                    source.setShouldBlockVisibilityChanges(true)
                    source.setTransitionVisibility(View.INVISIBLE)
                } else {
                    source.visibility = View.INVISIBLE
                }
            }
        }
    }

    override fun createExitController(): LaunchAnimator.Controller {
        return GhostedViewLaunchAnimatorController(source)
@@ -90,15 +100,17 @@ internal constructor(
    }

    override fun onExitAnimationCancelled() {
        if (source is LaunchableView) {
            // Make sure we allow the source to change its visibility again.
        (source as? LaunchableView)?.setShouldBlockVisibilityChanges(false)

            source.setShouldBlockVisibilityChanges(false)
        } else {
            // If the view is invisible it's probably because of us, so we make it visible
            // again.
            if (source.visibility == View.INVISIBLE) {
                source.visibility = View.VISIBLE
            }
        }
    }

    override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
        val type = cuj?.cujType ?: return null
+0 −5
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ class LaunchableImageView : ImageView, LaunchableView {
        LaunchableViewDelegate(
            this,
            superSetVisibility = { super.setVisibility(it) },
            superSetTransitionVisibility = { super.setTransitionVisibility(it) },
        )

    constructor(context: Context?) : super(context)
@@ -53,8 +52,4 @@ class LaunchableImageView : ImageView, LaunchableView {
    override fun setVisibility(visibility: Int) {
        delegate.setVisibility(visibility)
    }

    override fun setTransitionVisibility(visibility: Int) {
        delegate.setTransitionVisibility(visibility)
    }
}
Loading