blob: 4db1e276f431d99615c4dd18e237a53001445cad [file] [log] [blame]
Joshua Tsuji4accf5982019-04-22 17:36:11 -04001/*
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
17package com.android.systemui.bubbles;
18
19import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20
21import android.content.Context;
22import android.graphics.drawable.Drawable;
23import android.view.Gravity;
24import android.view.LayoutInflater;
25import android.view.View;
26import android.view.animation.AccelerateDecelerateInterpolator;
27import android.widget.FrameLayout;
28import android.widget.ImageView;
29import android.widget.LinearLayout;
30import android.widget.TextView;
31
32import androidx.dynamicanimation.animation.DynamicAnimation;
33import androidx.dynamicanimation.animation.SpringAnimation;
34import androidx.dynamicanimation.animation.SpringForce;
35
36import com.android.systemui.R;
37
38/** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
39public 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}