Merge "Move dismiss target from stack to subview"
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user