• 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.notification;
18 
19 import android.util.ArraySet;
20 import android.util.Pools;
21 import android.view.NotificationHeaderView;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.view.ViewParent;
25 import android.widget.ImageView;
26 import android.widget.ProgressBar;
27 import android.widget.TextView;
28 
29 import com.android.systemui.Interpolators;
30 import com.android.systemui.R;
31 import com.android.systemui.statusbar.CrossFadeHelper;
32 import com.android.systemui.statusbar.ExpandableNotificationRow;
33 import com.android.systemui.statusbar.ViewTransformationHelper;
34 
35 /**
36  * A transform state of a view.
37 */
38 public class TransformState {
39 
40     private static final float UNDEFINED = -1f;
41     private static final int TRANSOFORM_X = 0x1;
42     private static final int TRANSOFORM_Y = 0x10;
43     private static final int TRANSOFORM_ALL = TRANSOFORM_X | TRANSOFORM_Y;
44     private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
45     private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
46     private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
47     private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
48     private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
49     private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
50     private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
51     private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
52 
53     protected View mTransformedView;
54     private int[] mOwnPosition = new int[2];
55     private float mTransformationEndY = UNDEFINED;
56     private float mTransformationEndX = UNDEFINED;
57 
initFrom(View view)58     public void initFrom(View view) {
59         mTransformedView = view;
60     }
61 
62     /**
63      * Transforms the {@link #mTransformedView} from the given transformviewstate
64      * @param otherState the state to transform from
65      * @param transformationAmount how much to transform
66      */
transformViewFrom(TransformState otherState, float transformationAmount)67     public void transformViewFrom(TransformState otherState, float transformationAmount) {
68         mTransformedView.animate().cancel();
69         if (sameAs(otherState)) {
70             if (mTransformedView.getVisibility() == View.INVISIBLE) {
71                 // We have the same content, lets show ourselves
72                 mTransformedView.setAlpha(1.0f);
73                 mTransformedView.setVisibility(View.VISIBLE);
74             }
75         } else {
76             CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
77         }
78         transformViewFullyFrom(otherState, transformationAmount);
79     }
80 
transformViewFullyFrom(TransformState otherState, float transformationAmount)81     public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
82         transformViewFrom(otherState, TRANSOFORM_ALL, null, transformationAmount);
83     }
84 
transformViewVerticalFrom(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)85     public void transformViewVerticalFrom(TransformState otherState,
86             ViewTransformationHelper.CustomTransformation customTransformation,
87             float transformationAmount) {
88         transformViewFrom(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
89     }
90 
transformViewVerticalFrom(TransformState otherState, float transformationAmount)91     public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) {
92         transformViewFrom(otherState, TRANSOFORM_Y, null, transformationAmount);
93     }
94 
transformViewFrom(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)95     private void transformViewFrom(TransformState otherState, int transformationFlags,
96             ViewTransformationHelper.CustomTransformation customTransformation,
97             float transformationAmount) {
98         final View transformedView = mTransformedView;
99         boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
100         boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
101         boolean transformScale = transformScale();
102         // lets animate the positions correctly
103         if (transformationAmount == 0.0f
104                 || transformX && getTransformationStartX() == UNDEFINED
105                 || transformY && getTransformationStartY() == UNDEFINED
106                 || transformScale && getTransformationStartScaleX() == UNDEFINED
107                 || transformScale && getTransformationStartScaleY() == UNDEFINED) {
108             int[] otherPosition;
109             if (transformationAmount != 0.0f) {
110                 otherPosition = otherState.getLaidOutLocationOnScreen();
111             } else {
112                 otherPosition = otherState.getLocationOnScreen();
113             }
114             int[] ownStablePosition = getLaidOutLocationOnScreen();
115             if (customTransformation == null
116                     || !customTransformation.initTransformation(this, otherState)) {
117                 if (transformX) {
118                     setTransformationStartX(otherPosition[0] - ownStablePosition[0]);
119                 }
120                 if (transformY) {
121                     setTransformationStartY(otherPosition[1] - ownStablePosition[1]);
122                 }
123                 // we also want to animate the scale if we're the same
124                 View otherView = otherState.getTransformedView();
125                 if (transformScale && otherView.getWidth() != transformedView.getWidth()) {
126                     setTransformationStartScaleX(otherView.getWidth() * otherView.getScaleX()
127                             / (float) transformedView.getWidth());
128                     transformedView.setPivotX(0);
129                 } else {
130                     setTransformationStartScaleX(UNDEFINED);
131                 }
132                 if (transformScale && otherView.getHeight() != transformedView.getHeight()) {
133                     setTransformationStartScaleY(otherView.getHeight() * otherView.getScaleY()
134                             / (float) transformedView.getHeight());
135                     transformedView.setPivotY(0);
136                 } else {
137                     setTransformationStartScaleY(UNDEFINED);
138                 }
139             }
140             if (!transformX) {
141                 setTransformationStartX(UNDEFINED);
142             }
143             if (!transformY) {
144                 setTransformationStartY(UNDEFINED);
145             }
146             if (!transformScale) {
147                 setTransformationStartScaleX(UNDEFINED);
148                 setTransformationStartScaleY(UNDEFINED);
149             }
150             setClippingDeactivated(transformedView, true);
151         }
152         float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
153                 transformationAmount);
154         if (transformX) {
155             transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
156                     0.0f,
157                     interpolatedValue));
158         }
159         if (transformY) {
160             transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
161                     0.0f,
162                     interpolatedValue));
163         }
164         if (transformScale) {
165             float transformationStartScaleX = getTransformationStartScaleX();
166             if (transformationStartScaleX != UNDEFINED) {
167                 transformedView.setScaleX(
168                         NotificationUtils.interpolate(transformationStartScaleX,
169                                 1.0f,
170                                 interpolatedValue));
171             }
172             float transformationStartScaleY = getTransformationStartScaleY();
173             if (transformationStartScaleY != UNDEFINED) {
174                 transformedView.setScaleY(
175                         NotificationUtils.interpolate(transformationStartScaleY,
176                                 1.0f,
177                                 interpolatedValue));
178             }
179         }
180     }
181 
transformScale()182     protected boolean transformScale() {
183         return false;
184     }
185 
186     /**
187      * Transforms the {@link #mTransformedView} to the given transformviewstate
188      * @param otherState the state to transform from
189      * @param transformationAmount how much to transform
190      * @return whether an animation was started
191      */
transformViewTo(TransformState otherState, float transformationAmount)192     public boolean transformViewTo(TransformState otherState, float transformationAmount) {
193         mTransformedView.animate().cancel();
194         if (sameAs(otherState)) {
195             // We have the same text, lets show ourselfs
196             if (mTransformedView.getVisibility() == View.VISIBLE) {
197                 mTransformedView.setAlpha(0.0f);
198                 mTransformedView.setVisibility(View.INVISIBLE);
199             }
200             return false;
201         } else {
202             CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
203         }
204         transformViewFullyTo(otherState, transformationAmount);
205         return true;
206     }
207 
transformViewFullyTo(TransformState otherState, float transformationAmount)208     public void transformViewFullyTo(TransformState otherState, float transformationAmount) {
209         transformViewTo(otherState, TRANSOFORM_ALL, null, transformationAmount);
210     }
211 
transformViewVerticalTo(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)212     public void transformViewVerticalTo(TransformState otherState,
213             ViewTransformationHelper.CustomTransformation customTransformation,
214             float transformationAmount) {
215         transformViewTo(otherState, TRANSOFORM_Y, customTransformation, transformationAmount);
216     }
217 
transformViewVerticalTo(TransformState otherState, float transformationAmount)218     public void transformViewVerticalTo(TransformState otherState, float transformationAmount) {
219         transformViewTo(otherState, TRANSOFORM_Y, null, transformationAmount);
220     }
221 
transformViewTo(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)222     private void transformViewTo(TransformState otherState, int transformationFlags,
223             ViewTransformationHelper.CustomTransformation customTransformation,
224             float transformationAmount) {
225         // lets animate the positions correctly
226 
227         final View transformedView = mTransformedView;
228         boolean transformX = (transformationFlags & TRANSOFORM_X) != 0;
229         boolean transformY = (transformationFlags & TRANSOFORM_Y) != 0;
230         boolean transformScale = transformScale();
231         // lets animate the positions correctly
232         if (transformationAmount == 0.0f) {
233             if (transformX) {
234                 float transformationStartX = getTransformationStartX();
235                 float start = transformationStartX != UNDEFINED ? transformationStartX
236                         : transformedView.getTranslationX();
237                 setTransformationStartX(start);
238             }
239             if (transformY) {
240                 float transformationStartY = getTransformationStartY();
241                 float start = transformationStartY != UNDEFINED ? transformationStartY
242                         : transformedView.getTranslationY();
243                 setTransformationStartY(start);
244             }
245             View otherView = otherState.getTransformedView();
246             if (transformScale && otherView.getWidth() != transformedView.getWidth()) {
247                 setTransformationStartScaleX(transformedView.getScaleX());
248                 transformedView.setPivotX(0);
249             } else {
250                 setTransformationStartScaleX(UNDEFINED);
251             }
252             if (transformScale && otherView.getHeight() != transformedView.getHeight()) {
253                 setTransformationStartScaleY(transformedView.getScaleY());
254                 transformedView.setPivotY(0);
255             } else {
256                 setTransformationStartScaleY(UNDEFINED);
257             }
258             setClippingDeactivated(transformedView, true);
259         }
260         float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
261                 transformationAmount);
262         int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
263         int[] ownPosition = getLaidOutLocationOnScreen();
264         if (transformX) {
265             float endX = otherStablePosition[0] - ownPosition[0];
266             if (customTransformation != null
267                     && customTransformation.customTransformTarget(this, otherState)) {
268                 endX = mTransformationEndX;
269             }
270             transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
271                     endX,
272                     interpolatedValue));
273         }
274         if (transformY) {
275             float endY = otherStablePosition[1] - ownPosition[1];
276             if (customTransformation != null
277                     && customTransformation.customTransformTarget(this, otherState)) {
278                 endY = mTransformationEndY;
279             }
280             transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
281                     endY,
282                     interpolatedValue));
283         }
284         if (transformScale) {
285             View otherView = otherState.getTransformedView();
286             float transformationStartScaleX = getTransformationStartScaleX();
287             if (transformationStartScaleX != UNDEFINED) {
288                 transformedView.setScaleX(
289                         NotificationUtils.interpolate(transformationStartScaleX,
290                                 (otherView.getWidth() / (float) transformedView.getWidth()),
291                                 interpolatedValue));
292             }
293             float transformationStartScaleY = getTransformationStartScaleY();
294             if (transformationStartScaleY != UNDEFINED) {
295                 transformedView.setScaleY(
296                         NotificationUtils.interpolate(transformationStartScaleY,
297                                 (otherView.getHeight() / (float) transformedView.getHeight()),
298                                 interpolatedValue));
299             }
300         }
301     }
302 
setClippingDeactivated(final View transformedView, boolean deactivated)303     public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
304         if (!(transformedView.getParent() instanceof ViewGroup)) {
305             return;
306         }
307         ViewGroup view = (ViewGroup) transformedView.getParent();
308         while (true) {
309             ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
310             if (clipSet == null) {
311                 clipSet = new ArraySet<>();
312                 view.setTag(CLIP_CLIPPING_SET, clipSet);
313             }
314             Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
315             if (clipChildren == null) {
316                 clipChildren = view.getClipChildren();
317                 view.setTag(CLIP_CHILDREN_TAG, clipChildren);
318             }
319             Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
320             if (clipToPadding == null) {
321                 clipToPadding = view.getClipToPadding();
322                 view.setTag(CLIP_TO_PADDING, clipToPadding);
323             }
324             ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
325                     ? (ExpandableNotificationRow) view
326                     : null;
327             if (!deactivated) {
328                 clipSet.remove(transformedView);
329                 if (clipSet.isEmpty()) {
330                     view.setClipChildren(clipChildren);
331                     view.setClipToPadding(clipToPadding);
332                     view.setTag(CLIP_CLIPPING_SET, null);
333                     if (row != null) {
334                         row.setClipToActualHeight(true);
335                     }
336                 }
337             } else {
338                 clipSet.add(transformedView);
339                 view.setClipChildren(false);
340                 view.setClipToPadding(false);
341                 if (row != null && row.isChildInGroup()) {
342                     // We still want to clip to the parent's height
343                     row.setClipToActualHeight(false);
344                 }
345             }
346             if (row != null && !row.isChildInGroup()) {
347                 return;
348             }
349             final ViewParent parent = view.getParent();
350             if (parent instanceof ViewGroup) {
351                 view = (ViewGroup) parent;
352             } else {
353                 return;
354             }
355         }
356     }
357 
getLaidOutLocationOnScreen()358     public int[] getLaidOutLocationOnScreen() {
359         int[] location = getLocationOnScreen();
360         location[0] -= mTransformedView.getTranslationX();
361         location[1] -= mTransformedView.getTranslationY();
362         return location;
363     }
364 
getLocationOnScreen()365     public int[] getLocationOnScreen() {
366         mTransformedView.getLocationOnScreen(mOwnPosition);
367         return mOwnPosition;
368     }
369 
sameAs(TransformState otherState)370     protected boolean sameAs(TransformState otherState) {
371         return false;
372     }
373 
createFrom(View view)374     public static TransformState createFrom(View view) {
375         if (view instanceof TextView) {
376             TextViewTransformState result = TextViewTransformState.obtain();
377             result.initFrom(view);
378             return result;
379         }
380         if (view.getId() == com.android.internal.R.id.actions_container) {
381             ActionListTransformState result = ActionListTransformState.obtain();
382             result.initFrom(view);
383             return result;
384         }
385         if (view instanceof NotificationHeaderView) {
386             HeaderTransformState result = HeaderTransformState.obtain();
387             result.initFrom(view);
388             return result;
389         }
390         if (view instanceof ImageView) {
391             ImageTransformState result = ImageTransformState.obtain();
392             result.initFrom(view);
393             return result;
394         }
395         if (view instanceof ProgressBar) {
396             ProgressTransformState result = ProgressTransformState.obtain();
397             result.initFrom(view);
398             return result;
399         }
400         TransformState result = obtain();
401         result.initFrom(view);
402         return result;
403     }
404 
recycle()405     public void recycle() {
406         reset();
407         if (getClass() == TransformState.class) {
408             sInstancePool.release(this);
409         }
410     }
411 
setTransformationEndY(float transformationEndY)412     public void setTransformationEndY(float transformationEndY) {
413         mTransformationEndY = transformationEndY;
414     }
415 
setTransformationEndX(float transformationEndX)416     public void setTransformationEndX(float transformationEndX) {
417         mTransformationEndX = transformationEndX;
418     }
419 
getTransformationStartX()420     public float getTransformationStartX() {
421         Object tag = mTransformedView.getTag(TRANSFORMATION_START_X);
422         return tag == null ? UNDEFINED : (float) tag;
423     }
424 
getTransformationStartY()425     public float getTransformationStartY() {
426         Object tag = mTransformedView.getTag(TRANSFORMATION_START_Y);
427         return tag == null ? UNDEFINED : (float) tag;
428     }
429 
getTransformationStartScaleX()430     public float getTransformationStartScaleX() {
431         Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_X);
432         return tag == null ? UNDEFINED : (float) tag;
433     }
434 
getTransformationStartScaleY()435     public float getTransformationStartScaleY() {
436         Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_Y);
437         return tag == null ? UNDEFINED : (float) tag;
438     }
439 
setTransformationStartX(float transformationStartX)440     public void setTransformationStartX(float transformationStartX) {
441         mTransformedView.setTag(TRANSFORMATION_START_X, transformationStartX);
442     }
443 
setTransformationStartY(float transformationStartY)444     public void setTransformationStartY(float transformationStartY) {
445         mTransformedView.setTag(TRANSFORMATION_START_Y, transformationStartY);
446     }
447 
setTransformationStartScaleX(float startScaleX)448     private void setTransformationStartScaleX(float startScaleX) {
449         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, startScaleX);
450     }
451 
setTransformationStartScaleY(float startScaleY)452     private void setTransformationStartScaleY(float startScaleY) {
453         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, startScaleY);
454     }
455 
reset()456     protected void reset() {
457         mTransformedView = null;
458         mTransformationEndX = UNDEFINED;
459         mTransformationEndY = UNDEFINED;
460     }
461 
setVisible(boolean visible, boolean force)462     public void setVisible(boolean visible, boolean force) {
463         if (!force && mTransformedView.getVisibility() == View.GONE) {
464             return;
465         }
466         if (mTransformedView.getVisibility() != View.GONE) {
467             mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
468         }
469         mTransformedView.animate().cancel();
470         mTransformedView.setAlpha(visible ? 1.0f : 0.0f);
471         resetTransformedView();
472     }
473 
prepareFadeIn()474     public void prepareFadeIn() {
475         resetTransformedView();
476     }
477 
resetTransformedView()478     protected void resetTransformedView() {
479         mTransformedView.setTranslationX(0);
480         mTransformedView.setTranslationY(0);
481         mTransformedView.setScaleX(1.0f);
482         mTransformedView.setScaleY(1.0f);
483         setClippingDeactivated(mTransformedView, false);
484         abortTransformation();
485     }
486 
abortTransformation()487     public void abortTransformation() {
488         mTransformedView.setTag(TRANSFORMATION_START_X, UNDEFINED);
489         mTransformedView.setTag(TRANSFORMATION_START_Y, UNDEFINED);
490         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, UNDEFINED);
491         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, UNDEFINED);
492     }
493 
obtain()494     public static TransformState obtain() {
495         TransformState instance = sInstancePool.acquire();
496         if (instance != null) {
497             return instance;
498         }
499         return new TransformState();
500     }
501 
getTransformedView()502     public View getTransformedView() {
503         return mTransformedView;
504     }
505 }
506