• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BubbleDismissView(Context context)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. */
springIn()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. */
springOut()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      */
animateEncircleCenterWithX(boolean encircle)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. */
animateEncirclingCircleDisappearance()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. */
getDismissTargetCenterY()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. */
getDismissTarget()224     View getDismissTarget() {
225         return mDismissTarget;
226     }
227 }
228