Merge "Move dismiss target from stack to subview"

This commit is contained in:
Lyn Han
2020-07-23 18:05:33 +00:00
committed by Android (Google) Code Review
3 changed files with 100 additions and 246 deletions

View File

@@ -1,148 +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.
*/
package com.android.systemui.bubbles;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.R;
/** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
public class BubbleDismissView extends FrameLayout {
/** Duration for animations involving the dismiss target text/icon. */
private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
private static final float SCALE_FOR_POP = 1.2f;
private static final float SCALE_FOR_DISMISS = 0.9f;
private LinearLayout mDismissTarget;
private ImageView mDismissIcon;
private View mDismissCircle;
private SpringAnimation mDismissTargetAlphaSpring;
private SpringAnimation mDismissTargetVerticalSpring;
public BubbleDismissView(Context context) {
super(context);
setVisibility(GONE);
LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true);
mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container);
mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon);
mDismissCircle = findViewById(R.id.bubble_dismiss_circle);
// Set up the basic target area animations. These are very simple animations that don't need
// fancy interpolators.
final AccelerateDecelerateInterpolator interpolator =
new AccelerateDecelerateInterpolator();
mDismissIcon.animate()
.setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
.setInterpolator(interpolator);
mDismissCircle.animate()
.setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION / 2)
.setInterpolator(interpolator);
mDismissTargetAlphaSpring =
new SpringAnimation(mDismissTarget, DynamicAnimation.ALPHA)
.setSpring(new SpringForce()
.setStiffness(SpringForce.STIFFNESS_LOW)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mDismissTargetVerticalSpring =
new SpringAnimation(mDismissTarget, DynamicAnimation.TRANSLATION_Y)
.setSpring(new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mDismissTargetAlphaSpring.addEndListener((anim, canceled, alpha, velocity) -> {
// Since DynamicAnimations end when they're 'nearly' done, we can't rely on alpha being
// exactly zero when this listener is triggered. However, if it's less than 50% we can
// safely assume it was animating out rather than in.
if (alpha < 0.5f) {
// If the alpha spring was animating the view out, set it to GONE when it's done.
setVisibility(INVISIBLE);
}
});
}
/** Springs in the dismiss target. */
void springIn() {
setVisibility(View.VISIBLE);
// Fade in the dismiss target icon.
mDismissIcon.animate()
.setDuration(50)
.scaleX(1f)
.scaleY(1f)
.alpha(1f);
mDismissTarget.setAlpha(0f);
mDismissTargetAlphaSpring.animateToFinalPosition(1f);
// Spring up the dismiss target.
mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
mDismissTargetVerticalSpring.animateToFinalPosition(0);
mDismissCircle.setAlpha(0f);
mDismissCircle.setScaleX(SCALE_FOR_POP);
mDismissCircle.setScaleY(SCALE_FOR_POP);
// Fade in circle and reduce size.
mDismissCircle.animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f);
}
/** Springs out the dismiss target. */
void springOut() {
// Fade out the target icon.
mDismissIcon.animate()
.setDuration(50)
.scaleX(SCALE_FOR_DISMISS)
.scaleY(SCALE_FOR_DISMISS)
.alpha(0f);
// Fade out the target.
mDismissTargetAlphaSpring.animateToFinalPosition(0f);
// Spring the target down a bit.
mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);
// Pop out the circle.
mDismissCircle.animate()
.scaleX(SCALE_FOR_DISMISS)
.scaleY(SCALE_FOR_DISMISS)
.alpha(0f);
}
/** Returns the Y value of the center of the dismiss target. */
float getDismissTargetCenterY() {
return getTop() + mDismissTarget.getTop() + mDismissTarget.getHeight() / 2f;
}
/** Returns the dismiss target, which contains the text/icon and any added padding. */
View getDismissTarget() {
return mDismissTarget;
}
}

View File

@@ -48,7 +48,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.TransitionDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
@@ -95,7 +94,6 @@ import com.android.systemui.model.SysUiState;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.util.RelativeTouchListener;
import com.android.systemui.util.animation.PhysicsAnimator;
@@ -139,9 +137,6 @@ public class BubbleStackView extends FrameLayout
/** Percent to darken the bubbles when they're in the dismiss target. */
private static final float DARKEN_PERCENT = 0.3f;
/** Duration of the dismiss scrim fading in/out. */
private static final int DISMISS_TRANSITION_DURATION_MS = 200;
/** How long to wait, in milliseconds, before hiding the flyout. */
@VisibleForTesting
static final int FLYOUT_HIDE_AFTER = 5000;
@@ -300,7 +295,7 @@ public class BubbleStackView extends FrameLayout
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Stack view state:");
pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress);
pw.print(" showingDismiss: "); pw.println(mShowingDismiss);
pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing());
pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
pw.print(" expandedContainerVis: "); pw.println(mExpandedViewContainer.getVisibility());
pw.print(" expandedContainerAlpha: "); pw.println(mExpandedViewContainer.getAlpha());
@@ -347,7 +342,6 @@ public class BubbleStackView extends FrameLayout
private boolean mViewUpdatedRequested = false;
private boolean mIsExpansionAnimating = false;
private boolean mIsBubbleSwitchAnimating = false;
private boolean mShowingDismiss = false;
/** The view to desaturate/darken when magneted to the dismiss target. */
@Nullable private View mDesaturateAndDarkenTargetView;
@@ -465,7 +459,7 @@ public class BubbleStackView extends FrameLayout
if (wasFlungOut) {
mExpandedAnimationController.snapBubbleBack(
mExpandedAnimationController.getDraggedOutBubble(), velX, velY);
hideDismissTarget();
mDismissView.hide();
} else {
mExpandedAnimationController.onUnstuckFromTarget();
}
@@ -479,9 +473,9 @@ public class BubbleStackView extends FrameLayout
mExpandedAnimationController.dismissDraggedOutBubble(
mExpandedAnimationController.getDraggedOutBubble() /* bubble */,
mDismissTargetContainer.getHeight() /* translationYBy */,
mDismissView.getHeight() /* translationYBy */,
BubbleStackView.this::dismissMagnetizedObject /* after */);
hideDismissTarget();
mDismissView.hide();
}
};
@@ -502,7 +496,7 @@ public class BubbleStackView extends FrameLayout
if (wasFlungOut) {
mStackAnimationController.flingStackThenSpringToEdge(
mStackAnimationController.getStackPosition().x, velX, velY);
hideDismissTarget();
mDismissView.hide();
} else {
mStackAnimationController.onUnstuckFromTarget();
}
@@ -511,14 +505,14 @@ public class BubbleStackView extends FrameLayout
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
mStackAnimationController.animateStackDismissal(
mDismissTargetContainer.getHeight() /* translationYBy */,
mDismissView.getHeight() /* translationYBy */,
() -> {
resetDesaturationAndDarken();
dismissMagnetizedObject();
}
);
hideDismissTarget();
mDismissView.hide();
}
};
@@ -639,7 +633,7 @@ public class BubbleStackView extends FrameLayout
}
// Show the dismiss target, if we haven't already.
springInDismissTargetMaybe();
mDismissView.show();
// First, see if the magnetized object consumes the event - if so, we shouldn't move the
// bubble since it's stuck to the target.
@@ -681,7 +675,7 @@ public class BubbleStackView extends FrameLayout
SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
}
hideDismissTarget();
mDismissView.hide();
}
mIsDraggingStack = false;
@@ -743,12 +737,7 @@ public class BubbleStackView extends FrameLayout
}
};
private View mDismissTargetCircle;
private ViewGroup mDismissTargetContainer;
private PhysicsAnimator<View> mDismissTargetAnimator;
private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig(
SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
private DismissView mDismissView;
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
@Nullable
@@ -866,34 +855,8 @@ public class BubbleStackView extends FrameLayout
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
mDismissTargetCircle = new DismissCircleView(context);
final FrameLayout.LayoutParams newParams =
new FrameLayout.LayoutParams(targetSize, targetSize);
newParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
mDismissTargetCircle.setLayoutParams(newParams);
mDismissTargetAnimator = PhysicsAnimator.getInstance(mDismissTargetCircle);
mDismissTargetContainer = new FrameLayout(context);
mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams(
MATCH_PARENT,
getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
Gravity.BOTTOM));
final int bottomMargin =
getResources().getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin);
mDismissTargetContainer.setPadding(0, 0, 0, bottomMargin);
mDismissTargetContainer.setClipToPadding(false);
mDismissTargetContainer.setClipChildren(false);
mDismissTargetContainer.addView(mDismissTargetCircle);
mDismissTargetContainer.setVisibility(View.INVISIBLE);
mDismissTargetContainer.setBackgroundResource(
R.drawable.floating_dismiss_gradient_transition);
addView(mDismissTargetContainer);
// Start translated down so the target springs up.
mDismissTargetCircle.setTranslationY(
getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height));
mDismissView = new DismissView(context);
addView(mDismissView);
final ContentResolver contentResolver = getContext().getContentResolver();
final int dismissRadius = Settings.Secure.getInt(
@@ -901,7 +864,8 @@ public class BubbleStackView extends FrameLayout
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
// MagnetizedObjects.
mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius);
mMagneticTarget = new MagnetizedObject.MagneticTarget(
mDismissView.getCircle(), dismissRadius);
setClipChildren(false);
setFocusable(true);
@@ -1277,12 +1241,7 @@ public class BubbleStackView extends FrameLayout
}
mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
mStackAnimationController.updateResources(mOrientation);
final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
mDismissTargetCircle.getLayoutParams().width = targetSize;
mDismissTargetCircle.getLayoutParams().height = targetSize;
mDismissTargetCircle.requestLayout();
mDismissView.updateResources();
mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2);
}
@@ -2360,48 +2319,6 @@ public class BubbleStackView extends FrameLayout
}
}
/** Animates in the dismiss target. */
private void springInDismissTargetMaybe() {
if (mShowingDismiss) {
return;
}
mShowingDismiss = true;
mDismissTargetContainer.bringToFront();
mDismissTargetContainer.setZ(Short.MAX_VALUE - 1);
mDismissTargetContainer.setVisibility(VISIBLE);
((TransitionDrawable) mDismissTargetContainer.getBackground()).startTransition(
DISMISS_TRANSITION_DURATION_MS);
mDismissTargetAnimator.cancel();
mDismissTargetAnimator
.spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring)
.start();
}
/**
* Animates the dismiss target out, as well as the circle that encircles the bubbles, if they
* were dragged into the target and encircled.
*/
private void hideDismissTarget() {
if (!mShowingDismiss) {
return;
}
mShowingDismiss = false;
((TransitionDrawable) mDismissTargetContainer.getBackground()).reverseTransition(
DISMISS_TRANSITION_DURATION_MS);
mDismissTargetAnimator
.spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(),
mDismissTargetSpring)
.withEndActions(() -> mDismissTargetContainer.setVisibility(View.INVISIBLE))
.start();
}
/** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
private void animateFlyoutCollapsed(boolean collapsed, float velX) {
final boolean onLeft = mStackAnimationController.isStackOnLeftSide();

View File

@@ -0,0 +1,85 @@
package com.android.systemui.bubbles
import android.content.Context
import android.graphics.drawable.TransitionDrawable
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
import com.android.systemui.R
import com.android.systemui.util.DismissCircleView
import com.android.systemui.util.animation.PhysicsAnimator
/*
* View that handles interactions between DismissCircleView and BubbleStackView.
*/
class DismissView(context: Context) : FrameLayout(context) {
var circle = DismissCircleView(context).apply {
val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
val newParams = LayoutParams(targetSize, targetSize)
newParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
setLayoutParams(newParams)
setTranslationY(
resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height).toFloat())
}
var isShowing = false
private val animator = PhysicsAnimator.getInstance(circle)
private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
private val DISMISS_SCRIM_FADE_MS = 200
init {
setLayoutParams(LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
Gravity.BOTTOM))
setPadding(0, 0, 0, resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
setClipToPadding(false)
setClipChildren(false)
setVisibility(View.INVISIBLE)
setBackgroundResource(
R.drawable.floating_dismiss_gradient_transition)
addView(circle)
}
/**
* Animates this view in.
*/
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()
animator
.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
.start()
}
/**
* Animates this view out, as well as the circle that encircles the bubbles, if they
* were dragged into the target and encircled.
*/
fun hide() {
if (!isShowing) return
isShowing = false
(getBackground() as TransitionDrawable).reverseTransition(DISMISS_SCRIM_FADE_MS)
animator
.spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
spring)
.withEndActions({ setVisibility(View.INVISIBLE) })
.start()
}
fun updateResources() {
val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
circle.layoutParams.width = targetSize
circle.layoutParams.height = targetSize
circle.requestLayout()
}
}