blob: 4db1e276f431d99615c4dd18e237a53001445cad [file] [log] [blame]
/*
* 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;
}
}