• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.launcher3.taskbar.bubbles;
18 
19 import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
20 import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
21 import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
22 
23 import android.content.res.Resources;
24 import android.graphics.PointF;
25 import android.view.View;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.dynamicanimation.animation.DynamicAnimation;
30 import androidx.dynamicanimation.animation.FloatPropertyCompat;
31 
32 import com.android.launcher3.R;
33 import com.android.wm.shell.animation.PhysicsAnimator;
34 import com.android.wm.shell.common.bubbles.DismissCircleView;
35 import com.android.wm.shell.common.bubbles.DismissView;
36 
37 /**
38  * The animator performs the bubble animations while dragging and coordinates bubble and dismiss
39  * view animations when it gets magnetized, released or dismissed.
40  */
41 public class BubbleDragAnimator {
42     private static final float SCALE_BUBBLE_FOCUSED = 1.2f;
43     private static final float SCALE_BUBBLE_CAPTURED = 0.9f;
44     private static final float SCALE_BUBBLE_BAR_FOCUSED = 1.1f;
45 
46     private final PhysicsAnimator.SpringConfig mDefaultConfig =
47             new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
48     private final PhysicsAnimator.SpringConfig mTranslationConfig =
49             new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_LOW_BOUNCY);
50     @NonNull
51     private final View mView;
52     @NonNull
53     private final PhysicsAnimator<View> mBubbleAnimator;
54     @Nullable
55     private DismissView mDismissView;
56     @Nullable
57     private PhysicsAnimator<DismissCircleView> mDismissAnimator;
58     private final float mBubbleFocusedScale;
59     private final float mBubbleCapturedScale;
60     private final float mDismissCapturedScale;
61 
62     /**
63      * Should be initialised for each dragged view
64      *
65      * @param view the dragged view to animate
66      */
BubbleDragAnimator(@onNull View view)67     public BubbleDragAnimator(@NonNull View view) {
68         mView = view;
69         mBubbleAnimator = PhysicsAnimator.getInstance(view);
70         mBubbleAnimator.setDefaultSpringConfig(mDefaultConfig);
71 
72         Resources resources = view.getResources();
73         final int collapsedSize = resources.getDimensionPixelSize(
74                 R.dimen.bubblebar_dismiss_target_small_size);
75         final int expandedSize = resources.getDimensionPixelSize(
76                 R.dimen.bubblebar_dismiss_target_size);
77         mDismissCapturedScale = (float) collapsedSize / expandedSize;
78 
79         if (view instanceof BubbleBarView) {
80             mBubbleFocusedScale = SCALE_BUBBLE_BAR_FOCUSED;
81             mBubbleCapturedScale = mDismissCapturedScale;
82         } else {
83             mBubbleFocusedScale = SCALE_BUBBLE_FOCUSED;
84             mBubbleCapturedScale = SCALE_BUBBLE_CAPTURED;
85         }
86     }
87 
88     /**
89      * Sets dismiss view to be animated alongside the dragged bubble
90      */
setDismissView(@onNull DismissView dismissView)91     public void setDismissView(@NonNull DismissView dismissView) {
92         mDismissView = dismissView;
93         mDismissAnimator = PhysicsAnimator.getInstance(dismissView.getCircle());
94         mDismissAnimator.setDefaultSpringConfig(mDefaultConfig);
95     }
96 
97     /**
98      * Animates the focused state of the bubble when the dragging starts
99      */
animateFocused()100     public void animateFocused() {
101         mBubbleAnimator.cancel();
102         mBubbleAnimator
103                 .spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
104                 .spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
105                 .start();
106     }
107 
108     /**
109      * Animates the dragged bubble movement back to the initial position.
110      *
111      * @param initialPosition the position to animate to
112      * @param velocity        the initial velocity to use for the spring animation
113      * @param endActions      gets called when the animation completes or gets cancelled
114      */
animateToInitialState(@onNull PointF initialPosition, @NonNull PointF velocity, @Nullable Runnable endActions)115     public void animateToInitialState(@NonNull PointF initialPosition, @NonNull PointF velocity,
116             @Nullable Runnable endActions) {
117         mBubbleAnimator.cancel();
118         mBubbleAnimator
119                 .spring(DynamicAnimation.SCALE_X, 1f)
120                 .spring(DynamicAnimation.SCALE_Y, 1f)
121                 .spring(DynamicAnimation.TRANSLATION_X, initialPosition.x, velocity.x,
122                         mTranslationConfig)
123                 .spring(DynamicAnimation.TRANSLATION_Y, initialPosition.y, velocity.y,
124                         mTranslationConfig)
125                 .addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
126                         boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
127                         boolean allRelevantPropertyAnimationsEnded) -> {
128                     if (canceled || allRelevantPropertyAnimationsEnded) {
129                         resetAnimatedViews(initialPosition);
130                         if (endActions != null) {
131                             endActions.run();
132                         }
133                     }
134                 })
135                 .start();
136     }
137 
138     /**
139      * Animates the dragged view alongside the dismiss view when it gets captured in the dismiss
140      * target area.
141      */
animateDismissCaptured()142     public void animateDismissCaptured() {
143         mBubbleAnimator.cancel();
144         mBubbleAnimator
145                 .spring(DynamicAnimation.SCALE_X, mBubbleCapturedScale)
146                 .spring(DynamicAnimation.SCALE_Y, mBubbleCapturedScale)
147                 .spring(DynamicAnimation.ALPHA, mDismissCapturedScale)
148                 .start();
149 
150         if (mDismissAnimator != null) {
151             mDismissAnimator.cancel();
152             mDismissAnimator
153                     .spring(DynamicAnimation.SCALE_X, mDismissCapturedScale)
154                     .spring(DynamicAnimation.SCALE_Y, mDismissCapturedScale)
155                     .start();
156         }
157     }
158 
159     /**
160      * Animates the dragged view alongside the dismiss view when it gets released from the dismiss
161      * target area.
162      */
animateDismissReleased()163     public void animateDismissReleased() {
164         mBubbleAnimator.cancel();
165         mBubbleAnimator
166                 .spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
167                 .spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
168                 .spring(DynamicAnimation.ALPHA, 1f)
169                 .start();
170 
171         if (mDismissAnimator != null) {
172             mDismissAnimator.cancel();
173             mDismissAnimator
174                     .spring(DynamicAnimation.SCALE_X, 1f)
175                     .spring(DynamicAnimation.SCALE_Y, 1f)
176                     .start();
177         }
178     }
179 
180     /**
181      * Animates the dragged bubble dismiss when it's released in the dismiss target area.
182      *
183      * @param initialPosition the initial position to move the bubble too after animation finishes
184      * @param endActions      gets called when the animation completes or gets cancelled
185      */
animateDismiss(@onNull PointF initialPosition, @Nullable Runnable endActions)186     public void animateDismiss(@NonNull PointF initialPosition, @Nullable Runnable endActions) {
187         float dismissHeight = mDismissView != null ? mDismissView.getHeight() : 0f;
188         float translationY = mView.getTranslationY() + dismissHeight;
189         mBubbleAnimator
190                 .spring(DynamicAnimation.TRANSLATION_Y, translationY)
191                 .spring(DynamicAnimation.SCALE_X, 0f)
192                 .spring(DynamicAnimation.SCALE_Y, 0f)
193                 .spring(DynamicAnimation.ALPHA, 0f)
194                 .addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
195                         boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
196                         boolean allRelevantPropertyAnimationsEnded) -> {
197                     if (canceled || allRelevantPropertyAnimationsEnded) {
198                         resetAnimatedViews(initialPosition);
199                         if (endActions != null) endActions.run();
200                     }
201                 })
202                 .start();
203     }
204 
205     /**
206      * Reset the animated views to the initial state
207      *
208      * @param initialPosition position of the bubble
209      */
resetAnimatedViews(@onNull PointF initialPosition)210     private void resetAnimatedViews(@NonNull PointF initialPosition) {
211         mView.setScaleX(1f);
212         mView.setScaleY(1f);
213         mView.setAlpha(1f);
214         mView.setTranslationX(initialPosition.x);
215         mView.setTranslationY(initialPosition.y);
216 
217         if (mDismissView != null) {
218             mDismissView.getCircle().setScaleX(1f);
219             mDismissView.getCircle().setScaleY(1f);
220         }
221     }
222 }
223