上一篇文章 原文翻译 Android_Develop_API Guides_Animation Resources(动画资源)
介绍了Android中的动画资源,里面有一个章节是讲如何自定义插值器(Custom interpolators)的。
但是当前Android只为我们提供了自定义基于现有插值器的部分定制,只能修改当前要被修改的插值器所支持的属性。
例如:
文件位置:res/anim/my_overshoot_interpolator.xml:
<?xml version="1.0" encoding="utf-8"?> <overshootInterpolator xmlns:android="http://schemas.android.com/apk/res/android" android:tension="7.0" />
这个插值器只是改变了<overshootInterpolator>动画的tension属性,将默认的2改成了7。
我之前试着写了一个一个自定义插值器XML文件:
对应Java类文件:com.and.resource.anim.FlashInterpolator.java
package com.and.resource.anim; import android.view.animation.Interpolator;
/** * 自定义插入帧.(实现Interpolator接口) * * @author chenjl * */ public class FlashInterpolator implements Interpolator { @Override public float getInterpolation(float input) { if (input < 0.5f) { return 0; } else { return 1; } } }
在anim下创建了该插值器类对应的文件,文件位置:res/anim/flash_interpolator.xml
<?xml version="1.0" encoding="utf-8"?> <flashInterpolator />
然后在我的动画XML文件中通过android:interpolator属性引用该文件:
文件位置:res/anim/my_animation.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <alpha android:interpolator="@anim/flash_interpolator" android:duration="4000" android:fillAfter="true" android:fromAlpha="1" android:repeatCount="infinite" android:repeatMode="restart" android:toAlpha="0.01" > </alpha> </set>
Okay,最后,我像加载普通的anim动画XML文件一样,调用
AnimationUtils.loadAnimation(this, R.anim.my_animation);
来加载这个动画。但是,报错了,错误信息如下:
12-09 05:11:41.144: E/AndroidRuntime(3649): Caused by: java.lang.RuntimeException: Unknown interpolator name: flashInterpolator 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.view.animation.AnimationUtils.createInterpolatorFromXml(AnimationUtils.java:328) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.view.animation.AnimationUtils.loadInterpolator(AnimationUtils.java:271) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.view.animation.Animation.setInterpolator(Animation.java:391) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.view.animation.Animation.<init>(Animation.java:255) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.view.animation.AlphaAnimation.<init>(AlphaAnimation.java:40) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.view.animation.AnimationUtils.createAnimationFromXml(AnimationUtils.java:116) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.view.animation.AnimationUtils.createAnimationFromXml(AnimationUtils.java:114) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.view.animation.AnimationUtils.createAnimationFromXml(AnimationUtils.java:91) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.view.animation.AnimationUtils.loadAnimation(AnimationUtils.java:72) 12-09 05:11:41.144: E/AndroidRuntime(3649): at com.and.resource.MainActivity.onCreate(MainActivity.java:25) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.app.Activity.performCreate(Activity.java:5231) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087) 12-09 05:11:41.144: E/AndroidRuntime(3649): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2159) 12-09 05:11:41.144: E/AndroidRuntime(3649): ... 11 more
跟着错误日记,进入相关源码查看报错位置。
AnimationUtils类中createInterpolatorFromXml(Context c, XmlPullParser parser)方法的源代码:
private static Interpolator createInterpolatorFromXml(Context c, XmlPullParser parser) throws XmlPullParserException, IOException { Interpolator interpolator = 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; } AttributeSet attrs = Xml.asAttributeSet(parser); String name = parser.getName(); if (name.equals("linearInterpolator")) { interpolator = new LinearInterpolator(c, attrs); } else if (name.equals("accelerateInterpolator")) { interpolator = new AccelerateInterpolator(c, attrs); } else if (name.equals("decelerateInterpolator")) { interpolator = new DecelerateInterpolator(c, attrs); } else if (name.equals("accelerateDecelerateInterpolator")) { interpolator = new AccelerateDecelerateInterpolator(c, attrs); } else if (name.equals("cycleInterpolator")) { interpolator = new CycleInterpolator(c, attrs); } else if (name.equals("anticipateInterpolator")) { interpolator = new AnticipateInterpolator(c, attrs); } else if (name.equals("overshootInterpolator")) { interpolator = new OvershootInterpolator(c, attrs); } else if (name.equals("anticipateOvershootInterpolator")) { interpolator = new AnticipateOvershootInterpolator(c, attrs); } else if (name.equals("bounceInterpolator")) { interpolator = new BounceInterpolator(c, attrs); } else { throw new RuntimeException("Unknown interpolator name: " + parser.getName()); } } return interpolator; }
我很快发现,这里的if判断,根本没有考虑其他的自定义插值器类,如果遇到未知的,会直接抛出运行时错误。
那为什么要把插值器接口开放呢!!!?好吧,答案是,的确可以通过代码来使用:
ImageView myImage = (ImageView) findViewById(R.id.img_anim_test); Animation myAnim = AnimationUtils.loadAnimation(this, R.anim.my_animation); FlashInterpolator myInterpolator = new FlashInterpolator(); myAnim.setInterpolator(myInterpolator); myImage.startAnimation(myAnim);
但我们可以改造下这个AnimationUtils -> createInterpolatorFromXml(Context c, XmlPullParser parser) 方法。定义一个类,例如,OptAnimationUtils,然后将AnimationUtils -> createInterpolatorFromXml(Context c, XmlPullParser parser)中的源码拷贝过来,将方法中的:
else { throw new RuntimeException("Unknown interpolator name: " + parser.getName()); }
改成:
else { try { interpolator = (Interpolator) Class.forName(name).getConstructor(Context.class, AttributeSet.class).newInstance(c, attrs); } catch (Exception te) { throw new RuntimeException("Unknown interpolator name: " + parser.getName() + " error:" + te.getMessage()); } }
当然,你需要把Interpolator loadInterpolator(Context context, int id)方法也拷贝进来。
然后,这么使用:
1)修改 res/anim/flash_interpolator.xml
<?xml version="1.0" encoding="utf-8"?> <com.and.resource.anim.FlashInterpolator />
2)修改com.and.resource.anim.FlashInterpolator,添加构造方法:
package com.and.resource.anim; import android.content.Context; import android.util.AttributeSet; import android.view.animation.Interpolator; /** * 自定义插入帧.(实现Interpolator接口) * * @author chenjl * */ public class FlashInterpolator implements Interpolator { public FlashInterpolator() { } public FlashInterpolator(Context context, AttributeSet attrs) { } @Override public float getInterpolation(float input) { if (input < 0.5f) { return 0; } else { return 1; } } }
3)代码中使用:
myAnim.setInterpolator(OptAnimationUtils.loadInterpolator(this, R.anim.flash_interpolator));
目前,我们仍然无法将这个自定义的插值器在某个动画XML中直接通过android:interpolator属性来引用,因为我们的程序无法修改系统的源码。
貌似这个方法很鸡肋啊,因为我们只需要在代码中new出插值器类,然后使用Animation.setInterpolator(Interpolator in);设置即可,上面的方法貌似多了许多步骤。
但是,这里只是告诉大家一个方法,如果你自定义了Animation同时自定义了插值器,那么这个方法就允许你直接将插值器通过android:interpolator直接放置在XML动画资源中了。
如果是大批量的动画文件,那不是很有用嘛。^_^
Okay,到此结束了。