Home | History | Annotate | Download | only in deskclock
      1 /*
      2  * Copyright (C) 2014 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.deskclock;
     18 
     19 import android.animation.Animator;
     20 import android.animation.ArgbEvaluator;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.PropertyValuesHolder;
     23 import android.animation.TypeEvaluator;
     24 import android.animation.ValueAnimator;
     25 import android.graphics.Rect;
     26 import android.graphics.drawable.Animatable;
     27 import android.graphics.drawable.Drawable;
     28 import android.graphics.drawable.LayerDrawable;
     29 import android.support.v4.graphics.drawable.DrawableCompat;
     30 import android.support.v4.view.animation.FastOutSlowInInterpolator;
     31 import android.util.Property;
     32 import android.view.View;
     33 import android.view.animation.Interpolator;
     34 import android.widget.ImageView;
     35 
     36 import java.lang.reflect.InvocationTargetException;
     37 import java.lang.reflect.Method;
     38 
     39 public class AnimatorUtils {
     40 
     41     public static final Interpolator DECELERATE_ACCELERATE_INTERPOLATOR = new Interpolator() {
     42         @Override
     43         public float getInterpolation(float x) {
     44             return 0.5f + 4.0f * (x - 0.5f) * (x - 0.5f) * (x - 0.5f);
     45         }
     46     };
     47 
     48     public static final Interpolator INTERPOLATOR_FAST_OUT_SLOW_IN =
     49             new FastOutSlowInInterpolator();
     50 
     51     public static final Property<View, Integer> BACKGROUND_ALPHA =
     52             new Property<View, Integer>(Integer.class, "background.alpha") {
     53         @Override
     54         public Integer get(View view) {
     55             Drawable background = view.getBackground();
     56             if (background instanceof LayerDrawable
     57                     && ((LayerDrawable) background).getNumberOfLayers() > 0) {
     58                 background = ((LayerDrawable) background).getDrawable(0);
     59             }
     60             return background.getAlpha();
     61         }
     62 
     63         @Override
     64         public void set(View view, Integer value) {
     65             setBackgroundAlpha(view, value);
     66         }
     67     };
     68 
     69     /**
     70      * Sets the alpha of the top layer's drawable (of the background) only, if the background is a
     71      * layer drawable, to ensure that the other layers (i.e., the selectable item background, and
     72      * therefore the touch feedback RippleDrawable) are not affected.
     73      *
     74      * @param view the affected view
     75      * @param value the alpha value (0-255)
     76      */
     77     public static void setBackgroundAlpha(View view, Integer value) {
     78         Drawable background = view.getBackground();
     79         if (background instanceof LayerDrawable
     80                 && ((LayerDrawable) background).getNumberOfLayers() > 0) {
     81             background = ((LayerDrawable) background).getDrawable(0);
     82         }
     83         background.setAlpha(value);
     84     }
     85 
     86     public static final Property<ImageView, Integer> DRAWABLE_ALPHA =
     87             new Property<ImageView, Integer>(Integer.class, "drawable.alpha") {
     88         @Override
     89         public Integer get(ImageView view) {
     90             return view.getDrawable().getAlpha();
     91         }
     92 
     93         @Override
     94         public void set(ImageView view, Integer value) {
     95             view.getDrawable().setAlpha(value);
     96         }
     97     };
     98 
     99     public static final Property<ImageView, Integer> DRAWABLE_TINT =
    100             new Property<ImageView, Integer>(Integer.class, "drawable.tint") {
    101         @Override
    102         public Integer get(ImageView view) {
    103             return null;
    104         }
    105 
    106         @Override
    107         public void set(ImageView view, Integer value) {
    108             // Ensure the drawable is wrapped using DrawableCompat.
    109             final Drawable drawable = view.getDrawable();
    110             final Drawable wrappedDrawable = DrawableCompat.wrap(drawable);
    111             if (wrappedDrawable != drawable) {
    112                 view.setImageDrawable(wrappedDrawable);
    113             }
    114             // Set the new tint value via DrawableCompat.
    115             DrawableCompat.setTint(wrappedDrawable, value);
    116         }
    117     };
    118 
    119     @SuppressWarnings("unchecked")
    120     public static final TypeEvaluator<Integer> ARGB_EVALUATOR = new ArgbEvaluator();
    121 
    122     private static Method sAnimateValue;
    123     private static boolean sTryAnimateValue = true;
    124 
    125     public static void setAnimatedFraction(ValueAnimator animator, float fraction) {
    126         if (Utils.isLMR1OrLater()) {
    127             animator.setCurrentFraction(fraction);
    128             return;
    129         }
    130 
    131         if (sTryAnimateValue) {
    132             // try to set the animated fraction directly so that it isn't affected by the
    133             // internal animator scale or time (b/17938711)
    134             try {
    135                 if (sAnimateValue == null) {
    136                     sAnimateValue = ValueAnimator.class
    137                             .getDeclaredMethod("animateValue", float.class);
    138                     sAnimateValue.setAccessible(true);
    139                 }
    140 
    141                 sAnimateValue.invoke(animator, fraction);
    142                 return;
    143             } catch (NoSuchMethodException | InvocationTargetException
    144                     | IllegalAccessException e) {
    145                 // something went wrong, don't try that again
    146                 LogUtils.e("Unable to use animateValue directly", e);
    147                 sTryAnimateValue = false;
    148             }
    149         }
    150 
    151         // if that doesn't work then just fall back to setting the current play time
    152         animator.setCurrentPlayTime(Math.round(fraction * animator.getDuration()));
    153     }
    154 
    155     public static void reverse(ValueAnimator... animators) {
    156         for (ValueAnimator animator : animators) {
    157             final float fraction = animator.getAnimatedFraction();
    158             if (fraction > 0.0f) {
    159                 animator.reverse();
    160                 setAnimatedFraction(animator, 1.0f - fraction);
    161             }
    162         }
    163     }
    164 
    165     public static void cancel(ValueAnimator... animators) {
    166         for (ValueAnimator animator : animators) {
    167             animator.cancel();
    168         }
    169     }
    170 
    171     public static ValueAnimator getScaleAnimator(View view, float... values) {
    172         return ObjectAnimator.ofPropertyValuesHolder(view,
    173                 PropertyValuesHolder.ofFloat(View.SCALE_X, values),
    174                 PropertyValuesHolder.ofFloat(View.SCALE_Y, values));
    175     }
    176 
    177     public static ValueAnimator getAlphaAnimator(View view, float... values) {
    178         return ObjectAnimator.ofFloat(view, View.ALPHA, values);
    179     }
    180 
    181     public static final Property<View, Integer> VIEW_LEFT =
    182             new Property<View, Integer>(Integer.class, "left") {
    183                 @Override
    184                 public Integer get(View view) {
    185                     return view.getLeft();
    186                 }
    187 
    188                 @Override
    189                 public void set(View view, Integer left) {
    190                     view.setLeft(left);
    191                 }
    192             };
    193 
    194     public static final Property<View, Integer> VIEW_TOP =
    195             new Property<View, Integer>(Integer.class, "top") {
    196                 @Override
    197                 public Integer get(View view) {
    198                     return view.getTop();
    199                 }
    200 
    201                 @Override
    202                 public void set(View view, Integer top) {
    203                     view.setTop(top);
    204                 }
    205             };
    206 
    207     public static final Property<View, Integer> VIEW_BOTTOM =
    208             new Property<View, Integer>(Integer.class, "bottom") {
    209                 @Override
    210                 public Integer get(View view) {
    211                     return view.getBottom();
    212                 }
    213 
    214                 @Override
    215                 public void set(View view, Integer bottom) {
    216                     view.setBottom(bottom);
    217                 }
    218             };
    219 
    220     public static final Property<View, Integer> VIEW_RIGHT =
    221             new Property<View, Integer>(Integer.class, "right") {
    222                 @Override
    223                 public Integer get(View view) {
    224                     return view.getRight();
    225                 }
    226 
    227                 @Override
    228                 public void set(View view, Integer right) {
    229                     view.setRight(right);
    230                 }
    231             };
    232 
    233     /**
    234      * @param target the view to be morphed
    235      * @param from the bounds of the {@code target} before animating
    236      * @param to the bounds of the {@code target} after animating
    237      * @return an animator that morphs the {@code target} between the {@code from} bounds and the
    238      *      {@code to} bounds. Note that it is the *content* bounds that matter here, so padding
    239      *      insets contributed by the background are subtracted from the views when computing the
    240      *      {@code target} bounds.
    241      */
    242     public static Animator getBoundsAnimator(View target, View from, View to) {
    243         // Fetch the content insets for the views. Content bounds are what matter, not total bounds.
    244         final Rect targetInsets = new Rect();
    245         target.getBackground().getPadding(targetInsets);
    246         final Rect fromInsets = new Rect();
    247         from.getBackground().getPadding(fromInsets);
    248         final Rect toInsets = new Rect();
    249         to.getBackground().getPadding(toInsets);
    250 
    251         // Before animating, the content bounds of target must match the content bounds of from.
    252         final int startLeft = from.getLeft() - fromInsets.left + targetInsets.left;
    253         final int startTop = from.getTop() - fromInsets.top + targetInsets.top;
    254         final int startRight = from.getRight() - fromInsets.right + targetInsets.right;
    255         final int startBottom = from.getBottom() - fromInsets.bottom + targetInsets.bottom;
    256 
    257         // After animating, the content bounds of target must match the content bounds of to.
    258         final int endLeft = to.getLeft() - toInsets.left + targetInsets.left;
    259         final int endTop = to.getTop() - toInsets.top + targetInsets.top;
    260         final int endRight = to.getRight() - toInsets.right + targetInsets.right;
    261         final int endBottom = to.getBottom() - toInsets.bottom + targetInsets.bottom;
    262 
    263         return getBoundsAnimator(target, startLeft, startTop, startRight, startBottom, endLeft,
    264                 endTop, endRight, endBottom);
    265     }
    266 
    267     /**
    268      * Returns an animator that animates the bounds of a single view.
    269      */
    270     public static Animator getBoundsAnimator(View view, int fromLeft, int fromTop, int fromRight,
    271             int fromBottom, int toLeft, int toTop, int toRight, int toBottom) {
    272         view.setLeft(fromLeft);
    273         view.setTop(fromTop);
    274         view.setRight(fromRight);
    275         view.setBottom(fromBottom);
    276 
    277         return ObjectAnimator.ofPropertyValuesHolder(view,
    278                 PropertyValuesHolder.ofInt(VIEW_LEFT, toLeft),
    279                 PropertyValuesHolder.ofInt(VIEW_TOP, toTop),
    280                 PropertyValuesHolder.ofInt(VIEW_RIGHT, toRight),
    281                 PropertyValuesHolder.ofInt(VIEW_BOTTOM, toBottom));
    282     }
    283 
    284     public static void startDrawableAnimation(ImageView view) {
    285         final Drawable d = view.getDrawable();
    286         if (d instanceof Animatable) {
    287             ((Animatable) d).start();
    288         }
    289     }
    290 }