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 "1". 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         private final float mFrameWidth;
    204         private final int mSize;
    205 
    206         public KeyframeSet(int size) {
    207             mSize = size;
    208             mFrameWidth = 1 / (float) (size - 1);
    209         }
    210 
    211         void setValue(float fraction, Object target) {
    212             int i = MathUtils.constrain((int) Math.ceil(fraction / mFrameWidth), 1, mSize - 1);
    213             float amount = (fraction - mFrameWidth * (i - 1)) / mFrameWidth;
    214             interpolate(i, amount, target);
    215         }
    216 
    217         protected abstract void interpolate(int index, float amount, Object target);
    218 
    219         public static KeyframeSet ofInt(Property property, int... values) {
    220             return new IntKeyframeSet((Property<?, Integer>) property, values);
    221         }
    222 
    223         public static KeyframeSet ofFloat(Property property, float... values) {
    224             return new FloatKeyframeSet((Property<?, Float>) property, values);
    225         }
    226     }
    227 
    228     private static class FloatKeyframeSet<T> extends KeyframeSet {
    229         private final float[] mValues;
    230         private final Property<T, Float> mProperty;
    231 
    232         public FloatKeyframeSet(Property<T, Float> property, float[] values) {
    233             super(values.length);
    234             mProperty = property;
    235             mValues = values;
    236         }
    237 
    238         @Override
    239         protected void interpolate(int index, float amount, Object target) {
    240             float firstFloat = mValues[index - 1];
    241             float secondFloat = mValues[index];
    242             mProperty.set((T) target, firstFloat + (secondFloat - firstFloat) * amount);
    243         }
    244     }
    245 
    246     private static class IntKeyframeSet<T> extends KeyframeSet {
    247         private final int[] mValues;
    248         private final Property<T, Integer> mProperty;
    249 
    250         public IntKeyframeSet(Property<T, Integer> property, int[] values) {
    251             super(values.length);
    252             mProperty = property;
    253             mValues = values;
    254         }
    255 
    256         @Override
    257         protected void interpolate(int index, float amount, Object target) {
    258             int firstFloat = mValues[index - 1];
    259             int secondFloat = mValues[index];
    260             mProperty.set((T) target, (int) (firstFloat + (secondFloat - firstFloat) * amount));
    261         }
    262     }
    263 }
    264