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 android.content.Context;
20 import android.view.LayoutInflater;
21 import android.view.View;
22 import android.view.animation.AccelerateDecelerateInterpolator;
23 import android.widget.FrameLayout;
24 import android.widget.ImageView;
25 import android.widget.LinearLayout;
26 
27 import androidx.dynamicanimation.animation.DynamicAnimation;
28 import androidx.dynamicanimation.animation.SpringAnimation;
29 import androidx.dynamicanimation.animation.SpringForce;
30 
31 import com.android.systemui.R;
32 
33 /** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
34 public class BubbleDismissView extends FrameLayout {
35     /** Duration for animations involving the dismiss target text/icon. */
36     private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
37     private static final float SCALE_FOR_POP = 1.2f;
38     private static final float SCALE_FOR_DISMISS = 0.9f;
39 
40     private LinearLayout mDismissTarget;
41     private ImageView mDismissIcon;
42     private View mDismissCircle;
43 
44     private SpringAnimation mDismissTargetAlphaSpring;
45     private SpringAnimation mDismissTargetVerticalSpring;
46 
BubbleDismissView(Context context)47     public BubbleDismissView(Context context) {
48         super(context);
49         setVisibility(GONE);
50 
51         LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true);
52         mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container);
53         mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon);
54         mDismissCircle = findViewById(R.id.bubble_dismiss_circle);
55 
56         // Set up the basic target area animations. These are very simple animations that don't need
57         // fancy interpolators.
58         final AccelerateDecelerateInterpolator interpolator =
59                 new AccelerateDecelerateInterpolator();
60         mDismissIcon.animate()
61                 .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
62                 .setInterpolator(interpolator);
63         mDismissCircle.animate()
64                 .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION / 2)
65                 .setInterpolator(interpolator);
66 
67         mDismissTargetAlphaSpring =
68                 new SpringAnimation(mDismissTarget, DynamicAnimation.ALPHA)
69                         .setSpring(new SpringForce()
70                                 .setStiffness(SpringForce.STIFFNESS_LOW)
71                                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
72         mDismissTargetVerticalSpring =
73                 new SpringAnimation(mDismissTarget, DynamicAnimation.TRANSLATION_Y)
74                         .setSpring(new SpringForce()
75                                 .setStiffness(SpringForce.STIFFNESS_MEDIUM)
76                                 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
77 
78         mDismissTargetAlphaSpring.addEndListener((anim, canceled, alpha, velocity) -> {
79             // Since DynamicAnimations end when they're 'nearly' done, we can't rely on alpha being
80             // exactly zero when this listener is triggered. However, if it's less than 50% we can
81             // safely assume it was animating out rather than in.
82             if (alpha < 0.5f) {
83                 // If the alpha spring was animating the view out, set it to GONE when it's done.
84                 setVisibility(INVISIBLE);
85             }
86         });
87     }
88 
89     /** Springs in the dismiss target. */
springIn()90     void springIn() {
91         setVisibility(View.VISIBLE);
92 
93         // Fade in the dismiss target icon.
94         mDismissIcon.animate()
95                 .setDuration(50)
96                 .scaleX(1f)
97                 .scaleY(1f)
98                 .alpha(1f);
99         mDismissTarget.setAlpha(0f);
100         mDismissTargetAlphaSpring.animateToFinalPosition(1f);
101 
102         // Spring up the dismiss target.
103         mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
104         mDismissTargetVerticalSpring.animateToFinalPosition(0);
105 
106         mDismissCircle.setAlpha(0f);
107         mDismissCircle.setScaleX(SCALE_FOR_POP);
108         mDismissCircle.setScaleY(SCALE_FOR_POP);
109 
110         // Fade in circle and reduce size.
111         mDismissCircle.animate()
112                 .alpha(1f)
113                 .scaleX(1f)
114                 .scaleY(1f);
115     }
116 
117     /** Springs out the dismiss target. */
springOut()118     void springOut() {
119         // Fade out the target icon.
120         mDismissIcon.animate()
121                 .setDuration(50)
122                 .scaleX(SCALE_FOR_DISMISS)
123                 .scaleY(SCALE_FOR_DISMISS)
124                 .alpha(0f);
125 
126         // Fade out the target.
127         mDismissTargetAlphaSpring.animateToFinalPosition(0f);
128 
129         // Spring the target down a bit.
130         mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);
131 
132         // Pop out the circle.
133         mDismissCircle.animate()
134                 .scaleX(SCALE_FOR_DISMISS)
135                 .scaleY(SCALE_FOR_DISMISS)
136                 .alpha(0f);
137     }
138 
139     /** Returns the Y value of the center of the dismiss target. */
getDismissTargetCenterY()140     float getDismissTargetCenterY() {
141         return getTop() + mDismissTarget.getTop() + mDismissTarget.getHeight() / 2f;
142     }
143 
144     /** Returns the dismiss target, which contains the text/icon and any added padding. */
getDismissTarget()145     View getDismissTarget() {
146         return mDismissTarget;
147     }
148 }
149