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

Commit f928f5b2 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Wrap animated dialog view inside another view

Before this CL, when animating a dialog from a View, we would extract
the original dialog Window background and set it as the background of
the original dialog content View. This would cause problems if that
original dialog content View had some paddings and if the background had
insets, as setting the View background would mess up the View paddings.

This CL now adds an additional View between the host dialog root and the
original dialog content Wiew. We set the original dialog Window
background to this additional View, so that the original dialog View
remains unchanged.

Bug: 193634619
Test: atest DialogLaunchAnimatorTest
Test: Manual, launch the User Switcher dialog and observe that the
      paddings are correct.
Change-Id: I64e688431c2d205f0cf200b9debfe195dc3e2364
parent 2642eed1
Loading
Loading
Loading
Loading
+36 −23
Original line number Diff line number Diff line
@@ -221,10 +221,11 @@ private class DialogLaunchAnimation(
    private val hostDialogRoot = FrameLayout(context)

    /**
     * The content view of [originalDialog], which will be stolen from that dialog and added to
     * [hostDialogRoot].
     * The parent of the original dialog content view, that serves as a fake window that will have
     * the same size as the original dialog window and to which we will set the original dialog
     * window background.
     */
    private var originalDialogView: View? = null
    private val dialogContentParent = FrameLayout(context)

    /**
     * The background color of [originalDialogView], taking into consideration the [originalDialog]
@@ -374,9 +375,6 @@ private class DialogLaunchAnimation(
    }

    private fun showDialogFromView(dialogView: View) {
        // Save the dialog view for later as we will need it for the close animation.
        this.originalDialogView = dialogView

        // Close the dialog when clicking outside of it.
        hostDialogRoot.setOnClickListener { hostDialog.dismiss() }
        dialogView.isClickable = true
@@ -394,29 +392,46 @@ private class DialogLaunchAnimation(
            throw IllegalStateException("Dialogs with no backgrounds on window are not supported")
        }

        dialogView.setBackgroundResource(backgroundRes)
        // Add a parent view to the original dialog view to which we will set the original dialog
        // window background. This View serves as a fake window with background, so that we are sure
        // that we don't override the dialog view paddings with the window background that usually
        // has insets.
        dialogContentParent.setBackgroundResource(backgroundRes)
        hostDialogRoot.addView(
            dialogContentParent,

            // We give it the size of its original dialog window.
            FrameLayout.LayoutParams(
                originalDialog.window.attributes.width,
                originalDialog.window.attributes.height,
                Gravity.CENTER
            )
        )

        // Make the dialog view parent invisible for now, to make sure it's not drawn yet.
        dialogContentParent.visibility = View.INVISIBLE

        val background = dialogContentParent.background!!
        originalDialogBackgroundColor =
            GhostedViewLaunchAnimatorController.findGradientDrawable(dialogView.background!!)
            GhostedViewLaunchAnimatorController.findGradientDrawable(background)
                ?.color
                ?.defaultColor ?: Color.BLACK

        // Add the dialog view to the host (fullscreen) dialog and make it invisible to make sure
        // it's not drawn yet.
        // Add the dialog view to its parent (that has the original window background).
        (dialogView.parent as? ViewGroup)?.removeView(dialogView)
        hostDialogRoot.addView(
        dialogContentParent.addView(
            dialogView,

            // We give it the size of its original dialog window.
            // It should match its parent size, which is sized the same as the original dialog
            // window.
            FrameLayout.LayoutParams(
                originalDialog.window.attributes.width,
                originalDialog.window.attributes.height,
                Gravity.CENTER
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
        )
        dialogView.visibility = View.INVISIBLE

        // Start the animation when the dialog is laid out in the center of the host dialog.
        dialogView.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
        dialogContentParent.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
            override fun onLayoutChange(
                view: View,
                left: Int,
@@ -428,7 +443,7 @@ private class DialogLaunchAnimation(
                oldRight: Int,
                oldBottom: Int
            ) {
                dialogView.removeOnLayoutChangeListener(this)
                dialogContentParent.removeOnLayoutChangeListener(this)

                isOriginalDialogViewLaidOut = true
                maybeStartLaunchAnimation()
@@ -548,7 +563,7 @@ private class DialogLaunchAnimation(
                }

                touchSurface.visibility = View.VISIBLE
                originalDialogView!!.visibility = View.INVISIBLE
                dialogContentParent.visibility = View.INVISIBLE

                // The animated ghost was just removed. We create a temporary ghost that will be
                // removed only once we draw the touch surface, to avoid flickering that would
@@ -578,12 +593,10 @@ private class DialogLaunchAnimation(
        onLaunchAnimationStart: () -> Unit = {},
        onLaunchAnimationEnd: () -> Unit = {}
    ) {
        val dialogView = this.originalDialogView!!

        // Create 2 ghost controllers to animate both the dialog and the touch surface in the host
        // dialog.
        val startView = if (isLaunching) touchSurface else dialogView
        val endView = if (isLaunching) dialogView else touchSurface
        val startView = if (isLaunching) touchSurface else dialogContentParent
        val endView = if (isLaunching) dialogContentParent else touchSurface
        val startViewController = GhostedViewLaunchAnimatorController(startView)
        val endViewController = GhostedViewLaunchAnimatorController(endView)
        startViewController.launchContainer = hostDialogRoot
+62 −66
Original line number Diff line number Diff line
@@ -16,12 +16,9 @@
  ~ limitations under the License.
  -->

<FrameLayout
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sysui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="24dp"
@@ -90,4 +87,3 @@
        />

</androidx.constraintlayout.widget.ConstraintLayout>
 No newline at end of file
</FrameLayout>
 No newline at end of file
+18 −1
Original line number Diff line number Diff line
@@ -60,9 +60,20 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
        assertEquals(0, dialog.findViewById<ViewGroup>(android.R.id.content).childCount)
        assertEquals(1, hostDialogContent.childCount)

        // The original dialog content is added to another view that is the same size as the
        // original dialog window.
        val hostDialogRoot = hostDialogContent.getChildAt(0) as ViewGroup
        assertEquals(1, hostDialogRoot.childCount)
        assertEquals(dialog.contentView, hostDialogRoot.getChildAt(0))

        val dialogContentParent = hostDialogRoot.getChildAt(0) as ViewGroup
        assertEquals(1, dialogContentParent.childCount)
        assertEquals(TestDialog.DIALOG_WIDTH, dialogContentParent.layoutParams.width)
        assertEquals(TestDialog.DIALOG_HEIGHT, dialogContentParent.layoutParams.height)

        val dialogContent = dialogContentParent.getChildAt(0)
        assertEquals(dialog.contentView, dialogContent)
        assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.width)
        assertEquals(ViewGroup.LayoutParams.MATCH_PARENT, dialogContent.layoutParams.height)

        // Hiding/showing/dismissing the dialog should hide/show/dismiss the host dialog given that
        // it's a ListenableDialog.
@@ -126,6 +137,11 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
    }

    private class TestDialog(context: Context) : Dialog(context), ListenableDialog {
        companion object {
            const val DIALOG_WIDTH = 100
            const val DIALOG_HEIGHT = 200
        }

        private val listeners = hashSetOf<DialogListener>()
        val contentView = View(context)
        var onStartCalled = false
@@ -138,6 +154,7 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
            setContentView(contentView)
        }