/*
* Copyright 2016 Flipkart Internet Pvt. Ltd.
*
* 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.flipkart.android.proteus.toolbox;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.Log;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.AnticipateOvershootInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.CycleInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import android.view.animation.PathInterpolator;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import com.flipkart.android.proteus.parser.ParseHelper;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.annotations.SerializedName;
/**
* Defines common utilities for working with animations.
*/
public class AnimationUtils {
private static final String TAG = "AnimationUtils";
private static final String LINEAR_INTERPOLATOR = "linearInterpolator";
private static final String ACCELERATE_INTERPOLATOR = "accelerateInterpolator";
private static final String DECELERATE_INTERPOLATOR = "decelerateInterpolator";
private static final String ACCELERATE_DECELERATE_INTERPOLATOR = "accelerateDecelerateInterpolator";
private static final String CYCLE_INTERPOLATOR = "cycleInterpolator";
private static final String ANTICIPATE_INTERPOLATOR = "anticipateInterpolator";
private static final String OVERSHOOT_INTERPOLATOR = "overshootInterpolator";
private static final String ANTICIPATE_OVERSHOOT_INTERPOLATOR = "anticipateOvershootInterpolator";
private static final String BOUNCE_INTERPOLATOR = "bounceInterpolator";
private static final String PATH_INTERPOLATOR = "pathInterpolator";
private static final String TYPE = "type";
private static final String SET = "set";
private static final String ALPHA = "alpha";
private static final String SCALE = "scale";
private static final String ROTATE = "rotate";
private static final String TRANSLATE = "translate";
private static final String PERCENT_SELF = "%";
private static final String PERCENT_RELATIVE_PARENT = "%p";
private static Gson sGson = new Gson();
/**
* Loads an {@link Animation} object from a resource
*
* @param context Application context used to access resources
* @param value JSON representation of the Animation
* @return The animation object reference by the specified id
* @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
*/
public static Animation loadAnimation(Context context, JsonElement value) throws Resources.NotFoundException {
Animation anim = null;
if (value.isJsonPrimitive()) {
anim = handleString(context, value.getAsString());
} else if (value.isJsonObject()) {
anim = handleElement(context, value.getAsJsonObject());
} else {
if (ProteusConstants.isLoggingEnabled()) {
Log.e(TAG, "Could not load animation for : " + value.toString());
}
}
return anim;
}
private static Animation handleString(Context c, String value) {
Animation anim = null;
if (ParseHelper.isTweenAnimationResource(value)) {
try {
Resources r = c.getResources();
int animationId = r.getIdentifier(value, "anim", c.getPackageName());
anim = android.view.animation.AnimationUtils.loadAnimation(c, animationId);
} catch (Exception ex) {
System.out.println("Could not load local resource " + value);
}
}
return anim;
}
private static Animation handleElement(Context c, JsonObject value) {
Animation anim = null;
JsonElement type = value.get(TYPE);
String animationType = type.getAsString();
AnimationProperties animationProperties = null;
if (SET.equalsIgnoreCase(animationType)) {
animationProperties = sGson.fromJson(value, AnimationSetProperties.class);
} else if (ALPHA.equalsIgnoreCase(animationType)) {
animationProperties = sGson.fromJson(value, AlphaAnimProperties.class);
} else if (SCALE.equalsIgnoreCase(animationType)) {
animationProperties = sGson.fromJson(value, ScaleAnimProperties.class);
} else if (ROTATE.equalsIgnoreCase(animationType)) {
animationProperties = sGson.fromJson(value, RotateAnimProperties.class);
} else if (TRANSLATE.equalsIgnoreCase(animationType)) {
animationProperties = sGson.fromJson(value, TranslateAnimProperties.class);
}
if (null != animationProperties) {
anim = animationProperties.instantiate(c);
}
return anim;
}
/**
* Loads an {@link Interpolator} object from a resource
*
* @param context Application context used to access resources
* @param value Json representation of the Interpolator
* @return The animation object reference by the specified id
* @throws android.content.res.Resources.NotFoundException
*/
public static Interpolator loadInterpolator(Context context, JsonElement value)
throws Resources.NotFoundException {
Interpolator interpolator = null;
if (value.isJsonPrimitive()) {
interpolator = handleStringInterpolator(context, value.getAsString());
} else if (value.isJsonObject()) {
interpolator = handleElementInterpolator(context, value.getAsJsonObject());
} else {
if (ProteusConstants.isLoggingEnabled()) {
Log.e(TAG, "Could not load interpolator for : " + value.toString());
}
}
return interpolator;
}
private static Interpolator handleStringInterpolator(Context c, String value) {
Interpolator interpolator = null;
if (ParseHelper.isTweenAnimationResource(value)) {
try {
Resources r = c.getResources();
int interpolatorID = r.getIdentifier(value, "anim", c.getPackageName());
interpolator = android.view.animation.AnimationUtils.loadInterpolator(c, interpolatorID);
} catch (Exception ex) {
System.out.println("Could not load local resource " + value);
}
}
return interpolator;
}
private static Interpolator handleElementInterpolator(Context c, JsonObject value) {
Interpolator interpolator = null;
JsonElement type = value.get("type");
String interpolatorType = type.getAsString();
InterpolatorProperties interpolatorProperties = null;
if (LINEAR_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolator = new LinearInterpolator();
} else if (ACCELERATE_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolator = new AccelerateInterpolator();
} else if (DECELERATE_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolator = new DecelerateInterpolator();
} else if (ACCELERATE_DECELERATE_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolator = new AccelerateDecelerateInterpolator();
} else if (CYCLE_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolatorProperties = sGson.fromJson(value, CycleInterpolatorProperties.class);
} else if (ANTICIPATE_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolatorProperties = sGson.fromJson(value, AnticipateInterpolatorProperties.class);
} else if (OVERSHOOT_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolatorProperties = sGson.fromJson(value, OvershootInterpolatorProperties.class);
} else if (ANTICIPATE_OVERSHOOT_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolatorProperties = sGson.fromJson(value, AnticipateOvershootInterpolatorProperties.class);
} else if (BOUNCE_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolator = new BounceInterpolator();
} else if (PATH_INTERPOLATOR.equalsIgnoreCase(interpolatorType)) {
interpolatorProperties = sGson.fromJson(value, PathInterpolatorProperties.class);
} else {
if (ProteusConstants.isLoggingEnabled()) {
Log.e(TAG, "Unknown interpolator name: " + interpolatorType);
}
throw new RuntimeException("Unknown interpolator name: " + interpolatorType);
}
if (null != interpolatorProperties) {
interpolator = interpolatorProperties.createInterpolator(c);
}
return interpolator;
}
/**
* Utility class to parse a string description of a size.
*/
private static class Description {
/**
* One of Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or
* Animation.RELATIVE_TO_PARENT.
*/
public int type;
/**
* The absolute or relative dimension for this Description.
*/
public float value;
/**
* Size descriptions can appear in three forms:
* <ol>
* <li>An absolute size. This is represented by a number.</li>
* <li>A size relative to the size of the object being animated. This
* is represented by a number followed by "%".</li> *
* <li>A size relative to the size of the parent of object being
* animated. This is represented by a number followed by "%p".</li>
* </ol>
*
* @param value The Json value to parse
* @return The parsed version of the description
*/
static Description parseValue(JsonPrimitive value) {
Description d = new Description();
d.type = Animation.ABSOLUTE;
d.value = 0;
if (value != null && (value.isNumber() || value.isString())) {
if (value.isNumber()) {
d.type = Animation.ABSOLUTE;
d.value = value.getAsNumber().floatValue();
} else {
String stringValue = value.getAsString();
if (stringValue.endsWith(PERCENT_SELF)) {
stringValue = stringValue.substring(0, stringValue.length() - PERCENT_SELF.length());
d.value = Float.parseFloat(stringValue) / 100;
d.type = Animation.RELATIVE_TO_SELF;
} else if (stringValue.endsWith(PERCENT_RELATIVE_PARENT)) {
stringValue = stringValue.substring(0, stringValue.length() - PERCENT_RELATIVE_PARENT.length());
d.value = Float.parseFloat(stringValue) / 100;
d.type = Animation.RELATIVE_TO_PARENT;
} else {
d.type = Animation.ABSOLUTE;
d.value = value.getAsNumber().floatValue();
}
}
}
return d;
}
}
private abstract static class AnimationProperties {
@SerializedName("detachWallpaper")
Boolean detachWallpaper;
@SerializedName("duration")
Long duration;
@SerializedName("fillAfter")
Boolean fillAfter;
@SerializedName("fillBefore")
Boolean fillBefore;
@SerializedName("fillEnabled")
Boolean fillEnabled;
@SerializedName("interpolator")
JsonElement interpolator;
@SerializedName("repeatCount")
Integer repeatCount;
@SerializedName("repeatMode")
Integer repeatMode;
@SerializedName("startOffset")
Long startOffset;
@SerializedName("zAdjustment")
Integer zAdjustment;
public Animation instantiate(Context c) {
Animation anim = createAnimation(c);
if (null != anim) {
if (null != detachWallpaper) {
anim.setDetachWallpaper(detachWallpaper);
}
if (null != duration) {
anim.setDuration(duration);
}
if (null != fillAfter) {
anim.setFillAfter(fillAfter);
}
if (null != fillBefore) {
anim.setFillBefore(fillBefore);
}
if (null != fillEnabled) {
anim.setFillEnabled(fillEnabled);
}
if (null != interpolator) {
Interpolator i = loadInterpolator(c, interpolator);
if (null != i) {
anim.setInterpolator(i);
}
}
if (null != repeatCount) {
anim.setRepeatCount(repeatCount);
}
if (null != repeatMode) {
anim.setRepeatMode(repeatMode);
}
if (null != startOffset) {
anim.setStartOffset(startOffset);
}
if (null != zAdjustment) {
anim.setZAdjustment(zAdjustment);
}
}
return anim;
}
abstract Animation createAnimation(Context c);
}
private static class AnimationSetProperties extends AnimationProperties {
@SerializedName("shareInterpolator")
Boolean shareInterpolator;
@SerializedName("children")
JsonElement children;
@Override
Animation createAnimation(Context c) {
AnimationSet animationSet = new AnimationSet(shareInterpolator == null ? true : shareInterpolator);
if (null != children) {
if (children.isJsonArray()) {
for (JsonElement element : children.getAsJsonArray()) {
Animation animation = loadAnimation(c, element);
if (null != animation) {
animationSet.addAnimation(animation);
}
}
} else if (children.isJsonObject() || children.isJsonPrimitive()) {
Animation animation = loadAnimation(c, children);
if (null != animation) {
animationSet.addAnimation(animation);
}
}
}
return animationSet;
}
}
private static class AlphaAnimProperties extends AnimationProperties {
@SerializedName("fromAlpha")
public Float fromAlpha;
@SerializedName("toAlpha")
public Float toAlpha;
@Override
Animation createAnimation(Context c) {
return null == fromAlpha || null == toAlpha ? null : new AlphaAnimation(fromAlpha, toAlpha);
}
}
private static class ScaleAnimProperties extends AnimationProperties {
@SerializedName("fromXScale")
public Float fromXScale;
@SerializedName("toXScale")
public Float toXScale;
@SerializedName("fromYScale")
public Float fromYScale;
@SerializedName("toYScale")
public Float toYScale;
@SerializedName("pivotX")
public JsonPrimitive pivotX;
@SerializedName("pivotY")
public JsonPrimitive pivotY;
@Override
Animation createAnimation(Context c) {
if (pivotX != null && pivotY != null) {
Description pivotXDesc = Description.parseValue(pivotX);
Description pivotYDesc = Description.parseValue(pivotY);
return new ScaleAnimation(fromXScale, toXScale, fromYScale, toYScale, pivotXDesc.type, pivotXDesc.value, pivotYDesc.type, pivotYDesc.value);
} else {
return new ScaleAnimation(fromXScale, toXScale, fromYScale, toYScale);
}
}
}
private static class TranslateAnimProperties extends AnimationProperties {
@SerializedName("fromXDelta")
public JsonPrimitive fromXDelta;
@SerializedName("toXDelta")
public JsonPrimitive toXDelta;
@SerializedName("fromYDelta")
public JsonPrimitive fromYDelta;
@SerializedName("toYDelta")
public JsonPrimitive toYDelta;
@Override
Animation createAnimation(Context c) {
Description fromXDeltaDescription = Description.parseValue(fromXDelta);
Description toXDeltaDescription = Description.parseValue(toXDelta);
Description fromYDeltaDescription = Description.parseValue(fromYDelta);
Description toYDeltaDescription = Description.parseValue(toYDelta);
return new TranslateAnimation(fromXDeltaDescription.type, fromXDeltaDescription.value, toXDeltaDescription.type, toXDeltaDescription.value, fromYDeltaDescription.type, fromYDeltaDescription.value, toYDeltaDescription.type, toYDeltaDescription.value);
}
}
private static class RotateAnimProperties extends AnimationProperties {
@SerializedName("fromDegrees")
public Float fromDegrees;
@SerializedName("toDegrees")
public Float toDegrees;
@SerializedName("pivotX")
public JsonPrimitive pivotX;
@SerializedName("pivotY")
public JsonPrimitive pivotY;
@Override
Animation createAnimation(Context c) {
if (null != pivotX && null != pivotY) {
Description pivotXDesc = Description.parseValue(pivotX);
Description pivotYDesc = Description.parseValue(pivotY);
return new RotateAnimation(fromDegrees, toDegrees, pivotXDesc.type, pivotXDesc.value, pivotYDesc.type, pivotYDesc.value);
} else {
return new RotateAnimation(fromDegrees, toDegrees);
}
}
}
private abstract static class InterpolatorProperties {
abstract Interpolator createInterpolator(Context c);
}
private static class PathInterpolatorProperties extends InterpolatorProperties {
@SerializedName("controlX1")
public Float controlX1;
@SerializedName("controlY1")
public Float controlY1;
@SerializedName("controlX2")
public Float controlX2;
@SerializedName("controlY2")
public Float controlY2;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
Interpolator createInterpolator(Context c) {
if (null != controlX2 && null != controlY2) {
return new PathInterpolator(controlX1, controlY1, controlX2, controlY2);
} else {
return new PathInterpolator(controlX1, controlY1);
}
}
}
private static class AnticipateInterpolatorProperties extends InterpolatorProperties {
@SerializedName("tension")
public Float tension;
Interpolator createInterpolator(Context c) {
return new AnticipateInterpolator(tension);
}
}
private static class OvershootInterpolatorProperties extends InterpolatorProperties {
@SerializedName("tension")
public Float tension;
Interpolator createInterpolator(Context c) {
return tension == null ? new OvershootInterpolator() : new OvershootInterpolator(tension);
}
}
private static class AnticipateOvershootInterpolatorProperties extends InterpolatorProperties {
@SerializedName("tension")
public Float tension;
@SerializedName("extraTension")
public Float extraTension;
Interpolator createInterpolator(Context c) {
return null == tension ? new AnticipateOvershootInterpolator() : (null == extraTension ? new AnticipateOvershootInterpolator(tension) : new AnticipateOvershootInterpolator(tension, extraTension));
}
}
private static class CycleInterpolatorProperties extends InterpolatorProperties {
@SerializedName("cycles")
public Float cycles;
Interpolator createInterpolator(Context c) {
return new CycleInterpolator(cycles);
}
}
}