Home | History | Annotate | Download | only in widget
      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
     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 
     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 
     77     public static void recycle(View view) {
     78         setFirstLayout(view, true /* first */);
     79     }
     80 
     81     private static void setFirstLayout(View view, boolean first) {
     82         view.setTagInternal(TAG_FIRST_LAYOUT, first);
     83     }
     84 
     85     private static void setLayoutTop(View view, int top) {
     86         view.setTagInternal(TAG_LAYOUT_TOP, top);
     87     }
     88 
     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      */
    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      */
    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 
    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 
    128     private static void setTop(View v, int value) {
    129         v.setTagInternal(TAG_TOP, value);
    130         updateTopAndBottom(v);
    131     }
    132 
    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 
    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 
    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 
    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 
    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 
    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 
    245     public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
    246         ViewClippingUtil.setClippingDeactivated(transformedView, deactivated,
    247                 CLIPPING_PARAMETERS);
    248     }
    249 
    250     public static boolean isAnimatingTranslation(View v) {
    251         return v.getTag(TAG_TOP_ANIMATOR) != null;
    252     }
    253 
    254     public static boolean isAnimatingAlpha(View v) {
    255         return v.getTag(TAG_ALPHA_ANIMATOR) != null;
    256     }
    257 
    258     public static void setToLaidOutPosition(View view) {
    259         setTop(view, getLayoutTop(view));
    260     }
    261 }
    262