Loading packages/SystemUI/res/layout/ongoing_privacy_chip.xml +6 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,10 @@ android:layout_height="match_parent" android:layout_width="wrap_content" android:layout_gravity="center_vertical|end" android:focusable="true" > android:focusable="true" android:clipChildren="false" android:clipToPadding="false" > <LinearLayout android:id="@+id/icons_container" Loading @@ -34,5 +37,7 @@ android:layout_gravity="center" android:minWidth="@dimen/ongoing_appops_chip_min_width" android:maxWidth="@dimen/ongoing_appops_chip_max_width" android:clipChildren="false" android:clipToPadding="false" /> </com.android.systemui.privacy.OngoingPrivacyChip> No newline at end of file packages/SystemUI/res/layout/system_event_animation_window.xml +2 −12 Original line number Diff line number Diff line Loading @@ -19,17 +19,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_vertical|end" android:paddingTop="@dimen/status_bar_padding_top" android:paddingEnd="8dp" android:clipChildren="false" android:clipToPadding="false" > <ImageView android:id="@+id/dot_view" android:layout_width="10dp" android:layout_height="10dp" android:layout_gravity="center_vertical|end" android:src="@drawable/system_animation_ongoing_dot" android:visibility="invisible" /> </FrameLayout> No newline at end of file packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt +12 −1 Original line number Diff line number Diff line Loading @@ -22,13 +22,14 @@ import android.widget.ImageView import android.widget.LinearLayout import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.statusbar.events.BackgroundAnimatableView class OngoingPrivacyChip @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttrs: Int = 0, defStyleRes: Int = 0 ) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) { ) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView { private var iconMargin = 0 private var iconSize = 0 Loading @@ -50,6 +51,16 @@ class OngoingPrivacyChip @JvmOverloads constructor( updateResources() } /** * When animating as a chip in the status bar, we want to animate the width for the container * of the privacy items. We have to subtract our own top and left offset because the bounds * come to us as absolute on-screen bounds, and `iconsContainer` is laid out relative to the * frame layout's bounds. */ override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) { iconsContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top) } // Should only be called if the builder icons or app changed private fun updateView(builder: PrivacyChipBuilder) { fun setIcons(chipBuilder: PrivacyChipBuilder, iconsContainer: ViewGroup) { Loading packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +31 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.events import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.graphics.drawable.ColorDrawable Loading @@ -27,13 +28,15 @@ import com.android.systemui.R import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.privacy.PrivacyItem typealias ViewCreator = (context: Context) -> BackgroundAnimatableView interface StatusEvent { val priority: Int // Whether or not to force the status bar open and show a dot val forceVisible: Boolean // Whether or not to show an animation for this event val showAnimation: Boolean val viewCreator: (context: Context) -> View val viewCreator: ViewCreator var contentDescription: String? // Update this event with values from another event. Loading @@ -47,14 +50,37 @@ interface StatusEvent { } } class BGView( context: Context ) : View(context), BackgroundAnimatableView { override val view: View get() = this override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) { setLeftTopRightBottom(l, t, r, b) } } @SuppressLint("AppCompatCustomView") class BGImageView( context: Context ) : ImageView(context), BackgroundAnimatableView { override val view: View get() = this override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) { setLeftTopRightBottom(l, t, r, b) } } class BatteryEvent : StatusEvent { override val priority = 50 override val forceVisible = false override val showAnimation = true override var contentDescription: String? = "" override val viewCreator: (context: Context) -> View = { context -> val iv = ImageView(context) override val viewCreator: (context: Context) -> BGImageView = { context -> val iv = BGImageView(context) iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE)) iv.setBackgroundDrawable(ColorDrawable(Color.GREEN)) iv Loading @@ -72,7 +98,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { var privacyItems: List<PrivacyItem> = listOf() private var privacyChip: OngoingPrivacyChip? = null override val viewCreator: (context: Context) -> View = { context -> override val viewCreator: ViewCreator = { context -> val v = LayoutInflater.from(context) .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip v.privacyList = privacyItems Loading @@ -82,7 +108,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { } override fun toString(): String { return javaClass.simpleName return "${javaClass.simpleName}(forceVisible=$forceVisible, privacyItems=$privacyItems)" } override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean { Loading packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +103 −48 Original line number Diff line number Diff line Loading @@ -18,14 +18,16 @@ package com.android.systemui.statusbar.events import android.animation.ValueAnimator import android.content.Context import android.graphics.Point import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.View.MeasureSpec.AT_MOST import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.widget.FrameLayout import com.android.systemui.R import com.android.systemui.statusbar.phone.StatusBarLocationPublisher import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.window.StatusBarWindowController import javax.inject.Inject Loading @@ -35,44 +37,73 @@ import javax.inject.Inject class SystemEventChipAnimationController @Inject constructor( private val context: Context, private val statusBarWindowController: StatusBarWindowController, private val locationPublisher: StatusBarLocationPublisher private val contentInsetsProvider: StatusBarContentInsetsProvider ) : SystemStatusChipAnimationCallback { var showPersistentDot = false set(value) { field = value statusBarWindowController.setForceStatusBarVisible(value) maybeUpdateShowDot() } private lateinit var animationWindowView: FrameLayout private lateinit var animationDotView: View private var currentAnimatedView: View? = null private var currentAnimatedView: BackgroundAnimatableView? = null // Left for LTR, Right for RTL private var animationDirection = LEFT private var chipRight = 0 private var chipLeft = 0 private var chipWidth = 0 private var dotCenter = Point(0, 0) private var dotSize = context.resources.getDimensionPixelSize( R.dimen.ongoing_appops_dot_diameter) // If the chip animates away to a persistent dot, then we modify the CHIP_OUT animation private var isAnimatingToDot = false // TODO: move to dagger private var initialized = false override fun onChipAnimationStart( viewCreator: (context: Context) -> View, viewCreator: ViewCreator, @SystemAnimationState state: Int ) { if (!initialized) init() if (state == ANIMATING_IN) { currentAnimatedView = viewCreator(context) animationWindowView.addView(currentAnimatedView, layoutParamsDefault()) animationDirection = if (animationWindowView.isLayoutRtl) RIGHT else LEFT // Initialize the animated view val insets = contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation() currentAnimatedView = viewCreator(context).also { animationWindowView.addView( it.view, layoutParamsDefault( if (animationWindowView.isLayoutRtl) insets.first else insets.second)) it.view.alpha = 0f // For some reason, the window view's measured width is always 0 here, so use the // parent (status bar) it.view.measure( View.MeasureSpec.makeMeasureSpec( (animationWindowView.parent as View).width, AT_MOST), View.MeasureSpec.makeMeasureSpec(animationWindowView.height, AT_MOST)) chipWidth = it.chipWidth } // decide which direction we're animating from, and then set some screen coordinates val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation() when (animationDirection) { LEFT -> { chipRight = contentRect.right chipLeft = contentRect.right - chipWidth } else /* RIGHT */ -> { chipLeft = contentRect.left chipRight = contentRect.left + chipWidth } } // We are animating IN; chip comes in from View.END currentAnimatedView?.apply { val translation = width.toFloat() translationX = if (isLayoutRtl) -translation else translation alpha = 0f visibility = View.VISIBLE setPadding(locationPublisher.marginLeft, 0, locationPublisher.marginRight, 0) updateAnimatedViewBoundsForAmount(0.1f, this) } } else { // We are animating away currentAnimatedView?.apply { translationX = 0f currentAnimatedView!!.view.apply { alpha = 1f } } Loading @@ -82,15 +113,14 @@ class SystemEventChipAnimationController @Inject constructor( if (state == ANIMATING_IN) { // Finished animating in currentAnimatedView?.apply { translationX = 0f alpha = 1f updateAnimatedViewBoundsForAmount(1f, this) } } else { // Finished animating away currentAnimatedView?.apply { currentAnimatedView!!.view.apply { visibility = View.INVISIBLE } animationWindowView.removeView(currentAnimatedView) animationWindowView.removeView(currentAnimatedView!!.view) } } Loading @@ -98,22 +128,10 @@ class SystemEventChipAnimationController @Inject constructor( animator: ValueAnimator, @SystemAnimationState state: Int ) { // Alpha is parameterized 0,1, and translation from (width, 0) currentAnimatedView?.apply { val amt = animator.animatedValue as Float alpha = amt val w = width val translation = (1 - amt) * w translationX = if (isLayoutRtl) -translation else translation } } private fun maybeUpdateShowDot() { if (!initialized) return if (!showPersistentDot && currentAnimatedView == null) { animationDotView.visibility = View.INVISIBLE val amt = (animator.animatedValue as Float).amt() view.alpha = (animator.animatedValue as Float) updateAnimatedViewBoundsForAmount(amt, this) } } Loading @@ -121,19 +139,56 @@ class SystemEventChipAnimationController @Inject constructor( initialized = true animationWindowView = LayoutInflater.from(context) .inflate(R.layout.system_event_animation_window, null) as FrameLayout animationDotView = animationWindowView.findViewById(R.id.dot_view) val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL statusBarWindowController.addViewToWindow(animationWindowView, lp) animationWindowView.clipToPadding = false animationWindowView.clipChildren = false animationWindowView.measureAllChildren = true } private fun start() = if (animationWindowView.isLayoutRtl) right() else left() private fun right() = locationPublisher.marginRight private fun left() = locationPublisher.marginLeft private fun layoutParamsDefault(): FrameLayout.LayoutParams = private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also { it.gravity = Gravity.END or Gravity.CENTER_VERTICAL it.marginStart = start() it.marginEnd = marginEnd } private fun updateAnimatedViewBoundsForAmount(amt: Float, chip: BackgroundAnimatableView) { when (animationDirection) { LEFT -> { chip.setBoundsForAnimation( (chipRight - (chipWidth * amt)).toInt(), chip.view.top, chipRight, chip.view.bottom) } else /* RIGHT */ -> { chip.setBoundsForAnimation( chipLeft, chip.view.top, (chipLeft + (chipWidth * amt)).toInt(), chip.view.bottom) } } } private fun start() = if (animationWindowView.isLayoutRtl) right() else left() private fun right() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().right private fun left() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().left private fun Float.amt() = 0.01f.coerceAtLeast(this) } /** * Chips should provide a view that can be animated with something better than a fade-in */ interface BackgroundAnimatableView { val view: View // Since this can't extend View, add a view prop get() = this as View val chipWidth: Int get() = view.measuredWidth fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) } // Animation directions private const val LEFT = 1 private const val RIGHT = 2 Loading
packages/SystemUI/res/layout/ongoing_privacy_chip.xml +6 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,10 @@ android:layout_height="match_parent" android:layout_width="wrap_content" android:layout_gravity="center_vertical|end" android:focusable="true" > android:focusable="true" android:clipChildren="false" android:clipToPadding="false" > <LinearLayout android:id="@+id/icons_container" Loading @@ -34,5 +37,7 @@ android:layout_gravity="center" android:minWidth="@dimen/ongoing_appops_chip_min_width" android:maxWidth="@dimen/ongoing_appops_chip_max_width" android:clipChildren="false" android:clipToPadding="false" /> </com.android.systemui.privacy.OngoingPrivacyChip> No newline at end of file
packages/SystemUI/res/layout/system_event_animation_window.xml +2 −12 Original line number Diff line number Diff line Loading @@ -19,17 +19,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center_vertical|end" android:paddingTop="@dimen/status_bar_padding_top" android:paddingEnd="8dp" android:clipChildren="false" android:clipToPadding="false" > <ImageView android:id="@+id/dot_view" android:layout_width="10dp" android:layout_height="10dp" android:layout_gravity="center_vertical|end" android:src="@drawable/system_animation_ongoing_dot" android:visibility="invisible" /> </FrameLayout> No newline at end of file
packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt +12 −1 Original line number Diff line number Diff line Loading @@ -22,13 +22,14 @@ import android.widget.ImageView import android.widget.LinearLayout import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.statusbar.events.BackgroundAnimatableView class OngoingPrivacyChip @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttrs: Int = 0, defStyleRes: Int = 0 ) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) { ) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView { private var iconMargin = 0 private var iconSize = 0 Loading @@ -50,6 +51,16 @@ class OngoingPrivacyChip @JvmOverloads constructor( updateResources() } /** * When animating as a chip in the status bar, we want to animate the width for the container * of the privacy items. We have to subtract our own top and left offset because the bounds * come to us as absolute on-screen bounds, and `iconsContainer` is laid out relative to the * frame layout's bounds. */ override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) { iconsContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top) } // Should only be called if the builder icons or app changed private fun updateView(builder: PrivacyChipBuilder) { fun setIcons(chipBuilder: PrivacyChipBuilder, iconsContainer: ViewGroup) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +31 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.events import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.graphics.drawable.ColorDrawable Loading @@ -27,13 +28,15 @@ import com.android.systemui.R import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.privacy.PrivacyItem typealias ViewCreator = (context: Context) -> BackgroundAnimatableView interface StatusEvent { val priority: Int // Whether or not to force the status bar open and show a dot val forceVisible: Boolean // Whether or not to show an animation for this event val showAnimation: Boolean val viewCreator: (context: Context) -> View val viewCreator: ViewCreator var contentDescription: String? // Update this event with values from another event. Loading @@ -47,14 +50,37 @@ interface StatusEvent { } } class BGView( context: Context ) : View(context), BackgroundAnimatableView { override val view: View get() = this override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) { setLeftTopRightBottom(l, t, r, b) } } @SuppressLint("AppCompatCustomView") class BGImageView( context: Context ) : ImageView(context), BackgroundAnimatableView { override val view: View get() = this override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) { setLeftTopRightBottom(l, t, r, b) } } class BatteryEvent : StatusEvent { override val priority = 50 override val forceVisible = false override val showAnimation = true override var contentDescription: String? = "" override val viewCreator: (context: Context) -> View = { context -> val iv = ImageView(context) override val viewCreator: (context: Context) -> BGImageView = { context -> val iv = BGImageView(context) iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE)) iv.setBackgroundDrawable(ColorDrawable(Color.GREEN)) iv Loading @@ -72,7 +98,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { var privacyItems: List<PrivacyItem> = listOf() private var privacyChip: OngoingPrivacyChip? = null override val viewCreator: (context: Context) -> View = { context -> override val viewCreator: ViewCreator = { context -> val v = LayoutInflater.from(context) .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip v.privacyList = privacyItems Loading @@ -82,7 +108,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { } override fun toString(): String { return javaClass.simpleName return "${javaClass.simpleName}(forceVisible=$forceVisible, privacyItems=$privacyItems)" } override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean { Loading
packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +103 −48 Original line number Diff line number Diff line Loading @@ -18,14 +18,16 @@ package com.android.systemui.statusbar.events import android.animation.ValueAnimator import android.content.Context import android.graphics.Point import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.View.MeasureSpec.AT_MOST import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.widget.FrameLayout import com.android.systemui.R import com.android.systemui.statusbar.phone.StatusBarLocationPublisher import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.window.StatusBarWindowController import javax.inject.Inject Loading @@ -35,44 +37,73 @@ import javax.inject.Inject class SystemEventChipAnimationController @Inject constructor( private val context: Context, private val statusBarWindowController: StatusBarWindowController, private val locationPublisher: StatusBarLocationPublisher private val contentInsetsProvider: StatusBarContentInsetsProvider ) : SystemStatusChipAnimationCallback { var showPersistentDot = false set(value) { field = value statusBarWindowController.setForceStatusBarVisible(value) maybeUpdateShowDot() } private lateinit var animationWindowView: FrameLayout private lateinit var animationDotView: View private var currentAnimatedView: View? = null private var currentAnimatedView: BackgroundAnimatableView? = null // Left for LTR, Right for RTL private var animationDirection = LEFT private var chipRight = 0 private var chipLeft = 0 private var chipWidth = 0 private var dotCenter = Point(0, 0) private var dotSize = context.resources.getDimensionPixelSize( R.dimen.ongoing_appops_dot_diameter) // If the chip animates away to a persistent dot, then we modify the CHIP_OUT animation private var isAnimatingToDot = false // TODO: move to dagger private var initialized = false override fun onChipAnimationStart( viewCreator: (context: Context) -> View, viewCreator: ViewCreator, @SystemAnimationState state: Int ) { if (!initialized) init() if (state == ANIMATING_IN) { currentAnimatedView = viewCreator(context) animationWindowView.addView(currentAnimatedView, layoutParamsDefault()) animationDirection = if (animationWindowView.isLayoutRtl) RIGHT else LEFT // Initialize the animated view val insets = contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation() currentAnimatedView = viewCreator(context).also { animationWindowView.addView( it.view, layoutParamsDefault( if (animationWindowView.isLayoutRtl) insets.first else insets.second)) it.view.alpha = 0f // For some reason, the window view's measured width is always 0 here, so use the // parent (status bar) it.view.measure( View.MeasureSpec.makeMeasureSpec( (animationWindowView.parent as View).width, AT_MOST), View.MeasureSpec.makeMeasureSpec(animationWindowView.height, AT_MOST)) chipWidth = it.chipWidth } // decide which direction we're animating from, and then set some screen coordinates val contentRect = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation() when (animationDirection) { LEFT -> { chipRight = contentRect.right chipLeft = contentRect.right - chipWidth } else /* RIGHT */ -> { chipLeft = contentRect.left chipRight = contentRect.left + chipWidth } } // We are animating IN; chip comes in from View.END currentAnimatedView?.apply { val translation = width.toFloat() translationX = if (isLayoutRtl) -translation else translation alpha = 0f visibility = View.VISIBLE setPadding(locationPublisher.marginLeft, 0, locationPublisher.marginRight, 0) updateAnimatedViewBoundsForAmount(0.1f, this) } } else { // We are animating away currentAnimatedView?.apply { translationX = 0f currentAnimatedView!!.view.apply { alpha = 1f } } Loading @@ -82,15 +113,14 @@ class SystemEventChipAnimationController @Inject constructor( if (state == ANIMATING_IN) { // Finished animating in currentAnimatedView?.apply { translationX = 0f alpha = 1f updateAnimatedViewBoundsForAmount(1f, this) } } else { // Finished animating away currentAnimatedView?.apply { currentAnimatedView!!.view.apply { visibility = View.INVISIBLE } animationWindowView.removeView(currentAnimatedView) animationWindowView.removeView(currentAnimatedView!!.view) } } Loading @@ -98,22 +128,10 @@ class SystemEventChipAnimationController @Inject constructor( animator: ValueAnimator, @SystemAnimationState state: Int ) { // Alpha is parameterized 0,1, and translation from (width, 0) currentAnimatedView?.apply { val amt = animator.animatedValue as Float alpha = amt val w = width val translation = (1 - amt) * w translationX = if (isLayoutRtl) -translation else translation } } private fun maybeUpdateShowDot() { if (!initialized) return if (!showPersistentDot && currentAnimatedView == null) { animationDotView.visibility = View.INVISIBLE val amt = (animator.animatedValue as Float).amt() view.alpha = (animator.animatedValue as Float) updateAnimatedViewBoundsForAmount(amt, this) } } Loading @@ -121,19 +139,56 @@ class SystemEventChipAnimationController @Inject constructor( initialized = true animationWindowView = LayoutInflater.from(context) .inflate(R.layout.system_event_animation_window, null) as FrameLayout animationDotView = animationWindowView.findViewById(R.id.dot_view) val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL statusBarWindowController.addViewToWindow(animationWindowView, lp) animationWindowView.clipToPadding = false animationWindowView.clipChildren = false animationWindowView.measureAllChildren = true } private fun start() = if (animationWindowView.isLayoutRtl) right() else left() private fun right() = locationPublisher.marginRight private fun left() = locationPublisher.marginLeft private fun layoutParamsDefault(): FrameLayout.LayoutParams = private fun layoutParamsDefault(marginEnd: Int): FrameLayout.LayoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also { it.gravity = Gravity.END or Gravity.CENTER_VERTICAL it.marginStart = start() it.marginEnd = marginEnd } private fun updateAnimatedViewBoundsForAmount(amt: Float, chip: BackgroundAnimatableView) { when (animationDirection) { LEFT -> { chip.setBoundsForAnimation( (chipRight - (chipWidth * amt)).toInt(), chip.view.top, chipRight, chip.view.bottom) } else /* RIGHT */ -> { chip.setBoundsForAnimation( chipLeft, chip.view.top, (chipLeft + (chipWidth * amt)).toInt(), chip.view.bottom) } } } private fun start() = if (animationWindowView.isLayoutRtl) right() else left() private fun right() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().right private fun left() = contentInsetsProvider.getStatusBarContentAreaForCurrentRotation().left private fun Float.amt() = 0.01f.coerceAtLeast(this) } /** * Chips should provide a view that can be animated with something better than a fade-in */ interface BackgroundAnimatableView { val view: View // Since this can't extend View, add a view prop get() = this as View val chipWidth: Int get() = view.measuredWidth fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) } // Animation directions private const val LEFT = 1 private const val RIGHT = 2