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

Commit 6d7f8e20 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[Media TTT] Add the margins as part of the removal animation.

Bug: 203800644
Test: manual: Remove chip and verify that the chip also moves up
during the animation (see video attached to bug)

Change-Id: I7ce7516ee1ba3c92f5f8c7e1f3ef8bce6a0f5f6d
parent 693d0a41
Loading
Loading
Loading
Loading
+111 −57
Original line number Diff line number Diff line
@@ -360,7 +360,9 @@ class ViewHierarchyAnimator {
         * [interpolator] and [duration].
         *
         * The end state of the animation is controlled by [destination]. This value can be any of
         * the four corners, any of the four edges, or the center of the view.
         * the four corners, any of the four edges, or the center of the view. If any margins are
         * added on the side(s) of the [destination], the translation of those margins can be
         * included by specifying [includeMargins].
         *
         * @param onAnimationEnd an optional runnable that will be run once the animation finishes
         *    successfully. Will not be run if the animation is cancelled.
@@ -371,6 +373,7 @@ class ViewHierarchyAnimator {
            destination: Hotspot = Hotspot.CENTER,
            interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
            duration: Long = DEFAULT_DURATION,
            includeMargins: Boolean = false,
            onAnimationEnd: Runnable? = null,
        ): Boolean {
            if (
@@ -428,10 +431,12 @@ class ViewHierarchyAnimator {
            val endValues =
                processEndValuesForRemoval(
                    destination,
                    rootView,
                    rootView.left,
                    rootView.top,
                    rootView.right,
                    rootView.bottom
                    rootView.bottom,
                    includeMargins,
                )

            val boundsToAnimate = mutableSetOf<Bound>()
@@ -718,70 +723,111 @@ class ViewHierarchyAnimator {
         *         |         | ->  |       |  ->   |     |   ->    x---x    ->      x
         *         |         |     x-------x       x-----x
         *         x---------x
         *     4) destination=TOP, includeMargins=true (and view has large top margin)
         *                                                                     x---------x
         *                                                      x---------x
         *                                       x---------x    x---------x
         *                        x---------x    |         |
         *         x---------x    |         |    x---------x
         *         |         |    |         |
         *         |         | -> x---------x ->             ->             ->
         *         |         |
         *         x---------x
         * ```
         */
        private fun processEndValuesForRemoval(
            destination: Hotspot,
            rootView: View,
            left: Int,
            top: Int,
            right: Int,
            bottom: Int
            bottom: Int,
            includeMargins: Boolean = false,
        ): 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 marginAdjustment =
                if (includeMargins &&
                    (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
                    val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
                    DimenHolder(
                        left = marginLp.leftMargin,
                        top = marginLp.topMargin,
                        right = marginLp.rightMargin,
                        bottom = marginLp.bottomMargin
                    )
            } else {
                DimenHolder(0, 0, 0, 0)
            }

            return mapOf(
                Bound.LEFT to endLeft,
            // These are the end values to use *if* this bound is part of the destination.
            val endLeft = left - marginAdjustment.left
            val endTop = top - marginAdjustment.top
            val endRight = right + marginAdjustment.right
            val endBottom = bottom + marginAdjustment.bottom

            // For the below calculations: We need to ensure that the destination bound and the
            // bound *opposite* to the destination bound end at the same value, to ensure that the
            // view has size 0 for that dimension.
            // For example,
            //  - If destination=TOP, then endTop == endBottom. Left and right stay the same.
            //  - If destination=RIGHT, then endRight == endLeft. Top and bottom stay the same.
            //  - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight.

            return when (destination) {
                Hotspot.TOP -> mapOf(
                    Bound.TOP to endTop,
                    Bound.BOTTOM to endTop,
                    Bound.LEFT to left,
                    Bound.RIGHT to right,
                )
                Hotspot.TOP_RIGHT -> mapOf(
                    Bound.TOP to endTop,
                    Bound.BOTTOM to endTop,
                    Bound.RIGHT to endRight,
                Bound.BOTTOM to endBottom
                    Bound.LEFT to endRight,
                )
                Hotspot.RIGHT -> mapOf(
                    Bound.RIGHT to endRight,
                    Bound.LEFT to endRight,
                    Bound.TOP to top,
                    Bound.BOTTOM to bottom,
                )
                Hotspot.BOTTOM_RIGHT -> mapOf(
                    Bound.BOTTOM to endBottom,
                    Bound.TOP to endBottom,
                    Bound.RIGHT to endRight,
                    Bound.LEFT to endRight,
                )
                Hotspot.BOTTOM -> mapOf(
                    Bound.BOTTOM to endBottom,
                    Bound.TOP to endBottom,
                    Bound.LEFT to left,
                    Bound.RIGHT to right,
                )
                Hotspot.BOTTOM_LEFT -> mapOf(
                    Bound.BOTTOM to endBottom,
                    Bound.TOP to endBottom,
                    Bound.LEFT to endLeft,
                    Bound.RIGHT to endLeft,
                )
                Hotspot.LEFT -> mapOf(
                    Bound.LEFT to endLeft,
                    Bound.RIGHT to endLeft,
                    Bound.TOP to top,
                    Bound.BOTTOM to bottom,
                )
                Hotspot.TOP_LEFT -> mapOf(
                    Bound.TOP to endTop,
                    Bound.BOTTOM to endTop,
                    Bound.LEFT to endLeft,
                    Bound.RIGHT to endLeft,
                )
                Hotspot.CENTER -> mapOf(
                    Bound.LEFT to (endLeft + endRight) / 2,
                    Bound.RIGHT to (endLeft + endRight) / 2,
                    Bound.TOP to (endTop + endBottom) / 2,
                    Bound.BOTTOM to (endTop + endBottom) / 2,
                )
            }
        }

        /**
@@ -1061,4 +1107,12 @@ class ViewHierarchyAnimator {
        abstract fun setValue(view: View, value: Int)
        abstract fun getValue(view: View): Int
    }

    /** Simple data class to hold a set of dimens for left, top, right, bottom. */
    private data class DimenHolder(
        val left: Int,
        val top: Int,
        val right: Int,
        val bottom: Int,
    )
}
+1 −2
Original line number Diff line number Diff line
@@ -199,10 +199,9 @@ open class MediaTttChipControllerSender @Inject constructor(
            ViewHierarchyAnimator.Hotspot.TOP,
            Interpolators.EMPHASIZED_ACCELERATE,
            ANIMATION_DURATION,
            includeMargins = true,
            onAnimationEnd,
        )
        // TODO(b/203800644): Add includeMargins as an option to ViewHierarchyAnimator so that the
        //   animateChipOut matches the animateChipIn.
    }

    override fun shouldIgnoreViewRemoval(info: ChipSenderInfo, removalReason: String): Boolean {
+269 −5
Original line number Diff line number Diff line
@@ -935,6 +935,251 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
        checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100)
    }

    /* ******** start of animatesViewRemoval_includeMarginsTrue tests ******** */
    @Test
    fun animatesViewRemoval_includeMarginsTrue_center() {
        setUpRootWithChildren(includeMarginsOnFirstChild = true)
        val removedChild = rootView.getChildAt(0)
        val originalLeft = removedChild.left
        val originalTop = removedChild.top
        val originalRight = removedChild.right
        val originalBottom = removedChild.bottom

        val success = ViewHierarchyAnimator.animateRemoval(
            removedChild,
            destination = ViewHierarchyAnimator.Hotspot.CENTER,
            includeMargins = true,
        )
        forceLayout()

        assertTrue(success)
        assertNotNull(removedChild.getTag(R.id.tag_animator))
        advanceAnimation(removedChild, 1.0f)
        val expectedX = ((originalLeft - M_LEFT) + (originalRight + M_RIGHT)) / 2
        val expectedY = ((originalTop - M_TOP) + (originalBottom + M_BOTTOM)) / 2

        checkBounds(
            removedChild,
            l = expectedX,
            t = expectedY,
            r = expectedX,
            b = expectedY
        )
    }

    @Test
    fun animatesViewRemoval_includeMarginsTrue_left() {
        setUpRootWithChildren(includeMarginsOnFirstChild = true)
        val removedChild = rootView.getChildAt(0)
        val originalLeft = removedChild.left
        val originalTop = removedChild.top
        val originalBottom = removedChild.bottom

        val success = ViewHierarchyAnimator.animateRemoval(
            removedChild,
            destination = ViewHierarchyAnimator.Hotspot.LEFT,
            includeMargins = true,
        )
        forceLayout()

        assertTrue(success)
        assertNotNull(removedChild.getTag(R.id.tag_animator))
        advanceAnimation(removedChild, 1.0f)
        checkBounds(
            removedChild,
            l = originalLeft - M_LEFT,
            t = originalTop,
            r = originalLeft - M_LEFT,
            b = originalBottom
        )
    }

    @Test
    fun animatesViewRemoval_includeMarginsTrue_topLeft() {
        setUpRootWithChildren(includeMarginsOnFirstChild = true)
        val removedChild = rootView.getChildAt(0)
        val originalLeft = removedChild.left
        val originalTop = removedChild.top

        val success = ViewHierarchyAnimator.animateRemoval(
            removedChild,
            destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
            includeMargins = true,
        )
        forceLayout()

        assertTrue(success)
        assertNotNull(removedChild.getTag(R.id.tag_animator))
        advanceAnimation(removedChild, 1.0f)
        checkBounds(
            removedChild,
            l = originalLeft - M_LEFT,
            t = originalTop - M_TOP,
            r = originalLeft - M_LEFT,
            b = originalTop - M_TOP
        )
    }

    @Test
    fun animatesViewRemoval_includeMarginsTrue_top() {
        setUpRootWithChildren(includeMarginsOnFirstChild = true)
        val removedChild = rootView.getChildAt(0)
        val originalLeft = removedChild.left
        val originalTop = removedChild.top
        val originalRight = removedChild.right

        val success = ViewHierarchyAnimator.animateRemoval(
            removedChild,
            destination = ViewHierarchyAnimator.Hotspot.TOP,
            includeMargins = true,
        )
        forceLayout()

        assertTrue(success)
        assertNotNull(removedChild.getTag(R.id.tag_animator))
        advanceAnimation(removedChild, 1.0f)
        checkBounds(
            removedChild,
            l = originalLeft,
            t = originalTop - M_TOP,
            r = originalRight,
            b = originalTop - M_TOP
        )
    }

    @Test
    fun animatesViewRemoval_includeMarginsTrue_topRight() {
        setUpRootWithChildren(includeMarginsOnFirstChild = true)
        val removedChild = rootView.getChildAt(0)
        val originalTop = removedChild.top
        val originalRight = removedChild.right

        val success = ViewHierarchyAnimator.animateRemoval(
            removedChild,
            destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
            includeMargins = true,
        )
        forceLayout()

        assertTrue(success)
        assertNotNull(removedChild.getTag(R.id.tag_animator))
        advanceAnimation(removedChild, 1.0f)
        checkBounds(
            removedChild,
            l = originalRight + M_RIGHT,
            t = originalTop - M_TOP,
            r = originalRight + M_RIGHT,
            b = originalTop - M_TOP
        )
    }

    @Test
    fun animatesViewRemoval_includeMarginsTrue_right() {
        setUpRootWithChildren(includeMarginsOnFirstChild = true)
        val removedChild = rootView.getChildAt(0)
        val originalTop = removedChild.top
        val originalRight = removedChild.right
        val originalBottom = removedChild.bottom

        val success = ViewHierarchyAnimator.animateRemoval(
            removedChild,
            destination = ViewHierarchyAnimator.Hotspot.RIGHT,
            includeMargins = true,
        )
        forceLayout()

        assertTrue(success)
        assertNotNull(removedChild.getTag(R.id.tag_animator))
        advanceAnimation(removedChild, 1.0f)
        checkBounds(
            removedChild,
            l = originalRight + M_RIGHT,
            t = originalTop,
            r = originalRight + M_RIGHT,
            b = originalBottom
        )
    }

    @Test
    fun animatesViewRemoval_includeMarginsTrue_bottomRight() {
        setUpRootWithChildren(includeMarginsOnFirstChild = true)
        val removedChild = rootView.getChildAt(0)
        val originalRight = removedChild.right
        val originalBottom = removedChild.bottom

        val success = ViewHierarchyAnimator.animateRemoval(
            removedChild,
            destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
            includeMargins = true,
        )
        forceLayout()

        assertTrue(success)
        assertNotNull(removedChild.getTag(R.id.tag_animator))
        advanceAnimation(removedChild, 1.0f)
        checkBounds(
            removedChild,
            l = originalRight + M_RIGHT,
            t = originalBottom + M_BOTTOM,
            r = originalRight + M_RIGHT,
            b = originalBottom + M_BOTTOM
        )
    }

    @Test
    fun animatesViewRemoval_includeMarginsTrue_bottom() {
        setUpRootWithChildren(includeMarginsOnFirstChild = true)
        val removedChild = rootView.getChildAt(0)
        val originalLeft = removedChild.left
        val originalRight = removedChild.right
        val originalBottom = removedChild.bottom

        val success = ViewHierarchyAnimator.animateRemoval(
            removedChild,
            destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
            includeMargins = true,
        )
        forceLayout()

        assertTrue(success)
        assertNotNull(removedChild.getTag(R.id.tag_animator))
        advanceAnimation(removedChild, 1.0f)
        checkBounds(
            removedChild,
            l = originalLeft,
            t = originalBottom + M_BOTTOM,
            r = originalRight,
            b = originalBottom + M_BOTTOM
        )
    }

    @Test
    fun animatesViewRemoval_includeMarginsTrue_bottomLeft() {
        setUpRootWithChildren(includeMarginsOnFirstChild = true)
        val removedChild = rootView.getChildAt(0)
        val originalLeft = removedChild.left
        val originalBottom = removedChild.bottom

        val success = ViewHierarchyAnimator.animateRemoval(
            removedChild,
            destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
            includeMargins = true,
        )
        forceLayout()

        assertTrue(success)
        assertNotNull(removedChild.getTag(R.id.tag_animator))
        advanceAnimation(removedChild, 1.0f)
        checkBounds(
            removedChild,
            l = originalLeft - M_LEFT,
            t = originalBottom + M_BOTTOM,
            r = originalLeft - M_LEFT,
            b = originalBottom + M_BOTTOM
        )
    }
    /* ******** end of animatesViewRemoval_includeMarginsTrue tests ******** */

    @Test
    fun animatesChildrenDuringViewRemoval() {
        setUpRootWithChildren()
@@ -1215,7 +1460,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
        checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
    }

    private fun setUpRootWithChildren() {
    private fun setUpRootWithChildren(includeMarginsOnFirstChild: Boolean = false) {
        rootView = LinearLayout(mContext)
        (rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL
        (rootView as LinearLayout).weightSum = 1f
@@ -1229,13 +1474,26 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
        val secondChild = View(mContext)
        rootView.addView(secondChild)

        val childParams = LinearLayout.LayoutParams(
        val firstChildParams = LinearLayout.LayoutParams(
            0 /* width */,
            LinearLayout.LayoutParams.MATCH_PARENT
        )
        firstChildParams.weight = 0.5f
        if (includeMarginsOnFirstChild) {
            firstChildParams.leftMargin = M_LEFT
            firstChildParams.topMargin = M_TOP
            firstChildParams.rightMargin = M_RIGHT
            firstChildParams.bottomMargin = M_BOTTOM
        }
        firstChild.layoutParams = firstChildParams

        val secondChildParams = LinearLayout.LayoutParams(
            0 /* width */,
            LinearLayout.LayoutParams.MATCH_PARENT
        )
        childParams.weight = 0.5f
        firstChild.layoutParams = childParams
        secondChild.layoutParams = childParams
        secondChildParams.weight = 0.5f
        secondChild.layoutParams = secondChildParams

        firstGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */)
        (firstGrandChild.layoutParams as RelativeLayout.LayoutParams)
            .addRule(RelativeLayout.ALIGN_PARENT_START)
@@ -1315,3 +1573,9 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
        }
    }
}

// Margin values.
private const val M_LEFT = 14
private const val M_TOP = 16
private const val M_RIGHT = 18
private const val M_BOTTOM = 20