[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
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 1b7e26b..58ffef2 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -360,7 +360,9 @@
* [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 @@
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 @@
val endValues =
processEndValuesForRemoval(
destination,
+ rootView,
rootView.left,
rootView.top,
rootView.right,
- rootView.bottom
+ rootView.bottom,
+ includeMargins,
)
val boundsToAnimate = mutableSetOf<Bound>()
@@ -718,70 +723,111 @@
* | | -> | | -> | | -> 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,
- Bound.TOP to endTop,
- Bound.RIGHT to endRight,
- Bound.BOTTOM to endBottom
- )
+ // 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.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 @@
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,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 5d63145..ca066f4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -199,10 +199,9 @@
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 {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 986e7cd..6ab54a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -935,6 +935,251 @@
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 @@
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 @@
val secondChild = View(mContext)
rootView.addView(secondChild)
- val childParams = LinearLayout.LayoutParams(
+ val firstChildParams = LinearLayout.LayoutParams(
0 /* width */,
LinearLayout.LayoutParams.MATCH_PARENT
)
- childParams.weight = 0.5f
- firstChild.layoutParams = childParams
- secondChild.layoutParams = childParams
+ 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
+ )
+ 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 @@
}
}
}
+
+// 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