Home | History | Annotate | Download | only in qs
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package com.android.systemui.qs;
     16 
     17 import android.util.FloatProperty;
     18 import android.util.MathUtils;
     19 import android.util.Property;
     20 import android.view.View;
     21 import android.view.animation.Interpolator;
     22 
     23 import java.util.ArrayList;
     24 import java.util.List;
     25 
     26 /**
     27  * Helper class, that handles similar properties as animators (delay, interpolators)
     28  * but can have a float input as to the amount they should be in effect.  This allows
     29  * easier animation that tracks input.
     30  *
     31  * All "delays" and "times" are as fractions from 0-1.
     32  */
     33 public class TouchAnimator {
     34 
     35     private final Object[] mTargets;
     36     private final KeyframeSet[] mKeyframeSets;
     37     private final float mStartDelay;
     38     private final float mEndDelay;
     39     private final float mSpan;
     40     private final Interpolator mInterpolator;
     41     private final Listener mListener;
     42     private float mLastT = -1;
     43 
     44     private TouchAnimator(Object[] targets, KeyframeSet[] keyframeSets,
     45             float startDelay, float endDelay, Interpolator interpolator, Listener listener) {
     46         mTargets = targets;
     47         mKeyframeSets = keyframeSets;
     48         mStartDelay = startDelay;
     49         mEndDelay = endDelay;
     50         mSpan = (1 - mEndDelay - mStartDelay);
     51         mInterpolator = interpolator;
     52         mListener = listener;
     53     }
     54 
     55     public void setPosition(float fraction) {
     56         float t = MathUtils.constrain((fraction - mStartDelay) / mSpan, 0, 1);
     57         if (mInterpolator != null) {
     58             t = mInterpolator.getInterpolation(t);
     59         }
     60         if (t == mLastT) {
     61             return;
     62         }
     63         if (mListener != null) {
     64             if (t == 1) {
     65                 mListener.onAnimationAtEnd();
     66             } else if (t == 0) {
     67                 mListener.onAnimationAtStart();
     68             } else if (mLastT <= 0 || mLastT == 1) {
     69                 mListener.onAnimationStarted();
     70             }
     71             mLastT = t;
     72         }
     73         for (int i = 0; i < mTargets.length; i++) {
     74             mKeyframeSets[i].setValue(t, mTargets[i]);
     75         }
     76     }
     77 
     78     private static final FloatProperty<TouchAnimator> POSITION =
     79             new FloatProperty<TouchAnimator>("position") {
     80         @Override
     81         public void setValue(TouchAnimator touchAnimator, float value) {
     82             touchAnimator.setPosition(value);
     83         }
     84 
     85         @Override
     86         public Float get(TouchAnimator touchAnimator) {
     87             return touchAnimator.mLastT;
     88         }
     89     };
     90 
     91     public static class ListenerAdapter implements Listener {
     92         @Override
     93         public void onAnimationAtStart() { }
     94 
     95         @Override
     96         public void onAnimationAtEnd() { }
     97 
     98         @Override
     99         public void onAnimationStarted() { }
    100     }
    101 
    102     public interface Listener {
    103         /**
    104          * Called when the animator moves into a position of "0". Start and end delays are
    105          * taken into account, so this position may cover a range of fractional inputs.
    106          */
    107         void onAnimationAtStart();
    108 
    109         /**
    110          * Called when the animator moves into a position of "0". Start and end delays are
    111          * taken into account, so this position may cover a range of fractional inputs.
    112          */
    113         void onAnimationAtEnd();
    114 
    115         /**
    116          * Called when the animator moves out of the start or end position and is in a transient
    117          * state.
    118          */
    119         void onAnimationStarted();
    120     }
    121 
    122     public static class Builder {
    123         private List<Object> mTargets = new ArrayList<>();
    124         private List<KeyframeSet> mValues = new ArrayList<>();
    125 
    126         private float mStartDelay;
    127         private float mEndDelay;
    128         private Interpolator mInterpolator;
    129         private Listener mListener;
    130 
    131         public Builder addFloat(Object target, String property, float... values) {
    132             add(target, KeyframeSet.ofFloat(getProperty(target, property, float.class), values));
    133             return this;
    134         }
    135 
    136         public Builder addInt(Object target, String property, int... values) {
    137             add(target, KeyframeSet.ofInt(getProperty(target, property, int.class), values));
    138             return this;
    139         }
    140 
    141         private void add(Object target, KeyframeSet keyframeSet) {
    142             mTargets.add(target);
    143             mValues.add(keyframeSet);
    144         }
    145 
    146         private static Property getProperty(Object target, String property, Class<?> cls) {
    147             if (target instanceof View) {
    148                 switch (property) {
    149                     case "translationX":
    150                         return View.TRANSLATION_X;
    151                     case "translationY":
    152                         return View.TRANSLATION_Y;
    153                     case "translationZ":
    154                         return View.TRANSLATION_Z;
    155                     case "alpha":
    156                         return View.ALPHA;
    157                     case "rotation":
    158                         return View.ROTATION;
    159                     case "x":
    160                         return View.X;
    161                     case "y":
    162                         return View.Y;
    163                     case "scaleX":
    164                         return View.SCALE_X;
    165                     case "scaleY":
    166                         return View.SCALE_Y;
    167                 }
    168             }
    169             if (target instanceof TouchAnimator && "position".equals(property)) {
    170                 return POSITION;
    171             }
    172             return Property.of(target.getClass(), cls, property);
    173         }
    174 
    175         public Builder setStartDelay(float startDelay) {
    176             mStartDelay = startDelay;
    177             return this;
    178         }
    179 
    180         public Builder setEndDelay(float endDelay) {
    181             mEndDelay = endDelay;
    182             return this;
    183         }
    184 
    185         public Builder setInterpolator(Interpolator intepolator) {
    186             mInterpolator = intepolator;
    187             return this;
    188         }
    189 
    190         public Builder setListener(Listener listener) {
    191             mListener = listener;
    192             return this;
    193         }
    194 
    195         public TouchAnimator build() {
    196             return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]),
    197                     mValues.toArray(new KeyframeSet[mValues.size()]),
    198                     mStartDelay, mEndDelay, mInterpolator, mListener);
    199         }
    200     }
    201 
    202     private static abstract class KeyframeSet {
    203 
    204         private final float mFrameWidth;
    205         private final int mSize;
    206 
    207         public KeyframeSet(int size) {
    208             mSize = size;
    209             mFrameWidth = 1 / (float) (size - 1);
    210         }
    211 
    212         void setValue(float fraction, Object target) {
    213             int i;
    214             for (i = 1; i < mSize - 1 && fraction > mFrameWidth; i++);
    215             float amount = fraction / mFrameWidth;
    216             interpolate(i, amount, target);
    217         }
    218 
    219         protected abstract void interpolate(int index, float amount, Object target);
    220 
    221         public static KeyframeSet ofInt(Property property, int... values) {
    222             return new IntKeyframeSet((Property<?, Integer>) property, values);
    223         }
    224 
    225         public static KeyframeSet ofFloat(Property property, float... values) {
    226             return new FloatKeyframeSet((Property<?, Float>) property, values);
    227         }
    228     }
    229 
    230     private static class FloatKeyframeSet<T> extends KeyframeSet {
    231         private final float[] mValues;
    232         private final Property<T, Float> mProperty;
    233 
    234         public FloatKeyframeSet(Property<T, Float> property, float[] values) {
    235             super(values.length);
    236             mProperty = property;
    237             mValues = values;
    238         }
    239 
    240         @Override
    241         protected void interpolate(int index, float amount, Object target) {
    242             float firstFloat = mValues[index - 1];
    243             float secondFloat = mValues[index];
    244             mProperty.set((T) target, firstFloat + (secondFloat - firstFloat) * amount);
    245         }
    246     }
    247 
    248     private static class IntKeyframeSet<T> extends KeyframeSet {
    249         private final int[] mValues;
    250         private final Property<T, Integer> mProperty;
    251 
    252         public IntKeyframeSet(Property<T, Integer> property, int[] values) {
    253             super(values.length);
    254             mProperty = property;
    255             mValues = values;
    256         }
    257 
    258         @Override
    259         protected void interpolate(int index, float amount, Object target) {
    260             int firstFloat = mValues[index - 1];
    261             int secondFloat = mValues[index];
    262             mProperty.set((T) target, (int) (firstFloat + (secondFloat - firstFloat) * amount));
    263         }
    264     }
    265 }
    266