Merge "[Media TTT] Add the margins as part of the removal animation." into tm-qpr-dev am: 88c9f3af10

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

Change-Id: Ic2a9fd548c989634e28a376c7555e128df5d69ac
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Caitlin Shkuratov
2022-10-17 19:23:20 +00:00
committed by Automerger Merge Worker
3 changed files with 381 additions and 64 deletions

View File

@@ -360,7 +360,9 @@ class ViewHierarchyAnimator {
* [interpolator] and [duration]. * [interpolator] and [duration].
* *
* The end state of the animation is controlled by [destination]. This value can be any of * 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 * @param onAnimationEnd an optional runnable that will be run once the animation finishes
* successfully. Will not be run if the animation is cancelled. * successfully. Will not be run if the animation is cancelled.
@@ -371,6 +373,7 @@ class ViewHierarchyAnimator {
destination: Hotspot = Hotspot.CENTER, destination: Hotspot = Hotspot.CENTER,
interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR, interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
duration: Long = DEFAULT_DURATION, duration: Long = DEFAULT_DURATION,
includeMargins: Boolean = false,
onAnimationEnd: Runnable? = null, onAnimationEnd: Runnable? = null,
): Boolean { ): Boolean {
if ( if (
@@ -428,10 +431,12 @@ class ViewHierarchyAnimator {
val endValues = val endValues =
processEndValuesForRemoval( processEndValuesForRemoval(
destination, destination,
rootView,
rootView.left, rootView.left,
rootView.top, rootView.top,
rootView.right, rootView.right,
rootView.bottom rootView.bottom,
includeMargins,
) )
val boundsToAnimate = mutableSetOf<Bound>() val boundsToAnimate = mutableSetOf<Bound>()
@@ -718,70 +723,111 @@ class ViewHierarchyAnimator {
* | | -> | | -> | | -> x---x -> x * | | -> | | -> | | -> x---x -> x
* | | x-------x x-----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( private fun processEndValuesForRemoval(
destination: Hotspot, destination: Hotspot,
rootView: View,
left: Int, left: Int,
top: Int, top: Int,
right: Int, right: Int,
bottom: Int bottom: Int,
includeMargins: Boolean = false,
): Map<Bound, Int> { ): Map<Bound, Int> {
val endLeft = val marginAdjustment =
when (destination) { if (includeMargins &&
Hotspot.CENTER -> (left + right) / 2 (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
Hotspot.BOTTOM, val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
Hotspot.BOTTOM_LEFT, DimenHolder(
Hotspot.LEFT, left = marginLp.leftMargin,
Hotspot.TOP_LEFT, top = marginLp.topMargin,
Hotspot.TOP -> left right = marginLp.rightMargin,
Hotspot.TOP_RIGHT, bottom = marginLp.bottomMargin
Hotspot.RIGHT, )
Hotspot.BOTTOM_RIGHT -> right } else {
} DimenHolder(0, 0, 0, 0)
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
} }
return mapOf( // These are the end values to use *if* this bound is part of the destination.
Bound.LEFT to endLeft, 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.TOP to endTop,
Bound.RIGHT to endRight, Bound.BOTTOM to endTop,
Bound.BOTTOM to endBottom 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 @@ class ViewHierarchyAnimator {
abstract fun setValue(view: View, value: Int) abstract fun setValue(view: View, value: Int)
abstract fun getValue(view: View): 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,
)
} }

View File

@@ -199,10 +199,9 @@ open class MediaTttChipControllerSender @Inject constructor(
ViewHierarchyAnimator.Hotspot.TOP, ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_ACCELERATE, Interpolators.EMPHASIZED_ACCELERATE,
ANIMATION_DURATION, ANIMATION_DURATION,
includeMargins = true,
onAnimationEnd, 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 { override fun shouldIgnoreViewRemoval(info: ChipSenderInfo, removalReason: String): Boolean {

View File

@@ -935,6 +935,251 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
checkBounds(remainingChild, l = 0, t = 0, r = 100, b = 100) 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 @Test
fun animatesChildrenDuringViewRemoval() { fun animatesChildrenDuringViewRemoval() {
setUpRootWithChildren() setUpRootWithChildren()
@@ -1215,7 +1460,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
checkBounds(rootView, l = 10, t = 10, r = 50, b = 50) checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
} }
private fun setUpRootWithChildren() { private fun setUpRootWithChildren(includeMarginsOnFirstChild: Boolean = false) {
rootView = LinearLayout(mContext) rootView = LinearLayout(mContext)
(rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL (rootView as LinearLayout).orientation = LinearLayout.HORIZONTAL
(rootView as LinearLayout).weightSum = 1f (rootView as LinearLayout).weightSum = 1f
@@ -1229,13 +1474,26 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
val secondChild = View(mContext) val secondChild = View(mContext)
rootView.addView(secondChild) rootView.addView(secondChild)
val childParams = LinearLayout.LayoutParams( val firstChildParams = LinearLayout.LayoutParams(
0 /* width */, 0 /* width */,
LinearLayout.LayoutParams.MATCH_PARENT LinearLayout.LayoutParams.MATCH_PARENT
) )
childParams.weight = 0.5f firstChildParams.weight = 0.5f
firstChild.layoutParams = childParams if (includeMarginsOnFirstChild) {
secondChild.layoutParams = childParams 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 = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */)
(firstGrandChild.layoutParams as RelativeLayout.LayoutParams) (firstGrandChild.layoutParams as RelativeLayout.LayoutParams)
.addRule(RelativeLayout.ALIGN_PARENT_START) .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