/*
|
* 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 static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
|
import android.content.Context;
|
import android.graphics.drawable.Drawable;
|
import android.view.Gravity;
|
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 android.widget.TextView;
|
|
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/gradient. */
|
private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
|
|
private View mDismissGradient;
|
|
private LinearLayout mDismissTarget;
|
private ImageView mDismissIcon;
|
private TextView mDismissText;
|
private View mDismissCircle;
|
|
private SpringAnimation mDismissTargetAlphaSpring;
|
private SpringAnimation mDismissTargetVerticalSpring;
|
|
public BubbleDismissView(Context context) {
|
super(context);
|
setVisibility(GONE);
|
|
mDismissGradient = new FrameLayout(mContext);
|
|
FrameLayout.LayoutParams gradientParams =
|
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
|
gradientParams.gravity = Gravity.BOTTOM;
|
mDismissGradient.setLayoutParams(gradientParams);
|
|
Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
|
gradient.setAlpha((int) (255 * 0.85f));
|
mDismissGradient.setBackground(gradient);
|
|
mDismissGradient.setVisibility(GONE);
|
addView(mDismissGradient);
|
|
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);
|
mDismissText = findViewById(R.id.bubble_dismiss_text);
|
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();
|
mDismissGradient.animate()
|
.setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
|
.setInterpolator(interpolator);
|
mDismissText.animate()
|
.setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
|
.setInterpolator(interpolator);
|
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(GONE);
|
}
|
});
|
}
|
|
/** Springs in the dismiss target and fades in the gradient. */
|
void springIn() {
|
setVisibility(View.VISIBLE);
|
|
// Fade in the dismiss target (icon + text).
|
mDismissTarget.setAlpha(0f);
|
mDismissTargetAlphaSpring.animateToFinalPosition(1f);
|
|
// Spring up the dismiss target (icon + text).
|
mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
|
mDismissTargetVerticalSpring.animateToFinalPosition(0);
|
|
// Fade in the gradient.
|
mDismissGradient.setVisibility(VISIBLE);
|
mDismissGradient.animate().alpha(1f);
|
|
// Make sure the dismiss elements are in the separated position (in case we hid the target
|
// while they were condensed to cover the bubbles being in the target).
|
mDismissIcon.setAlpha(1f);
|
mDismissIcon.setScaleX(1f);
|
mDismissIcon.setScaleY(1f);
|
mDismissIcon.setTranslationX(0f);
|
mDismissText.setAlpha(1f);
|
mDismissText.setTranslationX(0f);
|
}
|
|
/** Springs out the dismiss target and fades out the gradient. */
|
void springOut() {
|
// Fade out the target.
|
mDismissTargetAlphaSpring.animateToFinalPosition(0f);
|
|
// Spring the target down a bit.
|
mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);
|
|
// Fade out the gradient and then set it to GONE so it's not in the SBV hierarchy.
|
mDismissGradient.animate().alpha(0f).withEndAction(
|
() -> mDismissGradient.setVisibility(GONE));
|
|
// Pop out the dismiss circle.
|
mDismissCircle.animate().alpha(0f).scaleX(1.2f).scaleY(1.2f);
|
}
|
|
/**
|
* Encircles the center of the dismiss target, pulling the X towards the center and hiding the
|
* text.
|
*/
|
void animateEncircleCenterWithX(boolean encircle) {
|
// Pull the text towards the center if we're encircling (it'll be faded out, leaving only
|
// the X icon over the bubbles), or back to normal if we're un-encircling.
|
final float textTranslation = encircle
|
? -mDismissIcon.getWidth() / 4f
|
: 0f;
|
|
// Center the icon if we're encircling, or put it back to normal if not.
|
final float iconTranslation = encircle
|
? mDismissTarget.getWidth() / 2f
|
- mDismissIcon.getWidth() / 2f
|
- mDismissIcon.getLeft()
|
: 0f;
|
|
// Fade in/out the text and translate it.
|
mDismissText.animate()
|
.alpha(encircle ? 0f : 1f)
|
.translationX(textTranslation);
|
|
mDismissIcon.animate()
|
.setDuration(150)
|
.translationX(iconTranslation);
|
|
// Fade out the gradient if we're encircling (the bubbles will 'absorb' it by darkening
|
// themselves).
|
mDismissGradient.animate()
|
.alpha(encircle ? 0f : 1f);
|
|
// Prepare the circle to be 'dropped in'.
|
if (encircle) {
|
mDismissCircle.setAlpha(0f);
|
mDismissCircle.setScaleX(1.2f);
|
mDismissCircle.setScaleY(1.2f);
|
}
|
|
// Drop in the circle, or pull it back up.
|
mDismissCircle.animate()
|
.alpha(encircle ? 1f : 0f)
|
.scaleX(encircle ? 1f : 0f)
|
.scaleY(encircle ? 1f : 0f);
|
}
|
|
/** Animates the circle and the centered icon out. */
|
void animateEncirclingCircleDisappearance() {
|
// Pop out the dismiss icon and circle.
|
mDismissIcon.animate()
|
.setDuration(50)
|
.scaleX(0.9f)
|
.scaleY(0.9f)
|
.alpha(0f);
|
mDismissCircle.animate()
|
.scaleX(0.9f)
|
.scaleY(0.9f)
|
.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;
|
}
|
}
|