/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nineoldandroids.animation;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.content.res.Resources.NotFoundException;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Xml;
import android.view.animation.AnimationUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
/**
* This class is used to instantiate animator XML files into Animator objects.
* <p>
* For performance reasons, inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
* to use this inflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource (R.
* <em>something</em> file.)
*/
public class AnimatorInflater {
private static final int[] AnimatorSet = new int[] {
/* 0 */ android.R.attr.ordering,
};
private static final int AnimatorSet_ordering = 0;
private static final int[] PropertyAnimator = new int[] {
/* 0 */ android.R.attr.propertyName,
};
private static final int PropertyAnimator_propertyName = 0;
private static final int[] Animator = new int[] {
/* 0 */ android.R.attr.interpolator,
/* 1 */ android.R.attr.duration,
/* 2 */ android.R.attr.startOffset,
/* 3 */ android.R.attr.repeatCount,
/* 4 */ android.R.attr.repeatMode,
/* 5 */ android.R.attr.valueFrom,
/* 6 */ android.R.attr.valueTo,
/* 7 */ android.R.attr.valueType,
};
private static final int Animator_interpolator = 0;
private static final int Animator_duration = 1;
private static final int Animator_startOffset = 2;
private static final int Animator_repeatCount = 3;
private static final int Animator_repeatMode = 4;
private static final int Animator_valueFrom = 5;
private static final int Animator_valueTo = 6;
private static final int Animator_valueType = 7;
/**
* These flags are used when parsing AnimatorSet objects
*/
private static final int TOGETHER = 0;
//private static final int SEQUENTIALLY = 1;
/**
* Enum values used in XML attributes to indicate the value for mValueType
*/
private static final int VALUE_TYPE_FLOAT = 0;
//private static final int VALUE_TYPE_INT = 1;
//private static final int VALUE_TYPE_COLOR = 4;
//private static final int VALUE_TYPE_CUSTOM = 5;
/**
* Loads an {@link Animator} object from a resource
*
* @param context Application context used to access resources
* @param id The resource id of the animation to load
* @return The animator object reference by the specified id
* @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
*/
public static Animator loadAnimator(Context context, int id)
throws NotFoundException {
XmlResourceParser parser = null;
try {
parser = context.getResources().getAnimation(id);
return createAnimatorFromXml(context, parser);
} catch (XmlPullParserException ex) {
Resources.NotFoundException rnf =
new Resources.NotFoundException("Can't load animation resource ID #0x" +
Integer.toHexString(id));
rnf.initCause(ex);
throw rnf;
} catch (IOException ex) {
Resources.NotFoundException rnf =
new Resources.NotFoundException("Can't load animation resource ID #0x" +
Integer.toHexString(id));
rnf.initCause(ex);
throw rnf;
} finally {
if (parser != null) parser.close();
}
}
private static Animator createAnimatorFromXml(Context c, XmlPullParser parser)
throws XmlPullParserException, IOException {
return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
}
private static Animator createAnimatorFromXml(Context c, XmlPullParser parser,
AttributeSet attrs, AnimatorSet parent, int sequenceOrdering)
throws XmlPullParserException, IOException {
Animator anim = null;
ArrayList<Animator> childAnims = null;
// Make sure we are on a start tag.
int type;
int depth = parser.getDepth();
while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
&& type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
if (name.equals("objectAnimator")) {
anim = loadObjectAnimator(c, attrs);
} else if (name.equals("animator")) {
anim = loadAnimator(c, attrs, null);
} else if (name.equals("set")) {
anim = new AnimatorSet();
TypedArray a = c.obtainStyledAttributes(attrs,
/*com.android.internal.R.styleable.*/AnimatorSet);
TypedValue orderingValue = new TypedValue();
a.getValue(/*com.android.internal.R.styleable.*/AnimatorSet_ordering, orderingValue);
int ordering = orderingValue.type == TypedValue.TYPE_INT_DEC ? orderingValue.data : TOGETHER;
createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim, ordering);
a.recycle();
} else {
throw new RuntimeException("Unknown animator name: " + parser.getName());
}
if (parent != null) {
if (childAnims == null) {
childAnims = new ArrayList<Animator>();
}
childAnims.add(anim);
}
}
if (parent != null && childAnims != null) {
Animator[] animsArray = new Animator[childAnims.size()];
int index = 0;
for (Animator a : childAnims) {
animsArray[index++] = a;
}
if (sequenceOrdering == TOGETHER) {
parent.playTogether(animsArray);
} else {
parent.playSequentially(animsArray);
}
}
return anim;
}
private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs)
throws NotFoundException {
ObjectAnimator anim = new ObjectAnimator();
loadAnimator(context, attrs, anim);
TypedArray a =
context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/PropertyAnimator);
String propertyName = a.getString(/*com.android.internal.R.styleable.*/PropertyAnimator_propertyName);
anim.setPropertyName(propertyName);
a.recycle();
return anim;
}
/**
* Creates a new animation whose parameters come from the specified context and
* attributes set.
*
* @param context the application environment
* @param attrs the set of attributes holding the animation parameters
*/
private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim)
throws NotFoundException {
TypedArray a =
context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/Animator);
long duration = a.getInt(/*com.android.internal.R.styleable.*/Animator_duration, 0);
long startDelay = a.getInt(/*com.android.internal.R.styleable.*/Animator_startOffset, 0);
int valueType = a.getInt(/*com.android.internal.R.styleable.*/Animator_valueType,
VALUE_TYPE_FLOAT);
if (anim == null) {
anim = new ValueAnimator();
}
//TypeEvaluator evaluator = null;
int valueFromIndex = /*com.android.internal.R.styleable.*/Animator_valueFrom;
int valueToIndex = /*com.android.internal.R.styleable.*/Animator_valueTo;
boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
TypedValue tvFrom = a.peekValue(valueFromIndex);
boolean hasFrom = (tvFrom != null);
int fromType = hasFrom ? tvFrom.type : 0;
TypedValue tvTo = a.peekValue(valueToIndex);
boolean hasTo = (tvTo != null);
int toType = hasTo ? tvTo.type : 0;
if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
(hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
// special case for colors: ignore valueType and get ints
getFloats = false;
anim.setEvaluator(new ArgbEvaluator());
}
if (getFloats) {
float valueFrom;
float valueTo;
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
valueFrom = a.getDimension(valueFromIndex, 0f);
} else {
valueFrom = a.getFloat(valueFromIndex, 0f);
}
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = a.getDimension(valueToIndex, 0f);
} else {
valueTo = a.getFloat(valueToIndex, 0f);
}
anim.setFloatValues(valueFrom, valueTo);
} else {
anim.setFloatValues(valueFrom);
}
} else {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = a.getDimension(valueToIndex, 0f);
} else {
valueTo = a.getFloat(valueToIndex, 0f);
}
anim.setFloatValues(valueTo);
}
} else {
int valueFrom;
int valueTo;
if (hasFrom) {
if (fromType == TypedValue.TYPE_DIMENSION) {
valueFrom = (int) a.getDimension(valueFromIndex, 0f);
} else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
valueFrom = a.getColor(valueFromIndex, 0);
} else {
valueFrom = a.getInt(valueFromIndex, 0);
}
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) a.getDimension(valueToIndex, 0f);
} else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
valueTo = a.getColor(valueToIndex, 0);
} else {
valueTo = a.getInt(valueToIndex, 0);
}
anim.setIntValues(valueFrom, valueTo);
} else {
anim.setIntValues(valueFrom);
}
} else {
if (hasTo) {
if (toType == TypedValue.TYPE_DIMENSION) {
valueTo = (int) a.getDimension(valueToIndex, 0f);
} else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
valueTo = a.getColor(valueToIndex, 0);
} else {
valueTo = a.getInt(valueToIndex, 0);
}
anim.setIntValues(valueTo);
}
}
}
anim.setDuration(duration);
anim.setStartDelay(startDelay);
if (a.hasValue(/*com.android.internal.R.styleable.*/Animator_repeatCount)) {
anim.setRepeatCount(
a.getInt(/*com.android.internal.R.styleable.*/Animator_repeatCount, 0));
}
if (a.hasValue(/*com.android.internal.R.styleable.*/Animator_repeatMode)) {
anim.setRepeatMode(
a.getInt(/*com.android.internal.R.styleable.*/Animator_repeatMode,
ValueAnimator.RESTART));
}
//if (evaluator != null) {
// anim.setEvaluator(evaluator);
//}
final int resID =
a.getResourceId(/*com.android.internal.R.styleable.*/Animator_interpolator, 0);
if (resID > 0) {
anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID));
}
a.recycle();
return anim;
}
}