Home | History | Annotate | Download | only in bubbles
      1 /*
      2  * Copyright (C) 2019 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.systemui.bubbles;
     18 
     19 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
     20 
     21 import android.content.Context;
     22 import android.graphics.drawable.Drawable;
     23 import android.view.Gravity;
     24 import android.view.LayoutInflater;
     25 import android.view.View;
     26 import android.view.animation.AccelerateDecelerateInterpolator;
     27 import android.widget.FrameLayout;
     28 import android.widget.ImageView;
     29 import android.widget.LinearLayout;
     30 import android.widget.TextView;
     31 
     32 import androidx.dynamicanimation.animation.DynamicAnimation;
     33 import androidx.dynamicanimation.animation.SpringAnimation;
     34 import androidx.dynamicanimation.animation.SpringForce;
     35 
     36 import com.android.systemui.R;
     37 
     38 /** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
     39 public class BubbleDismissView extends FrameLayout {
     40     /** Duration for animations involving the dismiss target text/icon/gradient. */
     41     private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
     42 
     43     private View mDismissGradient;
     44 
     45     private LinearLayout mDismissTarget;
     46     private ImageView mDismissIcon;
     47     private TextView mDismissText;
     48     private View mDismissCircle;
     49 
     50     private SpringAnimation mDismissTargetAlphaSpring;
     51     private SpringAnimation mDismissTargetVerticalSpring;
     52 
     53     public BubbleDismissView(Context context) {
     54         super(context);
     55         setVisibility(GONE);
     56 
     57         mDismissGradient = new FrameLayout(mContext);
     58 
     59         FrameLayout.LayoutParams gradientParams =
     60                 new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
     61         gradientParams.gravity = Gravity.BOTTOM;
     62         mDismissGradient.setLayoutParams(gradientParams);
     63 
     64         Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
     65         gradient.setAlpha((int) (255 * 0.85f));
     66         mDismissGradient.setBackground(gradient);
     67 
     68         mDismissGradient.setVisibility(GONE);
     69         addView(mDismissGradient);
     70 
     71         LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true);
     72         mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container);
     73         mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon);
     74         mDismissText = findViewById(R.id.bubble_dismiss_text);
     75         mDismissCircle = findViewById(R.id.bubble_dismiss_circle);
     76 
     77         // Set up the basic target area animations. These are very simple animations that don't need
     78         // fancy interpolators.
     79         final AccelerateDecelerateInterpolator interpolator =
     80                 new AccelerateDecelerateInterpolator();
     81         mDismissGradient.animate()
     82                 .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
     83                 .setInterpolator(interpolator);
     84         mDismissText.animate()
     85                 .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
     86                 .setInterpolator(interpolator);
     87         mDismissIcon.animate()
     88                 .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
     89                 .setInterpolator(interpolator);
     90         mDismissCircle.animate()
     91                 .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION / 2)
     92                 .setInterpolator(interpolator);
     93 
     94         mDismissTargetAlphaSpring =
     95                 new SpringAnimation(mDismissTarget, DynamicAnimation.ALPHA)
     96                         .setSpring(new SpringForce()
     97                                 .setStiffness(SpringForce.STIFFNESS_LOW)
     98                                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
     99         mDismissTargetVerticalSpring =
    100                 new SpringAnimation(mDismissTarget, DynamicAnimation.TRANSLATION_Y)
    101                         .setSpring(new SpringForce()
    102                                 .setStiffness(SpringForce.STIFFNESS_MEDIUM)
    103                                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
    104 
    105         mDismissTargetAlphaSpring.addEndListener((anim, canceled, alpha, velocity) -> {
    106             // Since DynamicAnimations end when they're 'nearly' done, we can't rely on alpha being
    107             // exactly zero when this listener is triggered. However, if it's less than 50% we can
    108             // safely assume it was animating out rather than in.
    109             if (alpha < 0.5f) {
    110                 // If the alpha spring was animating the view out, set it to GONE when it's done.
    111                 setVisibility(GONE);
    112             }
    113         });
    114     }
    115 
    116     /** Springs in the dismiss target and fades in the gradient. */
    117     void springIn() {
    118         setVisibility(View.VISIBLE);
    119 
    120         // Fade in the dismiss target (icon + text).
    121         mDismissTarget.setAlpha(0f);
    122         mDismissTargetAlphaSpring.animateToFinalPosition(1f);
    123 
    124         // Spring up the dismiss target (icon + text).
    125         mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
    126         mDismissTargetVerticalSpring.animateToFinalPosition(0);
    127 
    128         // Fade in the gradient.
    129         mDismissGradient.setVisibility(VISIBLE);
    130         mDismissGradient.animate().alpha(1f);
    131 
    132         // Make sure the dismiss elements are in the separated position (in case we hid the target
    133         // while they were condensed to cover the bubbles being in the target).
    134         mDismissIcon.setAlpha(1f);
    135         mDismissIcon.setScaleX(1f);
    136         mDismissIcon.setScaleY(1f);
    137         mDismissIcon.setTranslationX(0f);
    138         mDismissText.setAlpha(1f);
    139         mDismissText.setTranslationX(0f);
    140     }
    141 
    142     /** Springs out the dismiss target and fades out the gradient. */
    143     void springOut() {
    144         // Fade out the target.
    145         mDismissTargetAlphaSpring.animateToFinalPosition(0f);
    146 
    147         // Spring the target down a bit.
    148         mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);
    149 
    150         // Fade out the gradient and then set it to GONE so it's not in the SBV hierarchy.
    151         mDismissGradient.animate().alpha(0f).withEndAction(
    152                 () -> mDismissGradient.setVisibility(GONE));
    153 
    154         // Pop out the dismiss circle.
    155         mDismissCircle.animate().alpha(0f).scaleX(1.2f).scaleY(1.2f);
    156     }
    157 
    158     /**
    159      * Encircles the center of the dismiss target, pulling the X towards the center and hiding the
    160      * text.
    161      */
    162     void animateEncircleCenterWithX(boolean encircle) {
    163         // Pull the text towards the center if we're encircling (it'll be faded out, leaving only
    164         // the X icon over the bubbles), or back to normal if we're un-encircling.
    165         final float textTranslation = encircle
    166                 ? -mDismissIcon.getWidth() / 4f
    167                 : 0f;
    168 
    169         // Center the icon if we're encircling, or put it back to normal if not.
    170         final float iconTranslation = encircle
    171                 ? mDismissTarget.getWidth() / 2f
    172                 - mDismissIcon.getWidth() / 2f
    173                 - mDismissIcon.getLeft()
    174                 : 0f;
    175 
    176         // Fade in/out the text and translate it.
    177         mDismissText.animate()
    178                 .alpha(encircle ? 0f : 1f)
    179                 .translationX(textTranslation);
    180 
    181         mDismissIcon.animate()
    182                 .setDuration(150)
    183                 .translationX(iconTranslation);
    184 
    185         // Fade out the gradient if we're encircling (the bubbles will 'absorb' it by darkening
    186         // themselves).
    187         mDismissGradient.animate()
    188                 .alpha(encircle ? 0f : 1f);
    189 
    190         // Prepare the circle to be 'dropped in'.
    191         if (encircle) {
    192             mDismissCircle.setAlpha(0f);
    193             mDismissCircle.setScaleX(1.2f);
    194             mDismissCircle.setScaleY(1.2f);
    195         }
    196 
    197         // Drop in the circle, or pull it back up.
    198         mDismissCircle.animate()
    199                 .alpha(encircle ? 1f : 0f)
    200                 .scaleX(encircle ? 1f : 0f)
    201                 .scaleY(encircle ? 1f : 0f);
    202     }
    203 
    204     /** Animates the circle and the centered icon out. */
    205     void animateEncirclingCircleDisappearance() {
    206         // Pop out the dismiss icon and circle.
    207         mDismissIcon.animate()
    208                 .setDuration(50)
    209                 .scaleX(0.9f)
    210                 .scaleY(0.9f)
    211                 .alpha(0f);
    212         mDismissCircle.animate()
    213                 .scaleX(0.9f)
    214                 .scaleY(0.9f)
    215                 .alpha(0f);
    216     }
    217 
    218     /** Returns the Y value of the center of the dismiss target. */
    219     float getDismissTargetCenterY() {
    220         return getTop() + mDismissTarget.getTop() + mDismissTarget.getHeight() / 2f;
    221     }
    222 
    223     /** Returns the dismiss target, which contains the text/icon and any added padding. */
    224     View getDismissTarget() {
    225         return mDismissTarget;
    226     }
    227 }
    228