1 /*
2  * Copyright (C) 2017 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.internal.widget;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.util.IntProperty;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.view.animation.Interpolator;
26 import android.view.animation.PathInterpolator;
27 
28 import com.android.internal.R;
29 
30 /**
31  * A listener that automatically starts animations when the layout bounds change.
32  */
33 public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
34     private static final long APPEAR_ANIMATION_LENGTH = 210;
35     private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
36     public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
37     private static final int TAG_TOP_ANIMATOR = R.id.tag_top_animator;
38     private static final int TAG_TOP = R.id.tag_top_override;
39     private static final int TAG_LAYOUT_TOP = R.id.tag_layout_top;
40     private static final int TAG_FIRST_LAYOUT = R.id.tag_is_first_layout;
41     private static final int TAG_ALPHA_ANIMATOR = R.id.tag_alpha_animator;
42     private static final ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS =
43             view -> view.getId() == com.android.internal.R.id.notification_messaging;
44     private static final IntProperty<View> TOP =
45             new IntProperty<View>("top") {
46                 @Override
47                 public void setValue(View object, int value) {
48                     setTop(object, value);
49                 }
50 
51                 @Override
52                 public Integer get(View object) {
53                     return getTop(object);
54                 }
55             };
56 
57     @Override
onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)58     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
59             int oldTop, int oldRight, int oldBottom) {
60         setLayoutTop(v, top);
61         if (isFirstLayout(v)) {
62             setFirstLayout(v, false /* first */);
63             setTop(v, top);
64             return;
65         }
66         startTopAnimation(v, getTop(v), top, MessagingLayout.FAST_OUT_SLOW_IN);
67     }
68 
isFirstLayout(View view)69     private static boolean isFirstLayout(View view) {
70         Boolean tag = (Boolean) view.getTag(TAG_FIRST_LAYOUT);
71         if (tag == null) {
72             return true;
73         }
74         return tag;
75     }
76 
recycle(View view)77     public static void recycle(View view) {
78         setFirstLayout(view, true /* first */);
79     }
80 
setFirstLayout(View view, boolean first)81     private static void setFirstLayout(View view, boolean first) {
82         view.setTagInternal(TAG_FIRST_LAYOUT, first);
83     }
84 
setLayoutTop(View view, int top)85     private static void setLayoutTop(View view, int top) {
86         view.setTagInternal(TAG_LAYOUT_TOP, top);
87     }
88 
getLayoutTop(View view)89     public static int getLayoutTop(View view) {
90         Integer tag = (Integer) view.getTag(TAG_LAYOUT_TOP);
91         if (tag == null) {
92             return getTop(view);
93         }
94         return tag;
95     }
96 
97     /**
98      * Start a translation animation from a start offset to the laid out location
99      * @param view The view to animate
100      * @param startTranslation The starting translation to start from.
101      * @param interpolator The interpolator to use.
102      */
startLocalTranslationFrom(View view, int startTranslation, Interpolator interpolator)103     public static void startLocalTranslationFrom(View view, int startTranslation,
104             Interpolator interpolator) {
105         startTopAnimation(view, getTop(view) + startTranslation, getLayoutTop(view), interpolator);
106     }
107 
108     /**
109      * Start a translation animation from a start offset to the laid out location
110      * @param view The view to animate
111      * @param endTranslation The end translation to go to.
112      * @param interpolator The interpolator to use.
113      */
startLocalTranslationTo(View view, int endTranslation, Interpolator interpolator)114     public static void startLocalTranslationTo(View view, int endTranslation,
115             Interpolator interpolator) {
116         int top = getTop(view);
117         startTopAnimation(view, top, top + endTranslation, interpolator);
118     }
119 
getTop(View v)120     public static int getTop(View v) {
121         Integer tag = (Integer) v.getTag(TAG_TOP);
122         if (tag == null) {
123             return v.getTop();
124         }
125         return tag;
126     }
127 
setTop(View v, int value)128     private static void setTop(View v, int value) {
129         v.setTagInternal(TAG_TOP, value);
130         updateTopAndBottom(v);
131     }
132 
updateTopAndBottom(View v)133     private static void updateTopAndBottom(View v) {
134         int top = getTop(v);
135         int height = v.getHeight();
136         v.setTop(top);
137         v.setBottom(height + top);
138     }
139 
startTopAnimation(final View v, int start, int end, Interpolator interpolator)140     private static void startTopAnimation(final View v, int start, int end,
141             Interpolator interpolator) {
142         ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_TOP_ANIMATOR);
143         if (existing != null) {
144             existing.cancel();
145         }
146         if (!v.isShown() || start == end
147                 || (MessagingLinearLayout.isGone(v) && !isHidingAnimated(v))) {
148             setTop(v, end);
149             return;
150         }
151         ObjectAnimator animator = ObjectAnimator.ofInt(v, TOP, start, end);
152         setTop(v, start);
153         animator.setInterpolator(interpolator);
154         animator.setDuration(APPEAR_ANIMATION_LENGTH);
155         animator.addListener(new AnimatorListenerAdapter() {
156             public boolean mCancelled;
157 
158             @Override
159             public void onAnimationEnd(Animator animation) {
160                 v.setTagInternal(TAG_TOP_ANIMATOR, null);
161                 setClippingDeactivated(v, false);
162             }
163 
164             @Override
165             public void onAnimationCancel(Animator animation) {
166                 mCancelled = true;
167             }
168         });
169         setClippingDeactivated(v, true);
170         v.setTagInternal(TAG_TOP_ANIMATOR, animator);
171         animator.start();
172     }
173 
isHidingAnimated(View v)174     private static boolean isHidingAnimated(View v) {
175         if (v instanceof MessagingLinearLayout.MessagingChild) {
176             return ((MessagingLinearLayout.MessagingChild) v).isHidingAnimated();
177         }
178         return false;
179     }
180 
fadeIn(final View v)181     public static void fadeIn(final View v) {
182         ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_ALPHA_ANIMATOR);
183         if (existing != null) {
184             existing.cancel();
185         }
186         if (v.getVisibility() == View.INVISIBLE) {
187             v.setVisibility(View.VISIBLE);
188         }
189         ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.ALPHA,
190                 0.0f, 1.0f);
191         v.setAlpha(0.0f);
192         animator.setInterpolator(ALPHA_IN);
193         animator.setDuration(APPEAR_ANIMATION_LENGTH);
194         animator.addListener(new AnimatorListenerAdapter() {
195             @Override
196             public void onAnimationEnd(Animator animation) {
197                 v.setTagInternal(TAG_ALPHA_ANIMATOR, null);
198                 updateLayerType(v, false /* animating */);
199             }
200         });
201         updateLayerType(v, true /* animating */);
202         v.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
203         animator.start();
204     }
205 
updateLayerType(View view, boolean animating)206     private static void updateLayerType(View view, boolean animating) {
207         if (view.hasOverlappingRendering() && animating) {
208             view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
209         } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
210             view.setLayerType(View.LAYER_TYPE_NONE, null);
211         }
212     }
213 
fadeOut(final View view, Runnable endAction)214     public static void fadeOut(final View view, Runnable endAction) {
215         ObjectAnimator existing = (ObjectAnimator) view.getTag(TAG_ALPHA_ANIMATOR);
216         if (existing != null) {
217             existing.cancel();
218         }
219         if (!view.isShown() || (MessagingLinearLayout.isGone(view) && !isHidingAnimated(view))) {
220             view.setAlpha(0.0f);
221             if (endAction != null) {
222                 endAction.run();
223             }
224             return;
225         }
226         ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA,
227                 view.getAlpha(), 0.0f);
228         animator.setInterpolator(ALPHA_OUT);
229         animator.setDuration(APPEAR_ANIMATION_LENGTH);
230         animator.addListener(new AnimatorListenerAdapter() {
231             @Override
232             public void onAnimationEnd(Animator animation) {
233                 view.setTagInternal(TAG_ALPHA_ANIMATOR, null);
234                 updateLayerType(view, false /* animating */);
235                 if (endAction != null) {
236                     endAction.run();
237                 }
238             }
239         });
240         updateLayerType(view, true /* animating */);
241         view.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
242         animator.start();
243     }
244 
setClippingDeactivated(final View transformedView, boolean deactivated)245     public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
246         ViewClippingUtil.setClippingDeactivated(transformedView, deactivated,
247                 CLIPPING_PARAMETERS);
248     }
249 
isAnimatingTranslation(View v)250     public static boolean isAnimatingTranslation(View v) {
251         return v.getTag(TAG_TOP_ANIMATOR) != null;
252     }
253 
isAnimatingAlpha(View v)254     public static boolean isAnimatingAlpha(View v) {
255         return v.getTag(TAG_ALPHA_ANIMATOR) != null;
256     }
257 
setToLaidOutPosition(View view)258     public static void setToLaidOutPosition(View view) {
259         setTop(view, getLayoutTop(view));
260     }
261 }
262