• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.systemui.statusbar;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.util.ArrayMap;
23 import android.util.ArraySet;
24 import android.view.View;
25 import android.view.ViewGroup;
26 
27 import com.android.systemui.Interpolators;
28 import com.android.systemui.R;
29 import com.android.systemui.statusbar.notification.TransformState;
30 import com.android.systemui.statusbar.stack.StackStateAnimator;
31 
32 import java.util.Stack;
33 
34 /**
35  * A view that can be transformed to and from.
36  */
37 public class ViewTransformationHelper implements TransformableView {
38 
39     private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
40 
41     private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
42     private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
43     private ValueAnimator mViewTransformationAnimation;
44 
addTransformedView(int key, View transformedView)45     public void addTransformedView(int key, View transformedView) {
46         mTransformedViews.put(key, transformedView);
47     }
48 
reset()49     public void reset() {
50         mTransformedViews.clear();
51     }
52 
setCustomTransformation(CustomTransformation transformation, int viewType)53     public void setCustomTransformation(CustomTransformation transformation, int viewType) {
54         mCustomTransformations.put(viewType, transformation);
55     }
56 
57     @Override
getCurrentState(int fadingView)58     public TransformState getCurrentState(int fadingView) {
59         View view = mTransformedViews.get(fadingView);
60         if (view != null && view.getVisibility() != View.GONE) {
61             return TransformState.createFrom(view);
62         }
63         return null;
64     }
65 
66     @Override
transformTo(final TransformableView notification, final Runnable endRunnable)67     public void transformTo(final TransformableView notification, final Runnable endRunnable) {
68         if (mViewTransformationAnimation != null) {
69             mViewTransformationAnimation.cancel();
70         }
71         mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
72         mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
73             @Override
74             public void onAnimationUpdate(ValueAnimator animation) {
75                 transformTo(notification, animation.getAnimatedFraction());
76             }
77         });
78         mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
79         mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
80         mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
81             public boolean mCancelled;
82 
83             @Override
84             public void onAnimationEnd(Animator animation) {
85                 if (!mCancelled) {
86                     if (endRunnable != null) {
87                         endRunnable.run();
88                     }
89                     setVisible(false);
90                 } else {
91                     abortTransformations();
92                 }
93             }
94 
95             @Override
96             public void onAnimationCancel(Animator animation) {
97                 mCancelled = true;
98             }
99         });
100         mViewTransformationAnimation.start();
101     }
102 
103     @Override
transformTo(TransformableView notification, float transformationAmount)104     public void transformTo(TransformableView notification, float transformationAmount) {
105         for (Integer viewType : mTransformedViews.keySet()) {
106             TransformState ownState = getCurrentState(viewType);
107             if (ownState != null) {
108                 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
109                 if (customTransformation != null && customTransformation.transformTo(
110                         ownState, notification, transformationAmount)) {
111                     ownState.recycle();
112                     continue;
113                 }
114                 TransformState otherState = notification.getCurrentState(viewType);
115                 if (otherState != null) {
116                     ownState.transformViewTo(otherState, transformationAmount);
117                     otherState.recycle();
118                 } else {
119                     // there's no other view available
120                     CrossFadeHelper.fadeOut(mTransformedViews.get(viewType), transformationAmount);
121                 }
122                 ownState.recycle();
123             }
124         }
125     }
126 
127     @Override
transformFrom(final TransformableView notification)128     public void transformFrom(final TransformableView notification) {
129         if (mViewTransformationAnimation != null) {
130             mViewTransformationAnimation.cancel();
131         }
132         mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
133         mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
134             @Override
135             public void onAnimationUpdate(ValueAnimator animation) {
136                 transformFrom(notification, animation.getAnimatedFraction());
137             }
138         });
139         mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
140             public boolean mCancelled;
141 
142             @Override
143             public void onAnimationEnd(Animator animation) {
144                 if (!mCancelled) {
145                     setVisible(true);
146                 } else {
147                     abortTransformations();
148                 }
149             }
150 
151             @Override
152             public void onAnimationCancel(Animator animation) {
153                 mCancelled = true;
154             }
155         });
156         mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
157         mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
158         mViewTransformationAnimation.start();
159     }
160 
161     @Override
transformFrom(TransformableView notification, float transformationAmount)162     public void transformFrom(TransformableView notification, float transformationAmount) {
163         for (Integer viewType : mTransformedViews.keySet()) {
164             TransformState ownState = getCurrentState(viewType);
165             if (ownState != null) {
166                 CustomTransformation customTransformation = mCustomTransformations.get(viewType);
167                 if (customTransformation != null && customTransformation.transformFrom(
168                         ownState, notification, transformationAmount)) {
169                     ownState.recycle();
170                     continue;
171                 }
172                 TransformState otherState = notification.getCurrentState(viewType);
173                 if (otherState != null) {
174                     ownState.transformViewFrom(otherState, transformationAmount);
175                     otherState.recycle();
176                 } else {
177                     // There's no other view, lets fade us in
178                     // Certain views need to prepare the fade in and make sure its children are
179                     // completely visible. An example is the notification header.
180                     if (transformationAmount == 0.0f) {
181                         ownState.prepareFadeIn();
182                     }
183                     CrossFadeHelper.fadeIn(mTransformedViews.get(viewType), transformationAmount);
184                 }
185                 ownState.recycle();
186             }
187         }
188     }
189 
190     @Override
setVisible(boolean visible)191     public void setVisible(boolean visible) {
192         if (mViewTransformationAnimation != null) {
193             mViewTransformationAnimation.cancel();
194         }
195         for (Integer viewType : mTransformedViews.keySet()) {
196             TransformState ownState = getCurrentState(viewType);
197             if (ownState != null) {
198                 ownState.setVisible(visible, false /* force */);
199                 ownState.recycle();
200             }
201         }
202     }
203 
abortTransformations()204     private void abortTransformations() {
205         for (Integer viewType : mTransformedViews.keySet()) {
206             TransformState ownState = getCurrentState(viewType);
207             if (ownState != null) {
208                 ownState.abortTransformation();
209                 ownState.recycle();
210             }
211         }
212     }
213 
214     /**
215      * Add the remaining transformation views such that all views are being transformed correctly
216      * @param viewRoot the root below which all elements need to be transformed
217      */
addRemainingTransformTypes(View viewRoot)218     public void addRemainingTransformTypes(View viewRoot) {
219         // lets now tag the right views
220         int numValues = mTransformedViews.size();
221         for (int i = 0; i < numValues; i++) {
222             View view = mTransformedViews.valueAt(i);
223             while (view != viewRoot.getParent()) {
224                 view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
225                 view = (View) view.getParent();
226             }
227         }
228         Stack<View> stack = new Stack<>();
229         // Add the right views now
230         stack.push(viewRoot);
231         while (!stack.isEmpty()) {
232             View child = stack.pop();
233             if (child.getVisibility() == View.GONE) {
234                 continue;
235             }
236             Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
237             if (containsView == null) {
238                 // This one is unhandled, let's add it to our list.
239                 int id = child.getId();
240                 if (id != View.NO_ID) {
241                     // We only fade views with an id
242                     addTransformedView(id, child);
243                     continue;
244                 }
245             }
246             child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null);
247             if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){
248                 ViewGroup group = (ViewGroup) child;
249                 for (int i = 0; i < group.getChildCount(); i++) {
250                     stack.push(group.getChildAt(i));
251                 }
252             }
253         }
254     }
255 
resetTransformedView(View view)256     public void resetTransformedView(View view) {
257         TransformState state = TransformState.createFrom(view);
258         state.setVisible(true /* visible */, true /* force */);
259         state.recycle();
260     }
261 
262     /**
263      * @return a set of all views are being transformed.
264      */
getAllTransformingViews()265     public ArraySet<View> getAllTransformingViews() {
266         return new ArraySet<>(mTransformedViews.values());
267     }
268 
269     public static abstract class CustomTransformation {
270         /**
271          * Transform a state to the given view
272          * @param ownState the state to transform
273          * @param notification the view to transform to
274          * @param transformationAmount how much transformation should be done
275          * @return whether a custom transformation is performed
276          */
transformTo(TransformState ownState, TransformableView notification, float transformationAmount)277         public abstract boolean transformTo(TransformState ownState,
278                 TransformableView notification,
279                 float transformationAmount);
280 
281         /**
282          * Transform to this state from the given view
283          * @param ownState the state to transform to
284          * @param notification the view to transform from
285          * @param transformationAmount how much transformation should be done
286          * @return whether a custom transformation is performed
287          */
transformFrom(TransformState ownState, TransformableView notification, float transformationAmount)288         public abstract boolean transformFrom(TransformState ownState,
289                 TransformableView notification,
290                 float transformationAmount);
291 
292         /**
293          * Perform a custom initialisation before transforming.
294          *
295          * @param ownState our own state
296          * @param otherState the other state
297          * @return whether a custom initialization is done
298          */
initTransformation(TransformState ownState, TransformState otherState)299         public boolean initTransformation(TransformState ownState,
300                 TransformState otherState) {
301             return false;
302         }
303 
customTransformTarget(TransformState ownState, TransformState otherState)304         public boolean customTransformTarget(TransformState ownState,
305                 TransformState otherState) {
306             return false;
307         }
308     }
309 }
310