Move "max to height" roundness logic to Roundable.kt
The sum of top and bottom corner radii should be at max the clipped height of the roundable view. This logic was spread out between the ExpandableOutlineView and the ActivateableNotificationView. This CL moves it to the Roundable.kt. Bug: b/243302608 Test: post HUNs and observe the appear/disappear animations Test: see the corners changing as Notifs scroll into the Shelf Change-Id: I3a615e81f7bfbb9e9f512d0b3ce745c019ea637f
This commit is contained in:
@@ -3,7 +3,10 @@ package com.android.systemui.statusbar.notification
|
||||
import android.util.FloatProperty
|
||||
import android.view.View
|
||||
import androidx.annotation.FloatRange
|
||||
import com.android.systemui.Dependency
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.statusbar.notification.stack.AnimationProperties
|
||||
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
|
||||
import kotlin.math.abs
|
||||
@@ -20,6 +23,8 @@ interface Roundable {
|
||||
/** Properties required for a Roundable */
|
||||
val roundableState: RoundableState
|
||||
|
||||
val clipHeight: Int
|
||||
|
||||
/** Current top roundness */
|
||||
@get:FloatRange(from = 0.0, to = 1.0)
|
||||
@JvmDefault
|
||||
@@ -40,12 +45,16 @@ interface Roundable {
|
||||
/** Current top corner in pixel, based on [topRoundness] and [maxRadius] */
|
||||
@JvmDefault
|
||||
val topCornerRadius: Float
|
||||
get() = topRoundness * maxRadius
|
||||
get() =
|
||||
if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.topCornerRadius
|
||||
else topRoundness * maxRadius
|
||||
|
||||
/** Current bottom corner in pixel, based on [bottomRoundness] and [maxRadius] */
|
||||
@JvmDefault
|
||||
val bottomCornerRadius: Float
|
||||
get() = bottomRoundness * maxRadius
|
||||
get() =
|
||||
if (roundableState.newHeadsUpAnimFlagEnabled) roundableState.bottomCornerRadius
|
||||
else bottomRoundness * maxRadius
|
||||
|
||||
/** Get and update the current radii */
|
||||
@JvmDefault
|
||||
@@ -320,14 +329,20 @@ interface Roundable {
|
||||
* @param roundable Target of the radius animation
|
||||
* @param maxRadius Max corner radius in pixels
|
||||
*/
|
||||
class RoundableState(
|
||||
class RoundableState
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
internal val targetView: View,
|
||||
private val roundable: Roundable,
|
||||
maxRadius: Float,
|
||||
private val featureFlags: FeatureFlags = Dependency.get(FeatureFlags::class.java)
|
||||
) {
|
||||
internal var maxRadius = maxRadius
|
||||
private set
|
||||
|
||||
internal val newHeadsUpAnimFlagEnabled
|
||||
get() = featureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS)
|
||||
|
||||
/** Animatable for top roundness */
|
||||
private val topAnimatable = topAnimatable(roundable)
|
||||
|
||||
@@ -344,6 +359,41 @@ class RoundableState(
|
||||
internal var bottomRoundness = 0f
|
||||
private set
|
||||
|
||||
internal val topCornerRadius: Float
|
||||
get() {
|
||||
val height = roundable.clipHeight
|
||||
val topRadius = topRoundness * maxRadius
|
||||
val bottomRadius = bottomRoundness * maxRadius
|
||||
|
||||
if (height == 0) {
|
||||
return 0f
|
||||
} else if (topRadius + bottomRadius > height) {
|
||||
// The sum of top and bottom corner radii should be at max the clipped height
|
||||
val overShoot = topRadius + bottomRadius - height
|
||||
return topRadius - (overShoot * topRoundness / (topRoundness + bottomRoundness))
|
||||
}
|
||||
|
||||
return topRadius
|
||||
}
|
||||
|
||||
internal val bottomCornerRadius: Float
|
||||
get() {
|
||||
val height = roundable.clipHeight
|
||||
val topRadius = topRoundness * maxRadius
|
||||
val bottomRadius = bottomRoundness * maxRadius
|
||||
|
||||
if (height == 0) {
|
||||
return 0f
|
||||
} else if (topRadius + bottomRadius > height) {
|
||||
// The sum of top and bottom corner radii should be at max the clipped height
|
||||
val overShoot = topRadius + bottomRadius - height
|
||||
return bottomRadius -
|
||||
(overShoot * bottomRoundness / (topRoundness + bottomRoundness))
|
||||
}
|
||||
|
||||
return bottomRadius
|
||||
}
|
||||
|
||||
/** Last requested top roundness associated by [SourceType] */
|
||||
internal val topRoundnessMap = mutableMapOf<SourceType, Float>()
|
||||
|
||||
|
||||
@@ -566,12 +566,20 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
|
||||
|
||||
@Override
|
||||
public float getTopCornerRadius() {
|
||||
if (isNewHeadsUpAnimFlagEnabled()) {
|
||||
return super.getTopCornerRadius();
|
||||
}
|
||||
|
||||
float fraction = getInterpolatedAppearAnimationFraction();
|
||||
return MathUtils.lerp(0, super.getTopCornerRadius(), fraction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBottomCornerRadius() {
|
||||
if (isNewHeadsUpAnimFlagEnabled()) {
|
||||
return super.getBottomCornerRadius();
|
||||
}
|
||||
|
||||
float fraction = getInterpolatedAppearAnimationFraction();
|
||||
return MathUtils.lerp(0, super.getBottomCornerRadius(), fraction);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,10 @@ import android.util.IndentingPrintWriter;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.statusbar.notification.RoundableState;
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
|
||||
import com.android.systemui.util.DumpUtilsKt;
|
||||
@@ -47,12 +50,14 @@ public abstract class ExpandableOutlineView extends ExpandableView {
|
||||
private float mOutlineAlpha = -1f;
|
||||
private boolean mAlwaysRoundBothCorners;
|
||||
private Path mTmpPath = new Path();
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
|
||||
/**
|
||||
* {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
|
||||
* it is moved. Otherwise, the translation is set on the {@code ExpandableOutlineView} itself.
|
||||
*/
|
||||
protected boolean mDismissUsingRowTranslationX = true;
|
||||
|
||||
private float[] mTmpCornerRadii = new float[8];
|
||||
|
||||
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
|
||||
@@ -81,6 +86,15 @@ public abstract class ExpandableOutlineView extends ExpandableView {
|
||||
return mRoundableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClipHeight() {
|
||||
if (mCustomOutline) {
|
||||
return mOutlineRect.height();
|
||||
}
|
||||
|
||||
return super.getClipHeight();
|
||||
}
|
||||
|
||||
protected Path getClipPath(boolean ignoreTranslation) {
|
||||
int left;
|
||||
int top;
|
||||
@@ -112,7 +126,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
|
||||
return EMPTY_PATH;
|
||||
}
|
||||
float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
|
||||
if (topRadius + bottomRadius > height) {
|
||||
if (!isNewHeadsUpAnimFlagEnabled() && (topRadius + bottomRadius > height)) {
|
||||
float overShoot = topRadius + bottomRadius - height;
|
||||
float currentTopRoundness = getTopRoundness();
|
||||
float currentBottomRoundness = getBottomRoundness();
|
||||
@@ -153,6 +167,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
|
||||
super(context, attrs);
|
||||
setOutlineProvider(mProvider);
|
||||
initDimens();
|
||||
mFeatureFlags = Dependency.get(FeatureFlags.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -360,4 +375,9 @@ public abstract class ExpandableOutlineView extends ExpandableView {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(b/290365128) replace with ViewRefactorFlag
|
||||
protected boolean isNewHeadsUpAnimFlagEnabled() {
|
||||
return mFeatureFlags.isEnabled(Flags.IMPROVED_HUN_ANIMATIONS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,12 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
|
||||
return mRoundableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClipHeight() {
|
||||
int clipHeight = Math.max(mActualHeight - mClipTopAmount - mClipBottomAmount, 0);
|
||||
return Math.max(clipHeight, mMinimumHeightForClipping);
|
||||
}
|
||||
|
||||
private void initDimens() {
|
||||
mContentShift = getResources().getDimensionPixelSize(
|
||||
R.dimen.shelf_transform_content_shift);
|
||||
|
||||
@@ -119,6 +119,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
|
||||
return mRoundableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClipHeight() {
|
||||
return mView.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyRoundnessAndInvalidate() {
|
||||
if (mRoundnessChangedListener != null) {
|
||||
|
||||
@@ -30,8 +30,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableView
|
||||
*/
|
||||
class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) {
|
||||
|
||||
override var clipHeight = 0
|
||||
var cornerRadius = 0f
|
||||
var clipHeight = 0
|
||||
var clipRect = RectF()
|
||||
var clipPath = Path()
|
||||
|
||||
|
||||
@@ -191,6 +191,11 @@ public class NotificationChildrenContainer extends ViewGroup
|
||||
return mRoundableState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getClipHeight() {
|
||||
return Math.max(mActualHeight - mClipBottomAmount, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int childCount =
|
||||
|
||||
@@ -3,7 +3,11 @@ package com.android.systemui.statusbar.notification
|
||||
import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -15,8 +19,9 @@ import org.mockito.Mockito.verify
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class RoundableTest : SysuiTestCase() {
|
||||
val targetView: View = mock()
|
||||
val roundable = FakeRoundable(targetView)
|
||||
private val targetView: View = mock()
|
||||
private val featureFlags = FakeFeatureFlags()
|
||||
private val roundable = FakeRoundable(targetView = targetView, featureFlags = featureFlags)
|
||||
|
||||
@Test
|
||||
fun defaultConfig_shouldNotHaveRoundedCorner() {
|
||||
@@ -144,16 +149,62 @@ class RoundableTest : SysuiTestCase() {
|
||||
assertEquals(0.2f, roundable.roundableState.bottomRoundness)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCornerRadii_radius_maxed_to_height() {
|
||||
whenever(targetView.height).thenReturn(10)
|
||||
featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
|
||||
roundable.requestRoundness(1f, 1f, SOURCE1)
|
||||
|
||||
assertCornerRadiiEquals(5f, 5f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCornerRadii_topRadius_maxed_to_height() {
|
||||
whenever(targetView.height).thenReturn(5)
|
||||
featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
|
||||
roundable.requestRoundness(1f, 0f, SOURCE1)
|
||||
|
||||
assertCornerRadiiEquals(5f, 0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCornerRadii_bottomRadius_maxed_to_height() {
|
||||
whenever(targetView.height).thenReturn(5)
|
||||
featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
|
||||
roundable.requestRoundness(0f, 1f, SOURCE1)
|
||||
|
||||
assertCornerRadiiEquals(0f, 5f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCornerRadii_radii_kept() {
|
||||
whenever(targetView.height).thenReturn(100)
|
||||
featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
|
||||
roundable.requestRoundness(1f, 1f, SOURCE1)
|
||||
|
||||
assertCornerRadiiEquals(MAX_RADIUS, MAX_RADIUS)
|
||||
}
|
||||
|
||||
private fun assertCornerRadiiEquals(top: Float, bottom: Float) {
|
||||
assertEquals("topCornerRadius", top, roundable.topCornerRadius)
|
||||
assertEquals("bottomCornerRadius", bottom, roundable.bottomCornerRadius)
|
||||
}
|
||||
|
||||
class FakeRoundable(
|
||||
targetView: View,
|
||||
radius: Float = MAX_RADIUS,
|
||||
featureFlags: FeatureFlags
|
||||
) : Roundable {
|
||||
override val roundableState =
|
||||
RoundableState(
|
||||
targetView = targetView,
|
||||
roundable = this,
|
||||
maxRadius = radius,
|
||||
featureFlags = featureFlags
|
||||
)
|
||||
|
||||
override val clipHeight: Int
|
||||
get() = roundableState.targetView.height
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
Reference in New Issue
Block a user