From d976bb4a231bfc809086eca3dac532e03ab42796 Mon Sep 17 00:00:00 2001 From: Nicolo' Mazzucato Date: Tue, 20 Dec 2022 15:37:13 +0000 Subject: [PATCH] Fix flicker when starting folding The dimensions of LightRevealScrim were used before the view was being measured, causing the entire screen to become black for one frame sometimes (especially after boot. Now, LightRevealScrim accepts the initial dimensions what are used before the view is measured. Bug: 261560634 Test: LightRevealScrimTest && many unfolds soon after rebooting Change-Id: I91d1f2de82c563f085c9565c6e7116abb1e43e1d --- .../systemui/statusbar/LightRevealScrim.kt | 46 +++++++++++++++---- .../UnfoldLightRevealOverlayAnimation.kt | 28 ++++++----- .../statusbar/LightRevealScrimTest.kt | 24 +++++++++- 3 files changed, 77 insertions(+), 21 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 2334a4c27af8b..9421524c73a76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -90,8 +90,13 @@ object LiftReveal : LightRevealEffect { class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect { // Interpolator that reveals >80% of the content at 0.5 progress, makes revealing faster - private val interpolator = PathInterpolator(/* controlX1= */ 0.4f, /* controlY1= */ 0f, - /* controlX2= */ 0.2f, /* controlY2= */ 1f) + private val interpolator = + PathInterpolator( + /* controlX1= */ 0.4f, + /* controlY1= */ 0f, + /* controlX2= */ 0.2f, + /* controlY2= */ 1f + ) override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { val interpolatedAmount = interpolator.getInterpolation(amount) @@ -116,17 +121,17 @@ class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffe if (isVertical) { scrim.setRevealGradientBounds( - left = scrim.width / 2 - (scrim.width / 2) * gradientBoundsAmount, + left = scrim.viewWidth / 2 - (scrim.viewWidth / 2) * gradientBoundsAmount, top = 0f, - right = scrim.width / 2 + (scrim.width / 2) * gradientBoundsAmount, - bottom = scrim.height.toFloat() + right = scrim.viewWidth / 2 + (scrim.viewWidth / 2) * gradientBoundsAmount, + bottom = scrim.viewHeight.toFloat() ) } else { scrim.setRevealGradientBounds( left = 0f, - top = scrim.height / 2 - (scrim.height / 2) * gradientBoundsAmount, - right = scrim.width.toFloat(), - bottom = scrim.height / 2 + (scrim.height / 2) * gradientBoundsAmount + top = scrim.viewHeight / 2 - (scrim.viewHeight / 2) * gradientBoundsAmount, + right = scrim.viewWidth.toFloat(), + bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount ) } } @@ -234,7 +239,14 @@ class PowerButtonReveal( * transparent center. The center position, size, and stops of the gradient can be manipulated to * reveal views below the scrim as if they are being 'lit up'. */ -class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) { +class LightRevealScrim +@JvmOverloads +constructor( + context: Context?, + attrs: AttributeSet?, + initialWidth: Int? = null, + initialHeight: Int? = null +) : View(context, attrs) { /** Listener that is called if the scrim's opaqueness changes */ lateinit var isScrimOpaqueChangedListener: Consumer @@ -277,6 +289,17 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, var revealGradientWidth: Float = 0f var revealGradientHeight: Float = 0f + /** + * Keeps the initial value until the view is measured. See [LightRevealScrim.onMeasure]. + * + * Needed as the view dimensions are used before the onMeasure pass happens, and without preset + * width and height some flicker during fold/unfold happens. + */ + internal var viewWidth: Int = initialWidth ?: 0 + private set + internal var viewHeight: Int = initialHeight ?: 0 + private set + /** * Alpha of the fill that can be used in the beginning of the animation to hide the content. * Normally the gradient bounds are animated from small size so the content is not visible, but @@ -375,6 +398,11 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, invalidate() } + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + viewWidth = measuredWidth + viewHeight = measuredHeight + } /** * Sets bounds for the transparent oval gradient that reveals the views below the scrim. This is * simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 9cca950287296..523cf68891a08 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -159,18 +159,24 @@ constructor( ensureOverlayRemoved() val newRoot = SurfaceControlViewHost(context, context.display!!, wwm) - val newView = - LightRevealScrim(context, null).apply { - revealEffect = createLightRevealEffect() - isScrimOpaqueChangedListener = Consumer {} - revealAmount = - when (reason) { - FOLD -> TRANSPARENT - UNFOLD -> BLACK - } - } - val params = getLayoutParams() + val newView = + LightRevealScrim( + context, + attrs = null, + initialWidth = params.width, + initialHeight = params.height + ) + .apply { + revealEffect = createLightRevealEffect() + isScrimOpaqueChangedListener = Consumer {} + revealAmount = + when (reason) { + FOLD -> TRANSPARENT + UNFOLD -> BLACK + } + } + newRoot.setView(newView, params) if (onOverlayReady != null) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt index 97fe25d9a6193..d3befb4ad4cd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt @@ -20,6 +20,7 @@ import android.testing.AndroidTestingRunner import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -36,7 +37,7 @@ class LightRevealScrimTest : SysuiTestCase() { @Before fun setUp() { - scrim = LightRevealScrim(context, null) + scrim = LightRevealScrim(context, null, DEFAULT_WIDTH, DEFAULT_HEIGHT) scrim.isScrimOpaqueChangedListener = Consumer { opaque -> isOpaque = opaque } @@ -63,4 +64,25 @@ class LightRevealScrimTest : SysuiTestCase() { scrim.revealAmount = 0.5f assertFalse("Scrim is opaque even though it's revealed", scrim.isScrimOpaque) } + + @Test + fun testBeforeOnMeasure_defaultDimensions() { + assertThat(scrim.viewWidth).isEqualTo(DEFAULT_WIDTH) + assertThat(scrim.viewHeight).isEqualTo(DEFAULT_HEIGHT) + } + + @Test + fun testAfterOnMeasure_measuredDimensions() { + scrim.measure(/* widthMeasureSpec= */ exact(1), /* heightMeasureSpec= */ exact(2)) + + assertThat(scrim.viewWidth).isEqualTo(1) + assertThat(scrim.viewHeight).isEqualTo(2) + } + + private fun exact(value: Int) = View.MeasureSpec.makeMeasureSpec(value, View.MeasureSpec.EXACTLY) + + private companion object { + private const val DEFAULT_WIDTH = 42 + private const val DEFAULT_HEIGHT = 24 + } } \ No newline at end of file