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