1/*
2 * Copyright (C) 2011 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
17package com.android.internal.widget.multiwaveview;
18
19import java.util.ArrayList;
20import java.util.HashMap;
21import java.util.Iterator;
22import java.util.Map.Entry;
23
24import android.animation.Animator.AnimatorListener;
25import android.animation.Animator;
26import android.animation.AnimatorListenerAdapter;
27import android.animation.ObjectAnimator;
28import android.animation.PropertyValuesHolder;
29import android.animation.TimeInterpolator;
30import android.animation.ValueAnimator.AnimatorUpdateListener;
31import android.util.Log;
32
33class Tweener {
34    private static final String TAG = "Tweener";
35    private static final boolean DEBUG = false;
36
37    ObjectAnimator animator;
38    private static HashMap<Object, Tweener> sTweens = new HashMap<Object, Tweener>();
39
40    public Tweener(ObjectAnimator anim) {
41        animator = anim;
42    }
43
44    private static void remove(Animator animator) {
45        Iterator<Entry<Object, Tweener>> iter = sTweens.entrySet().iterator();
46        while (iter.hasNext()) {
47            Entry<Object, Tweener> entry = iter.next();
48            if (entry.getValue().animator == animator) {
49                if (DEBUG) Log.v(TAG, "Removing tweener " + sTweens.get(entry.getKey())
50                        + " sTweens.size() = " + sTweens.size());
51                iter.remove();
52                break; // an animator can only be attached to one object
53            }
54        }
55    }
56
57    public static Tweener to(Object object, long duration, Object... vars) {
58        long delay = 0;
59        AnimatorUpdateListener updateListener = null;
60        AnimatorListener listener = null;
61        TimeInterpolator interpolator = null;
62
63        // Iterate through arguments and discover properties to animate
64        ArrayList<PropertyValuesHolder> props = new ArrayList<PropertyValuesHolder>(vars.length/2);
65        for (int i = 0; i < vars.length; i+=2) {
66            if (!(vars[i] instanceof String)) {
67                throw new IllegalArgumentException("Key must be a string: " + vars[i]);
68            }
69            String key = (String) vars[i];
70            Object value = vars[i+1];
71            if ("simultaneousTween".equals(key)) {
72                // TODO
73            } else if ("ease".equals(key)) {
74                interpolator = (TimeInterpolator) value; // TODO: multiple interpolators?
75            } else if ("onUpdate".equals(key) || "onUpdateListener".equals(key)) {
76                updateListener = (AnimatorUpdateListener) value;
77            } else if ("onComplete".equals(key) || "onCompleteListener".equals(key)) {
78                listener = (AnimatorListener) value;
79            } else if ("delay".equals(key)) {
80                delay = ((Number) value).longValue();
81            } else if ("syncWith".equals(key)) {
82                // TODO
83            } else if (value instanceof float[]) {
84                props.add(PropertyValuesHolder.ofFloat(key,
85                        ((float[])value)[0], ((float[])value)[1]));
86            } else if (value instanceof int[]) {
87                props.add(PropertyValuesHolder.ofInt(key,
88                        ((int[])value)[0], ((int[])value)[1]));
89            } else if (value instanceof Number) {
90                float floatValue = ((Number)value).floatValue();
91                props.add(PropertyValuesHolder.ofFloat(key, floatValue));
92            } else {
93                throw new IllegalArgumentException(
94                        "Bad argument for key \"" + key + "\" with value " + value.getClass());
95            }
96        }
97
98        // Re-use existing tween, if present
99        Tweener tween = sTweens.get(object);
100        ObjectAnimator anim = null;
101        if (tween == null) {
102            anim = ObjectAnimator.ofPropertyValuesHolder(object,
103                    props.toArray(new PropertyValuesHolder[props.size()]));
104            tween = new Tweener(anim);
105            sTweens.put(object, tween);
106            if (DEBUG) Log.v(TAG, "Added new Tweener " + tween);
107        } else {
108            anim = sTweens.get(object).animator;
109            replace(props, object); // Cancel all animators for given object
110        }
111
112        if (interpolator != null) {
113            anim.setInterpolator(interpolator);
114        }
115
116        // Update animation with properties discovered in loop above
117        anim.setStartDelay(delay);
118        anim.setDuration(duration);
119        if (updateListener != null) {
120            anim.removeAllUpdateListeners(); // There should be only one
121            anim.addUpdateListener(updateListener);
122        }
123        if (listener != null) {
124            anim.removeAllListeners(); // There should be only one.
125            anim.addListener(listener);
126        }
127        anim.addListener(mCleanupListener);
128
129        return tween;
130    }
131
132    Tweener from(Object object, long duration, Object... vars) {
133        // TODO:  for v of vars
134        //            toVars[v] = object[v]
135        //            object[v] = vars[v]
136        return Tweener.to(object, duration, vars);
137    }
138
139    // Listener to watch for completed animations and remove them.
140    private static AnimatorListener mCleanupListener = new AnimatorListenerAdapter() {
141
142        @Override
143        public void onAnimationEnd(Animator animation) {
144            remove(animation);
145        }
146
147        @Override
148        public void onAnimationCancel(Animator animation) {
149            remove(animation);
150        }
151    };
152
153    public static void reset() {
154        if (DEBUG) {
155            Log.v(TAG, "Reset()");
156            if (sTweens.size() > 0) {
157                Log.v(TAG, "Cleaning up " + sTweens.size() + " animations");
158            }
159        }
160        sTweens.clear();
161    }
162
163    private static void replace(ArrayList<PropertyValuesHolder> props, Object... args) {
164        for (final Object killobject : args) {
165            Tweener tween = sTweens.get(killobject);
166            if (tween != null) {
167                tween.animator.cancel();
168                if (props != null) {
169                    tween.animator.setValues(
170                            props.toArray(new PropertyValuesHolder[props.size()]));
171                } else {
172                    sTweens.remove(tween);
173                }
174            }
175        }
176    }
177}
178