blob: 7b5a70eb543042e6ec74ae32ca97f176574644ec [file] [log] [blame]
Selim Cinek4ffd6362015-12-29 15:12:23 +01001/*
2 * Copyright (C) 2016 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.statusbar;
18
Selim Cinek8f2f6a62016-02-23 19:56:31 -080019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
Selim Cinek4ffd6362015-12-29 15:12:23 +010022import android.util.ArrayMap;
Adrian Roos75524412016-06-21 11:56:57 -070023import android.util.ArraySet;
Selim Cinek4ffd6362015-12-29 15:12:23 +010024import android.view.View;
Selim Cinek646d2052016-01-03 14:42:02 +080025import android.view.ViewGroup;
Selim Cinek5be6f332017-01-19 11:54:04 -080026import android.view.animation.Interpolator;
Selim Cinek4ffd6362015-12-29 15:12:23 +010027
Selim Cinek8f2f6a62016-02-23 19:56:31 -080028import com.android.systemui.Interpolators;
Selim Cinek646d2052016-01-03 14:42:02 +080029import com.android.systemui.R;
Selim Cinek4ffd6362015-12-29 15:12:23 +010030import com.android.systemui.statusbar.notification.TransformState;
Rohan Shah20790b82018-07-02 17:21:04 -070031import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
Selim Cinek4ffd6362015-12-29 15:12:23 +010032
Selim Cinek646d2052016-01-03 14:42:02 +080033import java.util.Stack;
34
Selim Cinek4ffd6362015-12-29 15:12:23 +010035/**
36 * A view that can be transformed to and from.
37 */
Selim Cinek1d6b50e2017-10-27 16:10:57 -070038public class ViewTransformationHelper implements TransformableView,
39 TransformState.TransformInfo {
Selim Cinek646d2052016-01-03 14:42:02 +080040
41 private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
42
Selim Cinek4ffd6362015-12-29 15:12:23 +010043 private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
Selim Cinekfd3e2622016-01-12 16:02:42 -080044 private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
Selim Cinek8f2f6a62016-02-23 19:56:31 -080045 private ValueAnimator mViewTransformationAnimation;
Selim Cinek4ffd6362015-12-29 15:12:23 +010046
47 public void addTransformedView(int key, View transformedView) {
48 mTransformedViews.put(key, transformedView);
49 }
50
51 public void reset() {
52 mTransformedViews.clear();
53 }
54
Selim Cinekfd3e2622016-01-12 16:02:42 -080055 public void setCustomTransformation(CustomTransformation transformation, int viewType) {
56 mCustomTransformations.put(viewType, transformation);
57 }
58
Selim Cinek4ffd6362015-12-29 15:12:23 +010059 @Override
60 public TransformState getCurrentState(int fadingView) {
61 View view = mTransformedViews.get(fadingView);
62 if (view != null && view.getVisibility() != View.GONE) {
Selim Cinek1d6b50e2017-10-27 16:10:57 -070063 return TransformState.createFrom(view, this);
Selim Cinek4ffd6362015-12-29 15:12:23 +010064 }
65 return null;
66 }
67
68 @Override
Selim Cinek8f2f6a62016-02-23 19:56:31 -080069 public void transformTo(final TransformableView notification, final Runnable endRunnable) {
70 if (mViewTransformationAnimation != null) {
71 mViewTransformationAnimation.cancel();
72 }
73 mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
74 mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
75 @Override
76 public void onAnimationUpdate(ValueAnimator animation) {
77 transformTo(notification, animation.getAnimatedFraction());
78 }
79 });
80 mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
81 mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
Selim Cinek51d94912016-03-02 15:34:28 -080082 mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
83 public boolean mCancelled;
Selim Cinek8f2f6a62016-02-23 19:56:31 -080084
Selim Cinek51d94912016-03-02 15:34:28 -080085 @Override
86 public void onAnimationEnd(Animator animation) {
87 if (!mCancelled) {
88 if (endRunnable != null) {
89 endRunnable.run();
Selim Cinek8f2f6a62016-02-23 19:56:31 -080090 }
Selim Cinek51d94912016-03-02 15:34:28 -080091 setVisible(false);
Selim Cinek1d6b50e2017-10-27 16:10:57 -070092 mViewTransformationAnimation = null;
Selim Cinek51d94912016-03-02 15:34:28 -080093 } else {
94 abortTransformations();
Selim Cinek8f2f6a62016-02-23 19:56:31 -080095 }
Selim Cinek51d94912016-03-02 15:34:28 -080096 }
Selim Cinek8f2f6a62016-02-23 19:56:31 -080097
Selim Cinek51d94912016-03-02 15:34:28 -080098 @Override
99 public void onAnimationCancel(Animator animation) {
100 mCancelled = true;
101 }
102 });
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800103 mViewTransformationAnimation.start();
104 }
105
106 @Override
107 public void transformTo(TransformableView notification, float transformationAmount) {
Selim Cinek4ffd6362015-12-29 15:12:23 +0100108 for (Integer viewType : mTransformedViews.keySet()) {
109 TransformState ownState = getCurrentState(viewType);
110 if (ownState != null) {
Selim Cinekfd3e2622016-01-12 16:02:42 -0800111 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
112 if (customTransformation != null && customTransformation.transformTo(
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800113 ownState, notification, transformationAmount)) {
Selim Cinekfd3e2622016-01-12 16:02:42 -0800114 ownState.recycle();
Selim Cinekfd3e2622016-01-12 16:02:42 -0800115 continue;
116 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100117 TransformState otherState = notification.getCurrentState(viewType);
118 if (otherState != null) {
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800119 ownState.transformViewTo(otherState, transformationAmount);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100120 otherState.recycle();
Selim Cinek4ffd6362015-12-29 15:12:23 +0100121 } else {
Selim Cinek8a71ad02016-09-07 20:00:25 -0700122 ownState.disappear(transformationAmount, notification);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100123 }
124 ownState.recycle();
125 }
126 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100127 }
128
129 @Override
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800130 public void transformFrom(final TransformableView notification) {
131 if (mViewTransformationAnimation != null) {
132 mViewTransformationAnimation.cancel();
133 }
134 mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
135 mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
136 @Override
137 public void onAnimationUpdate(ValueAnimator animation) {
138 transformFrom(notification, animation.getAnimatedFraction());
139 }
140 });
141 mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
142 public boolean mCancelled;
143
144 @Override
145 public void onAnimationEnd(Animator animation) {
146 if (!mCancelled) {
147 setVisible(true);
148 } else {
149 abortTransformations();
150 }
151 }
152
153 @Override
154 public void onAnimationCancel(Animator animation) {
155 mCancelled = true;
156 }
157 });
158 mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
159 mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
160 mViewTransformationAnimation.start();
161 }
162
163 @Override
164 public void transformFrom(TransformableView notification, float transformationAmount) {
Selim Cinek4ffd6362015-12-29 15:12:23 +0100165 for (Integer viewType : mTransformedViews.keySet()) {
166 TransformState ownState = getCurrentState(viewType);
167 if (ownState != null) {
Selim Cinekfd3e2622016-01-12 16:02:42 -0800168 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
169 if (customTransformation != null && customTransformation.transformFrom(
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800170 ownState, notification, transformationAmount)) {
Selim Cinekfd3e2622016-01-12 16:02:42 -0800171 ownState.recycle();
172 continue;
173 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100174 TransformState otherState = notification.getCurrentState(viewType);
175 if (otherState != null) {
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800176 ownState.transformViewFrom(otherState, transformationAmount);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100177 otherState.recycle();
178 } else {
Selim Cinek8a71ad02016-09-07 20:00:25 -0700179 ownState.appear(transformationAmount, notification);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100180 }
181 ownState.recycle();
182 }
183 }
184 }
185
186 @Override
187 public void setVisible(boolean visible) {
Selim Cinekd607daf2016-03-11 12:34:13 -0800188 if (mViewTransformationAnimation != null) {
189 mViewTransformationAnimation.cancel();
190 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100191 for (Integer viewType : mTransformedViews.keySet()) {
192 TransformState ownState = getCurrentState(viewType);
193 if (ownState != null) {
Adrian Roos75524412016-06-21 11:56:57 -0700194 ownState.setVisible(visible, false /* force */);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100195 ownState.recycle();
196 }
197 }
198 }
Selim Cinek646d2052016-01-03 14:42:02 +0800199
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800200 private void abortTransformations() {
201 for (Integer viewType : mTransformedViews.keySet()) {
202 TransformState ownState = getCurrentState(viewType);
203 if (ownState != null) {
204 ownState.abortTransformation();
205 ownState.recycle();
206 }
207 }
208 }
209
Selim Cinek646d2052016-01-03 14:42:02 +0800210 /**
211 * Add the remaining transformation views such that all views are being transformed correctly
212 * @param viewRoot the root below which all elements need to be transformed
213 */
214 public void addRemainingTransformTypes(View viewRoot) {
215 // lets now tag the right views
216 int numValues = mTransformedViews.size();
217 for (int i = 0; i < numValues; i++) {
218 View view = mTransformedViews.valueAt(i);
219 while (view != viewRoot.getParent()) {
220 view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
221 view = (View) view.getParent();
222 }
223 }
224 Stack<View> stack = new Stack<>();
225 // Add the right views now
226 stack.push(viewRoot);
227 while (!stack.isEmpty()) {
228 View child = stack.pop();
Selim Cinek646d2052016-01-03 14:42:02 +0800229 Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
230 if (containsView == null) {
231 // This one is unhandled, let's add it to our list.
232 int id = child.getId();
233 if (id != View.NO_ID) {
234 // We only fade views with an id
235 addTransformedView(id, child);
236 continue;
237 }
238 }
239 child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null);
240 if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){
241 ViewGroup group = (ViewGroup) child;
242 for (int i = 0; i < group.getChildCount(); i++) {
243 stack.push(group.getChildAt(i));
244 }
245 }
246 }
247 }
Selim Cinekfd3e2622016-01-12 16:02:42 -0800248
Adrian Roos75524412016-06-21 11:56:57 -0700249 public void resetTransformedView(View view) {
Selim Cinek1d6b50e2017-10-27 16:10:57 -0700250 TransformState state = TransformState.createFrom(view, this);
Adrian Roos75524412016-06-21 11:56:57 -0700251 state.setVisible(true /* visible */, true /* force */);
252 state.recycle();
253 }
254
255 /**
256 * @return a set of all views are being transformed.
257 */
258 public ArraySet<View> getAllTransformingViews() {
259 return new ArraySet<>(mTransformedViews.values());
260 }
261
Selim Cinek1d6b50e2017-10-27 16:10:57 -0700262 @Override
263 public boolean isAnimating() {
264 return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
265 }
266
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800267 public static abstract class CustomTransformation {
Selim Cinekfd3e2622016-01-12 16:02:42 -0800268 /**
269 * Transform a state to the given view
270 * @param ownState the state to transform
271 * @param notification the view to transform to
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800272 * @param transformationAmount how much transformation should be done
Selim Cinekfd3e2622016-01-12 16:02:42 -0800273 * @return whether a custom transformation is performed
274 */
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800275 public abstract boolean transformTo(TransformState ownState,
276 TransformableView notification,
277 float transformationAmount);
Selim Cinekfd3e2622016-01-12 16:02:42 -0800278
279 /**
280 * Transform to this state from the given view
281 * @param ownState the state to transform to
282 * @param notification the view to transform from
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800283 * @param transformationAmount how much transformation should be done
Selim Cinekfd3e2622016-01-12 16:02:42 -0800284 * @return whether a custom transformation is performed
285 */
Selim Cinek8f2f6a62016-02-23 19:56:31 -0800286 public abstract boolean transformFrom(TransformState ownState,
287 TransformableView notification,
288 float transformationAmount);
289
290 /**
291 * Perform a custom initialisation before transforming.
292 *
293 * @param ownState our own state
294 * @param otherState the other state
295 * @return whether a custom initialization is done
296 */
297 public boolean initTransformation(TransformState ownState,
298 TransformState otherState) {
299 return false;
300 }
301
302 public boolean customTransformTarget(TransformState ownState,
303 TransformState otherState) {
304 return false;
305 }
Selim Cinek5be6f332017-01-19 11:54:04 -0800306
307 /**
308 * Get a custom interpolator for this animation
309 * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY
310 * @param isFrom true if this transformation from the other view
311 */
312 public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) {
313 return null;
314 }
Selim Cinekfd3e2622016-01-12 16:02:42 -0800315 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100316}