Merge "[Media TTT] Animate the chip in." into tm-d1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
5c1baf523d
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
<!-- ViewBoundsAnimator -->
|
<!-- ViewBoundsAnimator -->
|
||||||
<item type="id" name="tag_animator"/>
|
<item type="id" name="tag_animator"/>
|
||||||
|
<item type="id" name="tag_alpha_animator"/>
|
||||||
<item type="id" name="tag_layout_listener"/>
|
<item type="id" name="tag_layout_listener"/>
|
||||||
<item type="id" name="tag_override_bottom"/>
|
<item type="id" name="tag_override_bottom"/>
|
||||||
<item type="id" name="tag_override_left"/>
|
<item type="id" name="tag_override_left"/>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class ViewHierarchyAnimator {
|
|||||||
private val DEFAULT_INTERPOLATOR = Interpolators.STANDARD
|
private val DEFAULT_INTERPOLATOR = Interpolators.STANDARD
|
||||||
private val DEFAULT_ADDITION_INTERPOLATOR = Interpolators.STANDARD_DECELERATE
|
private val DEFAULT_ADDITION_INTERPOLATOR = Interpolators.STANDARD_DECELERATE
|
||||||
private val DEFAULT_REMOVAL_INTERPOLATOR = Interpolators.STANDARD_ACCELERATE
|
private val DEFAULT_REMOVAL_INTERPOLATOR = Interpolators.STANDARD_ACCELERATE
|
||||||
|
private val DEFAULT_FADE_IN_INTERPOLATOR = Interpolators.ALPHA_IN
|
||||||
|
|
||||||
/** The properties used to animate the view bounds. */
|
/** The properties used to animate the view bounds. */
|
||||||
private val PROPERTIES = mapOf(
|
private val PROPERTIES = mapOf(
|
||||||
@@ -162,6 +163,10 @@ class ViewHierarchyAnimator {
|
|||||||
* animate an already visible view, see [animate] and [animateNextUpdate].
|
* animate an already visible view, see [animate] and [animateNextUpdate].
|
||||||
*
|
*
|
||||||
* Then animator unregisters itself once the first addition animation is complete.
|
* Then animator unregisters itself once the first addition animation is complete.
|
||||||
|
*
|
||||||
|
* @param includeFadeIn true if the animator should also fade in the view and child views.
|
||||||
|
* @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
|
||||||
|
* [includeFadeIn] is false.
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun animateAddition(
|
fun animateAddition(
|
||||||
@@ -169,7 +174,9 @@ class ViewHierarchyAnimator {
|
|||||||
origin: Hotspot = Hotspot.CENTER,
|
origin: Hotspot = Hotspot.CENTER,
|
||||||
interpolator: Interpolator = DEFAULT_ADDITION_INTERPOLATOR,
|
interpolator: Interpolator = DEFAULT_ADDITION_INTERPOLATOR,
|
||||||
duration: Long = DEFAULT_DURATION,
|
duration: Long = DEFAULT_DURATION,
|
||||||
includeMargins: Boolean = false
|
includeMargins: Boolean = false,
|
||||||
|
includeFadeIn: Boolean = false,
|
||||||
|
fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (isVisible(
|
if (isVisible(
|
||||||
rootView.visibility,
|
rootView.visibility,
|
||||||
@@ -186,6 +193,42 @@ class ViewHierarchyAnimator {
|
|||||||
origin, interpolator, duration, ignorePreviousValues = !includeMargins
|
origin, interpolator, duration, ignorePreviousValues = !includeMargins
|
||||||
)
|
)
|
||||||
addListener(rootView, listener, recursive = true)
|
addListener(rootView, listener, recursive = true)
|
||||||
|
|
||||||
|
if (!includeFadeIn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootView is ViewGroup) {
|
||||||
|
// First, fade in the container view
|
||||||
|
val containerDuration = duration / 6
|
||||||
|
createAndStartFadeInAnimator(
|
||||||
|
rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then, fade in the child views
|
||||||
|
val childDuration = duration / 3
|
||||||
|
for (i in 0 until rootView.childCount) {
|
||||||
|
val view = rootView.getChildAt(i)
|
||||||
|
createAndStartFadeInAnimator(
|
||||||
|
view,
|
||||||
|
childDuration,
|
||||||
|
// Wait until the container fades in before fading in the children
|
||||||
|
startDelay = containerDuration,
|
||||||
|
interpolator = fadeInInterpolator
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// For now, we don't recursively fade in additional sub views (e.g. grandchild
|
||||||
|
// views) since it hasn't been necessary, but we could add that functionality.
|
||||||
|
} else {
|
||||||
|
// Fade in the view during the first half of the addition
|
||||||
|
createAndStartFadeInAnimator(
|
||||||
|
rootView,
|
||||||
|
duration / 2,
|
||||||
|
startDelay = 0,
|
||||||
|
interpolator = fadeInInterpolator
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -834,6 +877,27 @@ class ViewHierarchyAnimator {
|
|||||||
view.setTag(R.id.tag_animator, animator)
|
view.setTag(R.id.tag_animator, animator)
|
||||||
animator.start()
|
animator.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createAndStartFadeInAnimator(
|
||||||
|
view: View,
|
||||||
|
duration: Long,
|
||||||
|
startDelay: Long,
|
||||||
|
interpolator: Interpolator
|
||||||
|
) {
|
||||||
|
val animator = ObjectAnimator.ofFloat(view, "alpha", 1f)
|
||||||
|
animator.startDelay = startDelay
|
||||||
|
animator.duration = duration
|
||||||
|
animator.interpolator = interpolator
|
||||||
|
animator.addListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
view.setTag(R.id.tag_alpha_animator, null /* tag */)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
(view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel()
|
||||||
|
view.setTag(R.id.tag_alpha_animator, animator)
|
||||||
|
animator.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An enum used to determine the origin of addition animations. */
|
/** An enum used to determine the origin of addition animations. */
|
||||||
|
|||||||
@@ -13,71 +13,85 @@
|
|||||||
~ See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<LinearLayout
|
<!-- Wrap in a frame layout so that we can update the margins on the inner layout. (Since this view
|
||||||
|
is the root view of a window, we cannot change the root view's margins.) -->
|
||||||
|
<!-- Alphas start as 0 because the view will be animated in. -->
|
||||||
|
<FrameLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||||
android:id="@+id/media_ttt_sender_chip"
|
android:id="@+id/media_ttt_sender_chip"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:padding="@dimen/media_ttt_chip_outer_padding"
|
|
||||||
android:background="@drawable/media_ttt_chip_background"
|
|
||||||
android:layout_marginTop="50dp"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
>
|
|
||||||
|
|
||||||
<com.android.internal.widget.CachingIconView
|
<LinearLayout
|
||||||
android:id="@+id/app_icon"
|
android:id="@+id/media_ttt_sender_chip_inner"
|
||||||
android:layout_width="@dimen/media_ttt_app_icon_size"
|
android:orientation="horizontal"
|
||||||
android:layout_height="@dimen/media_ttt_app_icon_size"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/text"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="@dimen/media_ttt_text_size"
|
android:padding="@dimen/media_ttt_chip_outer_padding"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:background="@drawable/media_ttt_chip_background"
|
||||||
/>
|
android:layout_marginTop="20dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:alpha="0.0"
|
||||||
|
>
|
||||||
|
|
||||||
<!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
|
<com.android.internal.widget.CachingIconView
|
||||||
|
android:id="@+id/app_icon"
|
||||||
|
android:layout_width="@dimen/media_ttt_app_icon_size"
|
||||||
|
android:layout_height="@dimen/media_ttt_app_icon_size"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:alpha="0.0"
|
||||||
|
/>
|
||||||
|
|
||||||
<ProgressBar
|
<TextView
|
||||||
android:id="@+id/loading"
|
android:id="@+id/text"
|
||||||
android:indeterminate="true"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="@dimen/media_ttt_status_icon_size"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="@dimen/media_ttt_status_icon_size"
|
android:textSize="@dimen/media_ttt_text_size"
|
||||||
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
|
android:alpha="0.0"
|
||||||
style="?android:attr/progressBarStyleSmall"
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<ImageView
|
<!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
|
||||||
android:id="@+id/failure_icon"
|
<ProgressBar
|
||||||
android:layout_width="@dimen/media_ttt_status_icon_size"
|
android:id="@+id/loading"
|
||||||
android:layout_height="@dimen/media_ttt_status_icon_size"
|
android:indeterminate="true"
|
||||||
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
|
android:layout_width="@dimen/media_ttt_status_icon_size"
|
||||||
android:src="@drawable/ic_warning"
|
android:layout_height="@dimen/media_ttt_status_icon_size"
|
||||||
android:tint="@color/GM2_red_500"
|
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
|
||||||
/>
|
android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
|
||||||
|
style="?android:attr/progressBarStyleSmall"
|
||||||
|
android:alpha="0.0"
|
||||||
|
/>
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
android:id="@+id/undo"
|
android:id="@+id/failure_icon"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="@dimen/media_ttt_status_icon_size"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="@dimen/media_ttt_status_icon_size"
|
||||||
android:text="@string/media_transfer_undo"
|
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
|
||||||
android:textColor="?androidprv:attr/textColorOnAccent"
|
android:src="@drawable/ic_warning"
|
||||||
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
|
android:tint="@color/GM2_red_500"
|
||||||
android:textSize="@dimen/media_ttt_text_size"
|
android:alpha="0.0"
|
||||||
android:paddingStart="@dimen/media_ttt_chip_outer_padding"
|
/>
|
||||||
android:paddingEnd="@dimen/media_ttt_chip_outer_padding"
|
|
||||||
android:paddingTop="@dimen/media_ttt_undo_button_vertical_padding"
|
|
||||||
android:paddingBottom="@dimen/media_ttt_undo_button_vertical_padding"
|
|
||||||
android:layout_marginTop="@dimen/media_ttt_undo_button_vertical_negative_margin"
|
|
||||||
android:layout_marginBottom="@dimen/media_ttt_undo_button_vertical_negative_margin"
|
|
||||||
android:background="@drawable/media_ttt_undo_background"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
<TextView
|
||||||
|
android:id="@+id/undo"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/media_transfer_undo"
|
||||||
|
android:textColor="?androidprv:attr/textColorOnAccent"
|
||||||
|
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
|
||||||
|
android:textSize="@dimen/media_ttt_text_size"
|
||||||
|
android:paddingStart="@dimen/media_ttt_chip_outer_padding"
|
||||||
|
android:paddingEnd="@dimen/media_ttt_chip_outer_padding"
|
||||||
|
android:paddingTop="@dimen/media_ttt_undo_button_vertical_padding"
|
||||||
|
android:paddingBottom="@dimen/media_ttt_undo_button_vertical_padding"
|
||||||
|
android:layout_marginTop="@dimen/media_ttt_undo_button_vertical_negative_margin"
|
||||||
|
android:layout_marginBottom="@dimen/media_ttt_undo_button_vertical_negative_margin"
|
||||||
|
android:background="@drawable/media_ttt_undo_background"
|
||||||
|
android:alpha="0.0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
|
|||||||
PowerManager.WAKE_REASON_APPLICATION,
|
PowerManager.WAKE_REASON_APPLICATION,
|
||||||
"com.android.systemui:media_tap_to_transfer_activated"
|
"com.android.systemui:media_tap_to_transfer_activated"
|
||||||
)
|
)
|
||||||
|
animateChipIn(currentChipView)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel and re-set the chip timeout each time we get a new state.
|
// Cancel and re-set the chip timeout each time we get a new state.
|
||||||
@@ -137,6 +138,12 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
|
|||||||
*/
|
*/
|
||||||
abstract fun updateChipView(chipInfo: T, currentChipView: ViewGroup)
|
abstract fun updateChipView(chipInfo: T, currentChipView: ViewGroup)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A method that can be implemented by subclcasses to do custom animations for when the chip
|
||||||
|
* appears.
|
||||||
|
*/
|
||||||
|
open fun animateChipIn(chipView: ViewGroup) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size that the icon should be, or null if no size override is needed.
|
* Returns the size that the icon should be, or null if no size override is needed.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import android.view.WindowManager
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.android.internal.statusbar.IUndoMediaTransferCallback
|
import com.android.internal.statusbar.IUndoMediaTransferCallback
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.animation.Interpolators
|
||||||
|
import com.android.systemui.animation.ViewHierarchyAnimator
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.media.taptotransfer.common.ChipInfoCommon
|
import com.android.systemui.media.taptotransfer.common.ChipInfoCommon
|
||||||
@@ -124,7 +126,6 @@ class MediaTttChipControllerSender @Inject constructor(
|
|||||||
currentChipView.requireViewById<View>(R.id.loading).visibility =
|
currentChipView.requireViewById<View>(R.id.loading).visibility =
|
||||||
chipState.isMidTransfer.visibleIfTrue()
|
chipState.isMidTransfer.visibleIfTrue()
|
||||||
|
|
||||||
|
|
||||||
// Undo
|
// Undo
|
||||||
val undoView = currentChipView.requireViewById<View>(R.id.undo)
|
val undoView = currentChipView.requireViewById<View>(R.id.undo)
|
||||||
val undoClickListener = chipState.undoClickListener(
|
val undoClickListener = chipState.undoClickListener(
|
||||||
@@ -138,6 +139,17 @@ class MediaTttChipControllerSender @Inject constructor(
|
|||||||
chipState.isTransferFailure.visibleIfTrue()
|
chipState.isTransferFailure.visibleIfTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun animateChipIn(chipView: ViewGroup) {
|
||||||
|
ViewHierarchyAnimator.animateAddition(
|
||||||
|
chipView.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner),
|
||||||
|
ViewHierarchyAnimator.Hotspot.TOP,
|
||||||
|
Interpolators.EMPHASIZED_DECELERATE,
|
||||||
|
duration = 500L,
|
||||||
|
includeMargins = true,
|
||||||
|
includeFadeIn = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun removeChip(removalReason: String) {
|
override fun removeChip(removalReason: String) {
|
||||||
// Don't remove the chip if we're mid-transfer since the user should still be able to
|
// Don't remove the chip if we're mid-transfer since the user should still be able to
|
||||||
// see the status of the transfer. (But do remove it if it's finally timed out.)
|
// see the status of the transfer. (But do remove it if it's finally timed out.)
|
||||||
|
|||||||
@@ -520,6 +520,145 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
|
|||||||
endAnimation(rootView)
|
endAnimation(rootView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun animatesAppearingViewsFadeIn_alphaStartsAtZero_endsAtOne() {
|
||||||
|
rootView.alpha = 0f
|
||||||
|
ViewHierarchyAnimator.animateAddition(rootView, includeFadeIn = true)
|
||||||
|
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
|
||||||
|
|
||||||
|
advanceFadeInAnimation(rootView, fraction = 1f)
|
||||||
|
endFadeInAnimation(rootView)
|
||||||
|
|
||||||
|
assertNull(rootView.getTag(R.id.tag_alpha_animator))
|
||||||
|
assertEquals(1f, rootView.alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun animatesAppearingViewsFadeIn_alphaStartsAboveZero_endsAtOne() {
|
||||||
|
rootView.alpha = 0.2f
|
||||||
|
ViewHierarchyAnimator.animateAddition(rootView, includeFadeIn = true)
|
||||||
|
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
|
||||||
|
|
||||||
|
advanceFadeInAnimation(rootView, fraction = 1f)
|
||||||
|
endFadeInAnimation(rootView)
|
||||||
|
|
||||||
|
assertNull(rootView.getTag(R.id.tag_alpha_animator))
|
||||||
|
assertEquals(1f, rootView.alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun animatesAppearingViewsFadeIn_alphaStartsAsZero_alphaUpdatedMidAnimation() {
|
||||||
|
rootView.alpha = 0f
|
||||||
|
ViewHierarchyAnimator.animateAddition(
|
||||||
|
rootView,
|
||||||
|
includeFadeIn = true,
|
||||||
|
fadeInInterpolator = Interpolators.LINEAR
|
||||||
|
)
|
||||||
|
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
|
||||||
|
|
||||||
|
advanceFadeInAnimation(rootView, fraction = 0.42f)
|
||||||
|
|
||||||
|
assertEquals(0.42f, rootView.alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun animatesAppearingViewsFadeIn_alphaStartsAboveZero_alphaUpdatedMidAnimation() {
|
||||||
|
rootView.alpha = 0.6f
|
||||||
|
ViewHierarchyAnimator.animateAddition(
|
||||||
|
rootView,
|
||||||
|
includeFadeIn = true,
|
||||||
|
fadeInInterpolator = Interpolators.LINEAR
|
||||||
|
)
|
||||||
|
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
|
||||||
|
|
||||||
|
advanceFadeInAnimation(rootView, fraction = 0.5f)
|
||||||
|
|
||||||
|
assertEquals(0.8f, rootView.alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun animatesAppearingViewsFadeIn_childViewAlphasAlsoAnimated() {
|
||||||
|
rootView.alpha = 0f
|
||||||
|
val firstChild = View(context)
|
||||||
|
firstChild.alpha = 0f
|
||||||
|
val secondChild = View(context)
|
||||||
|
secondChild.alpha = 0f
|
||||||
|
rootView.addView(firstChild)
|
||||||
|
rootView.addView(secondChild)
|
||||||
|
|
||||||
|
ViewHierarchyAnimator.animateAddition(
|
||||||
|
rootView,
|
||||||
|
includeFadeIn = true,
|
||||||
|
fadeInInterpolator = Interpolators.LINEAR
|
||||||
|
)
|
||||||
|
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
|
||||||
|
|
||||||
|
advanceFadeInAnimation(rootView, fraction = 0.5f)
|
||||||
|
|
||||||
|
assertEquals(0.5f, rootView.alpha)
|
||||||
|
assertEquals(0.5f, firstChild.alpha)
|
||||||
|
assertEquals(0.5f, secondChild.alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun animatesAppearingViewsFadeIn_animatesFromPreviousAnimationProgress() {
|
||||||
|
rootView.alpha = 0f
|
||||||
|
ViewHierarchyAnimator.animateAddition(
|
||||||
|
rootView,
|
||||||
|
includeFadeIn = true,
|
||||||
|
fadeInInterpolator = Interpolators.LINEAR
|
||||||
|
)
|
||||||
|
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
|
||||||
|
|
||||||
|
advanceFadeInAnimation(rootView, fraction = 0.5f)
|
||||||
|
assertEquals(0.5f, rootView.alpha)
|
||||||
|
assertNotNull(rootView.getTag(R.id.tag_alpha_animator))
|
||||||
|
|
||||||
|
// IF we request animation again
|
||||||
|
ViewHierarchyAnimator.animateAddition(
|
||||||
|
rootView,
|
||||||
|
includeFadeIn = true,
|
||||||
|
fadeInInterpolator = Interpolators.LINEAR
|
||||||
|
)
|
||||||
|
|
||||||
|
// THEN the alpha remains at its current value (it doesn't get reset to 0)
|
||||||
|
assertNotNull(rootView.getTag(R.id.tag_alpha_animator))
|
||||||
|
assertEquals(0.5f, rootView.alpha)
|
||||||
|
|
||||||
|
// IF we advance the new animation to the end
|
||||||
|
advanceFadeInAnimation(rootView, fraction = 1f)
|
||||||
|
endFadeInAnimation(rootView)
|
||||||
|
|
||||||
|
// THEN we still end at the correct value
|
||||||
|
assertNull(rootView.getTag(R.id.tag_alpha_animator))
|
||||||
|
assertEquals(1f, rootView.alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun animatesAppearingViews_fadeInFalse_alphasNotUpdated() {
|
||||||
|
rootView.alpha = 0.3f
|
||||||
|
val firstChild = View(context)
|
||||||
|
firstChild.alpha = 0.4f
|
||||||
|
val secondChild = View(context)
|
||||||
|
secondChild.alpha = 0.5f
|
||||||
|
rootView.addView(firstChild)
|
||||||
|
rootView.addView(secondChild)
|
||||||
|
|
||||||
|
ViewHierarchyAnimator.animateAddition(
|
||||||
|
rootView,
|
||||||
|
includeFadeIn = false,
|
||||||
|
fadeInInterpolator = Interpolators.LINEAR
|
||||||
|
)
|
||||||
|
rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
|
||||||
|
|
||||||
|
advanceFadeInAnimation(rootView, fraction = 1f)
|
||||||
|
|
||||||
|
assertEquals(0.3f, rootView.alpha)
|
||||||
|
assertEquals(0.4f, firstChild.alpha)
|
||||||
|
assertEquals(0.5f, secondChild.alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
fun animatesViewRemovalFromStartToEnd() {
|
fun animatesViewRemovalFromStartToEnd() {
|
||||||
setUpRootWithChildren()
|
setUpRootWithChildren()
|
||||||
|
|
||||||
@@ -1003,6 +1142,16 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun advanceFadeInAnimation(rootView: View, fraction: Float) {
|
||||||
|
(rootView.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.setCurrentFraction(fraction)
|
||||||
|
|
||||||
|
if (rootView is ViewGroup) {
|
||||||
|
for (i in 0 until rootView.childCount) {
|
||||||
|
advanceFadeInAnimation(rootView.getChildAt(i), fraction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun endAnimation(rootView: View) {
|
private fun endAnimation(rootView: View) {
|
||||||
(rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.end()
|
(rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.end()
|
||||||
|
|
||||||
@@ -1012,4 +1161,14 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun endFadeInAnimation(rootView: View) {
|
||||||
|
(rootView.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.end()
|
||||||
|
|
||||||
|
if (rootView is ViewGroup) {
|
||||||
|
for (i in 0 until rootView.childCount) {
|
||||||
|
endFadeInAnimation(rootView.getChildAt(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ import android.os.PowerManager
|
|||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.internal.logging.testing.UiEventLoggerFake
|
import com.android.internal.logging.testing.UiEventLoggerFake
|
||||||
@@ -620,22 +620,22 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
|
|||||||
verify(windowManager).removeView(any())
|
verify(windowManager).removeView(any())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LinearLayout.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
|
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
|
||||||
|
|
||||||
private fun LinearLayout.getChipText(): String =
|
private fun ViewGroup.getChipText(): String =
|
||||||
(this.requireViewById<TextView>(R.id.text)).text as String
|
(this.requireViewById<TextView>(R.id.text)).text as String
|
||||||
|
|
||||||
private fun LinearLayout.getLoadingIconVisibility(): Int =
|
private fun ViewGroup.getLoadingIconVisibility(): Int =
|
||||||
this.requireViewById<View>(R.id.loading).visibility
|
this.requireViewById<View>(R.id.loading).visibility
|
||||||
|
|
||||||
private fun LinearLayout.getUndoButton(): View = this.requireViewById(R.id.undo)
|
private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.undo)
|
||||||
|
|
||||||
private fun LinearLayout.getFailureIcon(): View = this.requireViewById(R.id.failure_icon)
|
private fun ViewGroup.getFailureIcon(): View = this.requireViewById(R.id.failure_icon)
|
||||||
|
|
||||||
private fun getChipView(): LinearLayout {
|
private fun getChipView(): ViewGroup {
|
||||||
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
|
val viewCaptor = ArgumentCaptor.forClass(View::class.java)
|
||||||
verify(windowManager).addView(viewCaptor.capture(), any())
|
verify(windowManager).addView(viewCaptor.capture(), any())
|
||||||
return viewCaptor.value as LinearLayout
|
return viewCaptor.value as ViewGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Helper method providing default parameters to not clutter up the tests. */
|
/** Helper method providing default parameters to not clutter up the tests. */
|
||||||
|
|||||||
Reference in New Issue
Block a user