Bubble dismiss updates

- large colored circle, uses same accent color that launcher
  has for remove / uninstall
- added a shrink animation for the target when the bubble is
  stuck via the magnet
- no longer desaturate the bubble being dismissed but rather add
  a bit of an alpha

Bug: 183657564
Test: manual - get a bubble, drag it around & dismiss it, notice
               the animation
Change-Id: If0d0d86b7389f0fa3314598266cd83ecabdc6a9b
This commit is contained in:
Mady Mellor
2021-05-13 01:22:13 -07:00
parent dd934371ae
commit 9254393aa5
8 changed files with 78 additions and 184 deletions

View File

@@ -1,28 +0,0 @@
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!--
The transparent circle outline that encircles the bubbles when they're in the dismiss target.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="#66FFFFFF" />
<solid android:color="#B3000000" />
</shape>

View File

@@ -1,26 +0,0 @@
<!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!-- The 'X' bubble dismiss icon. This is just ic_close with a stroke. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
android:fillColor="#FFFFFFFF"
android:strokeColor="#FF000000"/>
</vector>

View File

@@ -20,9 +20,8 @@
android:shape="oval">
<stroke
android:width="1dp"
android:color="#AAFFFFFF" />
<solid android:color="#77000000" />
android:width="2dp"
android:color="@android:color/system_accent1_600" />
<solid android:color="@android:color/system_accent1_600" />
</shape>

View File

@@ -21,5 +21,5 @@
android:viewportHeight="24.0">
<path
android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
android:fillColor="#FFFFFFFF"/>
android:fillColor="@android:color/system_neutral1_50"/>
</vector>

View File

@@ -1,49 +0,0 @@
<!--
~ Copyright (C) 2019 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<!-- Bubble dismiss target consisting of an X icon and the text 'Dismiss'. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="@dimen/floating_dismiss_gradient_height"
android:layout_gravity="bottom|center_horizontal">
<FrameLayout
android:id="@+id/bubble_dismiss_circle"
android:layout_width="@dimen/bubble_dismiss_encircle_size"
android:layout_height="@dimen/bubble_dismiss_encircle_size"
android:layout_gravity="center"
android:background="@drawable/bubble_dismiss_circle" />
<LinearLayout
android:id="@+id/bubble_dismiss_icon_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:paddingBottom="@dimen/bubble_dismiss_target_padding_y"
android:paddingTop="@dimen/bubble_dismiss_target_padding_y"
android:paddingLeft="@dimen/bubble_dismiss_target_padding_x"
android:paddingRight="@dimen/bubble_dismiss_target_padding_x"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="horizontal">
<ImageView
android:id="@+id/bubble_dismiss_close_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/bubble_dismiss_icon" />
</LinearLayout>
</FrameLayout>

View File

@@ -15,7 +15,8 @@
limitations under the License.
-->
<resources>
<dimen name="dismiss_circle_size">52dp</dimen>
<dimen name="dismiss_circle_size">96dp</dimen>
<dimen name="dismiss_circle_small">60dp</dimen>
<!-- The height of the gradient indicating the dismiss edge when moving a PIP. -->
<dimen name="floating_dismiss_gradient_height">250dp</dimen>

View File

@@ -34,10 +34,7 @@ import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -192,8 +189,7 @@ public class BubbleStackView extends FrameLayout
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
private final ValueAnimator mDesaturateAndDarkenAnimator;
private final Paint mDesaturateAndDarkenPaint = new Paint();
private final ValueAnimator mDismissBubbleAnimator;
private PhysicsAnimationLayout mBubbleContainer;
private StackAnimationController mStackAnimationController;
@@ -330,8 +326,8 @@ public class BubbleStackView extends FrameLayout
private boolean mIsExpansionAnimating = false;
private boolean mIsBubbleSwitchAnimating = false;
/** The view to desaturate/darken when magneted to the dismiss target. */
@Nullable private View mDesaturateAndDarkenTargetView;
/** The view to shrink and apply alpha to when magneted to the dismiss target. */
@Nullable private View mViewBeingDismissed;
private Rect mTempRect = new Rect();
@@ -415,8 +411,7 @@ public class BubbleStackView extends FrameLayout
if (mExpandedAnimationController.getDraggedOutBubble() == null) {
return;
}
animateDesaturateAndDarken(
animateDismissBubble(
mExpandedAnimationController.getDraggedOutBubble(), true);
}
@@ -426,8 +421,7 @@ public class BubbleStackView extends FrameLayout
if (mExpandedAnimationController.getDraggedOutBubble() == null) {
return;
}
animateDesaturateAndDarken(
animateDismissBubble(
mExpandedAnimationController.getDraggedOutBubble(), false);
if (wasFlungOut) {
@@ -459,14 +453,13 @@ public class BubbleStackView extends FrameLayout
@Override
public void onStuckToTarget(
@NonNull MagnetizedObject.MagneticTarget target) {
animateDesaturateAndDarken(mBubbleContainer, true);
animateDismissBubble(mBubbleContainer, true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velX, float velY, boolean wasFlungOut) {
animateDesaturateAndDarken(mBubbleContainer, false);
animateDismissBubble(mBubbleContainer, false);
if (wasFlungOut) {
mStackAnimationController.flingStackThenSpringToEdge(
mStackAnimationController.getStackPosition().x, velX, velY);
@@ -481,11 +474,10 @@ public class BubbleStackView extends FrameLayout
mStackAnimationController.animateStackDismissal(
mDismissView.getHeight() /* translationYBy */,
() -> {
resetDesaturationAndDarken();
resetDismissAnimator();
dismissMagnetizedObject();
}
);
mDismissView.hide();
}
};
@@ -836,17 +828,7 @@ public class BubbleStackView extends FrameLayout
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
mDismissView = new DismissView(context);
addView(mDismissView);
final ContentResolver contentResolver = getContext().getContentResolver();
final int dismissRadius = Settings.Secure.getInt(
contentResolver, "bubble_dismiss_radius", mBubbleSize * 2 /* default */);
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
// MagnetizedObjects.
mMagneticTarget = new MagnetizedObject.MagneticTarget(
mDismissView.getCircle(), dismissRadius);
setUpDismissView();
setClipChildren(false);
setFocusable(true);
@@ -891,6 +873,7 @@ public class BubbleStackView extends FrameLayout
mRelativeStackPositionBeforeRotation = null;
}
setUpDismissView();
if (mIsExpanded) {
// Re-draw bubble row and pointer for new orientation.
beforeExpandedViewAnimation();
@@ -905,30 +888,23 @@ public class BubbleStackView extends FrameLayout
}
removeOnLayoutChangeListener(mOrientationChangedListener);
};
final ColorMatrix animatedMatrix = new ColorMatrix();
final ColorMatrix darkenMatrix = new ColorMatrix();
mDesaturateAndDarkenAnimator = ValueAnimator.ofFloat(1f, 0f);
mDesaturateAndDarkenAnimator.addUpdateListener(animation -> {
final float maxDismissSize = getResources().getDimensionPixelSize(
R.dimen.dismiss_circle_size);
final float minDismissSize = getResources().getDimensionPixelSize(
R.dimen.dismiss_circle_small);
final float sizePercent = minDismissSize / maxDismissSize;
mDismissBubbleAnimator = ValueAnimator.ofFloat(1f, 0f);
mDismissBubbleAnimator.addUpdateListener(animation -> {
final float animatedValue = (float) animation.getAnimatedValue();
animatedMatrix.setSaturation(animatedValue);
final float animatedDarkenValue = (1f - animatedValue) * DARKEN_PERCENT;
darkenMatrix.setScale(
1f - animatedDarkenValue /* red */,
1f - animatedDarkenValue /* green */,
1f - animatedDarkenValue /* blue */,
1f /* alpha */);
// Concat the matrices so that the animatedMatrix both desaturates and darkens.
animatedMatrix.postConcat(darkenMatrix);
// Update the paint and apply it to the bubble container.
mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
if (mDesaturateAndDarkenTargetView != null) {
mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
if (mDismissView != null) {
mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f);
mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f);
final float scaleValue = Math.max(animatedValue, sizePercent);
mDismissView.getCircle().setScaleX(scaleValue);
mDismissView.getCircle().setScaleY(scaleValue);
}
if (mViewBeingDismissed != null) {
mViewBeingDismissed.setAlpha(Math.max(animatedValue, 0.7f));
}
});
@@ -1048,6 +1024,23 @@ public class BubbleStackView extends FrameLayout
}
};
private void setUpDismissView() {
if (mDismissView != null) {
removeView(mDismissView);
}
mDismissView = new DismissView(getContext());
addView(mDismissView);
final ContentResolver contentResolver = getContext().getContentResolver();
final int dismissRadius = Settings.Secure.getInt(
contentResolver, "bubble_dismiss_radius", mBubbleSize * 2 /* default */);
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
// MagnetizedObjects.
mMagneticTarget = new MagnetizedObject.MagneticTarget(
mDismissView.getCircle(), dismissRadius);
}
// TODO: Create ManageMenuView and move setup / animations there
private void setUpManageMenu() {
if (mManageMenu != null) {
@@ -1217,6 +1210,7 @@ public class BubbleStackView extends FrameLayout
public void onThemeChanged() {
setUpFlyout();
setUpManageMenu();
setUpDismissView();
updateOverflow();
updateUserEdu();
updateExpandedViewTheme();
@@ -1256,6 +1250,7 @@ public class BubbleStackView extends FrameLayout
updateOverflow();
setUpManageMenu();
setUpFlyout();
setUpDismissView();
mBubbleSize = mPositioner.getBubbleSize();
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getIconView() == null) {
@@ -2264,42 +2259,46 @@ public class BubbleStackView extends FrameLayout
}
}
/** Prepares and starts the desaturate/darken animation on the bubble stack. */
private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
mDesaturateAndDarkenTargetView = targetView;
/** Prepares and starts the dismiss animation on the bubble stack. */
private void animateDismissBubble(View targetView, boolean applyAlpha) {
mViewBeingDismissed = targetView;
if (mDesaturateAndDarkenTargetView == null) {
if (mViewBeingDismissed == null) {
return;
}
if (desaturateAndDarken) {
// Use the animated paint for the bubbles.
mDesaturateAndDarkenTargetView.setLayerType(
View.LAYER_TYPE_HARDWARE, mDesaturateAndDarkenPaint);
mDesaturateAndDarkenAnimator.removeAllListeners();
mDesaturateAndDarkenAnimator.start();
if (applyAlpha) {
mDismissBubbleAnimator.removeAllListeners();
mDismissBubbleAnimator.start();
} else {
mDesaturateAndDarkenAnimator.removeAllListeners();
mDesaturateAndDarkenAnimator.addListener(new AnimatorListenerAdapter() {
mDismissBubbleAnimator.removeAllListeners();
mDismissBubbleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
// Stop using the animated paint.
resetDesaturationAndDarken();
resetDismissAnimator();
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
resetDismissAnimator();
}
});
mDesaturateAndDarkenAnimator.reverse();
mDismissBubbleAnimator.reverse();
}
}
private void resetDesaturationAndDarken() {
private void resetDismissAnimator() {
mDismissBubbleAnimator.removeAllListeners();
mDismissBubbleAnimator.cancel();
mDesaturateAndDarkenAnimator.removeAllListeners();
mDesaturateAndDarkenAnimator.cancel();
if (mDesaturateAndDarkenTargetView != null) {
mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null);
mDesaturateAndDarkenTargetView = null;
if (mViewBeingDismissed != null) {
mViewBeingDismissed.setAlpha(1f);
mViewBeingDismissed = null;
}
if (mDismissView != null) {
mDismissView.getCircle().setScaleX(1f);
mDismissView.getCircle().setScaleY(1f);
}
}

View File

@@ -67,8 +67,6 @@ class DismissView(context: Context) : FrameLayout(context) {
fun show() {
if (isShowing) return
isShowing = true
bringToFront()
setZ(Short.MAX_VALUE - 1f)
setVisibility(View.VISIBLE)
(getBackground() as TransitionDrawable).startTransition(DISMISS_SCRIM_FADE_MS)
animator.cancel()