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