Merge "Fade out/in header with customizer in/out" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
0e42c76c72
@@ -3,7 +3,9 @@ package com.android.systemui.plugins.qs
|
||||
interface QSContainerController {
|
||||
fun setCustomizerAnimating(animating: Boolean)
|
||||
|
||||
fun setCustomizerShowing(showing: Boolean)
|
||||
fun setCustomizerShowing(showing: Boolean) = setCustomizerShowing(showing, 0L)
|
||||
|
||||
fun setCustomizerShowing(showing: Boolean, animationDuration: Long)
|
||||
|
||||
fun setDetailShowing(showing: Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,15 @@ public class QSDetailClipper {
|
||||
mBackground = (TransitionDrawable) detail.getBackground();
|
||||
}
|
||||
|
||||
public void animateCircularClip(int x, int y, boolean in, AnimatorListener listener) {
|
||||
updateCircularClip(true /* animate */, x, y, in, listener);
|
||||
/**
|
||||
* @param x x position where animation should originate
|
||||
* @param y y position where animation should originate
|
||||
* @param in whether animating in or out
|
||||
* @param listener Animation listener. Called whether or not {@code animate} is true.
|
||||
* @return the duration of the circular animator
|
||||
*/
|
||||
public long animateCircularClip(int x, int y, boolean in, AnimatorListener listener) {
|
||||
return updateCircularClip(true /* animate */, x, y, in, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,8 +57,9 @@ public class QSDetailClipper {
|
||||
* @param y y position where animation should originate
|
||||
* @param in whether animating in or out
|
||||
* @param listener Animation listener. Called whether or not {@code animate} is true.
|
||||
* @return the duration of the circular animator
|
||||
*/
|
||||
public void updateCircularClip(boolean animate, int x, int y, boolean in,
|
||||
public long updateCircularClip(boolean animate, int x, int y, boolean in,
|
||||
AnimatorListener listener) {
|
||||
if (mAnimator != null) {
|
||||
mAnimator.cancel();
|
||||
@@ -87,6 +95,7 @@ public class QSDetailClipper {
|
||||
mAnimator.addListener(mGoneOnEnd);
|
||||
}
|
||||
mAnimator.start();
|
||||
return mAnimator.getDuration();
|
||||
}
|
||||
|
||||
private final Runnable mReverseBackground = new Runnable() {
|
||||
|
||||
@@ -125,9 +125,10 @@ public class QSCustomizer extends LinearLayout {
|
||||
isShown = true;
|
||||
mOpening = true;
|
||||
setVisibility(View.VISIBLE);
|
||||
mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter));
|
||||
long duration = mClipper.animateCircularClip(
|
||||
mX, mY, true, new ExpandAnimatorListener(tileAdapter));
|
||||
mQsContainerController.setCustomizerAnimating(true);
|
||||
mQsContainerController.setCustomizerShowing(true);
|
||||
mQsContainerController.setCustomizerShowing(true, duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,13 +154,14 @@ public class QSCustomizer extends LinearLayout {
|
||||
// Make sure we're not opening (because we're closing). Nobody can think we are
|
||||
// customizing after the next two lines.
|
||||
mOpening = false;
|
||||
long duration = 0;
|
||||
if (animate) {
|
||||
mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
|
||||
duration = mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
|
||||
} else {
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
mQsContainerController.setCustomizerAnimating(animate);
|
||||
mQsContainerController.setCustomizerShowing(false);
|
||||
mQsContainerController.setCustomizerShowing(false, duration);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.animation.ShadeInterpolation
|
||||
import com.android.systemui.battery.BatteryMeterView
|
||||
import com.android.systemui.battery.BatteryMeterViewController
|
||||
@@ -310,6 +311,14 @@ class LargeScreenShadeHeaderController @Inject constructor(
|
||||
updateVisibility()
|
||||
}
|
||||
|
||||
fun startCustomizingAnimation(show: Boolean, duration: Long) {
|
||||
header.animate()
|
||||
.setDuration(duration)
|
||||
.alpha(if (show) 0f else 1f)
|
||||
.setInterpolator(if (show) Interpolators.ALPHA_OUT else Interpolators.ALPHA_IN)
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun loadConstraints() {
|
||||
if (header is MotionLayout) {
|
||||
// Use resources.getXml instead of passing the resource id due to bug b/205018300
|
||||
|
||||
@@ -35,6 +35,7 @@ class NotificationsQSContainerController @Inject constructor(
|
||||
view: NotificationsQuickSettingsContainer,
|
||||
private val navigationModeController: NavigationModeController,
|
||||
private val overviewProxyService: OverviewProxyService,
|
||||
private val largeScreenShadeHeaderController: LargeScreenShadeHeaderController,
|
||||
private val featureFlags: FeatureFlags,
|
||||
@Main private val delayableExecutor: DelayableExecutor
|
||||
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
|
||||
@@ -156,9 +157,12 @@ class NotificationsQSContainerController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCustomizerShowing(showing: Boolean) {
|
||||
isQSCustomizing = showing
|
||||
updateBottomSpacing()
|
||||
override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) {
|
||||
if (showing != isQSCustomizing) {
|
||||
isQSCustomizing = showing
|
||||
largeScreenShadeHeaderController.startCustomizingAnimation(showing, animationDuration)
|
||||
updateBottomSpacing()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDetailShowing(showing: Boolean) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.graphics.Rect
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.DisplayCutout
|
||||
import android.view.View
|
||||
import android.view.ViewPropertyAnimator
|
||||
import android.view.WindowInsets
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
@@ -30,6 +31,7 @@ import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.animation.ShadeInterpolation
|
||||
import com.android.systemui.battery.BatteryMeterView
|
||||
import com.android.systemui.battery.BatteryMeterViewController
|
||||
@@ -64,6 +66,7 @@ import org.mockito.Answers
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.anyBoolean
|
||||
import org.mockito.Mockito.anyFloat
|
||||
import org.mockito.Mockito.anyInt
|
||||
@@ -614,6 +617,34 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateOutOnStartCustomizing() {
|
||||
val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
controller.startCustomizingAnimation(show = true, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(0f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_OUT)
|
||||
verify(animator).start()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateInOnEndCustomizing() {
|
||||
val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
controller.startCustomizingAnimation(show = false, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(1f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_IN)
|
||||
verify(animator).start()
|
||||
}
|
||||
|
||||
private fun createWindowInsets(
|
||||
topCutout: Rect? = Rect()
|
||||
): WindowInsets {
|
||||
|
||||
@@ -4,10 +4,12 @@ import android.app.StatusBarManager
|
||||
import android.content.Context
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.View
|
||||
import android.view.ViewPropertyAnimator
|
||||
import android.widget.TextView
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.animation.ShadeInterpolation
|
||||
import com.android.systemui.battery.BatteryMeterView
|
||||
import com.android.systemui.battery.BatteryMeterViewController
|
||||
@@ -29,8 +31,10 @@ import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Answers
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.verifyZeroInteractions
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
@@ -198,4 +202,32 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
|
||||
context.getString(com.android.internal.R.string.status_bar_alarm_clock)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateOutOnStartCustomizing() {
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = true, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(0f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_OUT)
|
||||
verify(animator).start()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateInOnEndCustomizing() {
|
||||
val animator = mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
|
||||
val duration = 1000L
|
||||
whenever(view.animate()).thenReturn(animator)
|
||||
|
||||
mLargeScreenShadeHeaderController.startCustomizingAnimation(show = false, duration)
|
||||
|
||||
verify(animator).setDuration(duration)
|
||||
verify(animator).alpha(1f)
|
||||
verify(animator).setInterpolator(Interpolators.ALPHA_IN)
|
||||
verify(animator).start()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.function.Consumer
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -33,10 +34,10 @@ import org.mockito.Mockito.doNothing
|
||||
import org.mockito.Mockito.eq
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.function.Consumer
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@@ -63,6 +64,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
|
||||
@Mock
|
||||
private lateinit var largeScreenShadeHeaderController: LargeScreenShadeHeaderController
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
@Captor
|
||||
lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
|
||||
@@ -92,6 +95,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
notificationsQSContainer,
|
||||
navigationModeController,
|
||||
overviewProxyService,
|
||||
largeScreenShadeHeaderController,
|
||||
featureFlags,
|
||||
delayableExecutor
|
||||
)
|
||||
@@ -371,8 +375,14 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
container.removeAllViews()
|
||||
container.addView(newViewWithId(1))
|
||||
container.addView(newViewWithId(View.NO_ID))
|
||||
val controller = NotificationsQSContainerController(container, navigationModeController,
|
||||
overviewProxyService, featureFlags, delayableExecutor)
|
||||
val controller = NotificationsQSContainerController(
|
||||
container,
|
||||
navigationModeController,
|
||||
overviewProxyService,
|
||||
largeScreenShadeHeaderController,
|
||||
featureFlags,
|
||||
delayableExecutor
|
||||
)
|
||||
controller.updateConstraints()
|
||||
|
||||
assertThat(container.getChildAt(0).id).isEqualTo(1)
|
||||
@@ -397,6 +407,21 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartCustomizingWithDuration() {
|
||||
controller.setCustomizerShowing(true, 100L)
|
||||
verify(largeScreenShadeHeaderController).startCustomizingAnimation(true, 100L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEndCustomizingWithDuration() {
|
||||
controller.setCustomizerShowing(true, 0L) // Only tracks changes
|
||||
reset(largeScreenShadeHeaderController)
|
||||
|
||||
controller.setCustomizerShowing(false, 100L)
|
||||
verify(largeScreenShadeHeaderController).startCustomizingAnimation(false, 100L)
|
||||
}
|
||||
|
||||
private fun disableSplitShade() {
|
||||
setSplitShadeEnabled(false)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user