个人觉得,一款移动应用软件,影响用户体验的很大一部分就取决于动画,Android中也是如此,因此在应用层开发中我个人也很注重动画的使用,在能够熟练使用后就想探究其原理了,因此,开这篇博客不仅仅是对动画的一个总结,也是对动画的更深入的一个探究。话不多说,开写了。
Android动画可以分为三类:Tween Animation(补间动画)、Frame Animatino(帧动画)、Property Animation(属性动画),其中,补间动画比较简单,无非就是平移、缩放、透明度渐变,这些只需要根据相关api设置好想要的结果参数即可,想要达到预期效果可能会经过不断地调试设置~0.0~,当然,如果是老手,已经熟练了,熟练到知道一个dp、sp在手机上是多少个单位以及结果图在脑海里的样子 -0-会很容易了~。本文会重点介绍补间动画和属性动画,尤其是属性动画。
一、补间动画
1. 补间动画的使用
补间动画又分为四大类,分别是:旋转动画、平移动画、缩放动画、透明度渐变动画。下面从xml开始依次介绍动画的用法和相关的属性,最后通过直接用代码实现动画来探究其原理。
(1)旋转动画RoateAnimation
在补间动画中,动画都是平面2D效果,因此旋转动画也是指逆时针或顺时针旋转,其用法无非就是设置各个属性,其各属性含义如下:
<?xml version="1.0" encoding="utf-8"?> <!-- 旋转动画 --> <!-- ====== 属性说明 ========= formDegrees: 开始旋转前的角度 toDegrees: 旋转后的角度 pivotX: 中心点X的坐标,其单位%p表示相对父容器的坐标,%表示相对自己的坐标,没有单位只有数字则是指在屏幕上坐标的x轴坐标 pivotY: 旋转中心店Y的坐标 duration: 动画持续时间 fillAfter: 如果为true(前提是fillEnable不是false,fillEnable属性默认为true),则动画执行完毕后会保留执行后的状态 startOffset: 动画开始的延迟时间,单位是毫秒,比如如果该值为3000,那么动画就在3秒后执行 repeatCount: 动画执行的次数 repeatMode: 重复的方式,枚举类型,有两种选择"restart"表示执行完一次动画后又从头执行,"reverse"本身就是相反的意思, 它表示动画执行完动画后沿着原来旋转的方向反向旋转回来 --> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:detachWallpaper="true" android:duration="1400" android:fillAfter="false" android:fromDegrees="0" android:pivotX="50%" android:pivotY="50%" android:repeatCount="2" android:repeatMode="reverse" android:toDegrees="180" android:zAdjustment="bottom" > </rotate>以上的动画效果是:一个View绕其中心点顺时针旋转180°,并重复两次,在结束第一次动画后“原路返回”执行第二次动画(repeateMode属性设置为reverse的效果),动画持续时间是1.4秒,并且动画在结束后回到执行动画之前的位置(如果fillAfter设置为true,则执行动画完毕后视图保持执行后的状态)。
值得注意的就是pivotX和pivotY属性的单位,如果带有%表示相对自己旋转,如假设pivotX和pivotY都是50%,则表示动画以本视图的中心点(宽/2和高/2)旋转,如果是50%p,则表示动画以该视图的父视图的中心点来旋转。如果是50,则表示相对于屏幕坐标系的绝对坐标来旋转。
xml设置动画完毕,就差在java代码中使用了,使用方法很简单,如下两行代码搞定:
Animation anima = AnimationUtils.loadAnimation(mContext, R.anim.anim_rote); mImageView.startAnimation(anima);这样就可以让一个ImageView旋转起来,当然这是最简单的动画效果。现在假设我们有这样一个需求,在上面的动画基础上,我们需要在动画执行完成后,让该动画在上次的动画基础上继续执行,也就是前一个动画是从0到180°旋转,下一个动画就开始从180°到360°旋转,虽然fillAfter设置为true可以做到动画完成后保存其执行后的状态,但是当动画再次执行时仍然是从0到180°执行,因此,我们需要在动画结束后保存其执行后的状态,然后让其再次执行时从上一次执行后的状态开始执行,这就需要对动画进行监听,然而,我们要想实现这个需求使用xml并不方便,因为需要动态改变其属性,所以需要在代码中创建动画,这就体现了在代码中创建动画的灵活性但是维护性差评。对上面的代码改造如下:
/** * 旋转 * @param view */ public void onRote(View view){ // Animation anima = AnimationUtils.loadAnimation(mContext, R.anim.anim_rote); mFlag++; Animation anima = null; if(mFlag%2==1){ mToDegrees =180; mFromeDegrees = 0; anima = new RotateAnimation(mFromeDegrees, mToDegrees,Animation.RELATIVE_TO_SELF, 0.5F, Animation.RELATIVE_TO_SELF,0.5F); }else if(mFlag%2==0){ mToDegrees =360; mFromeDegrees = 180; anima = new RotateAnimation(mFromeDegrees, mToDegrees, Animation.RELATIVE_TO_SELF, 0.5F, Animation.RELATIVE_TO_SELF,0.5F); } anima.setDuration(1000); anima.setFillAfter(true); anima.setRepeatMode(Animation.REVERSE); anima.setRepeatCount(2); anima.setAnimationListener(new AnimationListener()); mImageView.startAnimation(anima); } /** * 动画监听 * * @author Administrator * */ @SuppressLint("NewApi") private class AnimationListener implements android.view.animation.Animation.AnimationListener { @Override public void onAnimationEnd(Animation animation) { // TODO Auto-generated method stub Animation anima = AnimationUtils.loadAnimation(mContext, R.anim.anim_rote_01); // mImageView.startAnimation(anima); // mImageView.postInvalidate(); // startActivity(new---activity); Toast.makeText(mContext, "动画执行结束", Toast.LENGTH_LONG).show(); } @Override public void onAnimationRepeat(Animation animation) { // TODO Auto-generated method stub Toast.makeText(mContext, "动画重复", Toast.LENGTH_LONG).show(); } @Override public void onAnimationStart(Animation animation) { // TODO Auto-generated method stub Toast.makeText(mContext, "动画开始了", Toast.LENGTH_LONG).show(); } }上面的代码创建的动画效果和xml创建的是一样的,需要注意的是,在创建Animation对象的时候,API中提供了三个构造器,三个构造器源码如下:
/** * Constructor used when a RotateAnimation is loaded from a resource. * * @param context Application context to use * @param attrs Attribute set from which to read values */ public RotateAnimation(Context context, AttributeSet attrs) { //此构造器是在用AnimationUtils.load的时候被调用,我们并不会直接使用到,官方注释已经很清楚了。 } /** * Constructor to use when building a RotateAnimation from code. * Default pivotX/pivotY point is (0,0). * 这个构造器比较简单,就是一个起始角度和结束角度,一般比较少用,因为我们需要指定旋转中心点 * @param fromDegrees Rotation offset to apply at the start of the * animation. * * @param toDegrees Rotation offset to apply at the end of the animation. */ public RotateAnimation(float fromDegrees, float toDegrees) { mFromDegrees = fromDegrees; mToDegrees = toDegrees; mPivotX = 0.0f; mPivotY = 0.0f; } /** * Constructor to use when building a RotateAnimation from code * 这个用的也不是很多,就是在上一个构造器的基础上添加了poivotX和pivotY两个参数用于指定旋转点 * @param fromDegrees Rotation offset to apply at the start of the * animation. * * @param toDegrees Rotation offset to apply at the end of the animation. * * @param pivotX The X coordinate of the point about which the object is * being rotated, specified as an absolute number where 0 is the left * edge. * @param pivotY The Y coordinate of the point about which the object is * being rotated, specified as an absolute number where 0 is the top * edge. */ public RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY) { mFromDegrees = fromDegrees; mToDegrees = toDegrees; mPivotXType = ABSOLUTE; mPivotYType = ABSOLUTE; mPivotXValue = pivotX; mPivotYValue = pivotY; initializePivotPoint(); } /** * Constructor to use when building a RotateAnimation from code * 重点就是这个构造器,是我们常用的,第三个参数是用于指定旋转的类型,有三种选择: * 1、相对自己(通过Animation.RELATIVE_TO_SELF设置) 2、绝对旋转(相对屏幕坐标系,通过Animation.ABSOLUTE) * 3、相对父视图。我们通过Animation.RELATIVE_TO_PARENT设置 * * @param fromDegrees Rotation offset to apply at the start of the * animation. * * @param toDegrees Rotation offset to apply at the end of the animation. * * @param pivotXType Specifies how pivotXValue should be interpreted. One of * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or * Animation.RELATIVE_TO_PARENT. * @param pivotXValue The X coordinate of the point about which the object * is being rotated, specified as an absolute number where 0 is the * left edge. This value can either be an absolute number if * pivotXType is ABSOLUTE, or a percentage (where 1.0 is 100%) * otherwise. * @param pivotYType Specifies how pivotYValue should be interpreted. One of * Animation.ABSOLUTE, Animation.RELATIVE_TO_SELF, or * Animation.RELATIVE_TO_PARENT. * @param pivotYValue The Y coordinate of the point about which the object * is being rotated, specified as an absolute number where 0 is the * top edge. This value can either be an absolute number if * pivotYType is ABSOLUTE, or a percentage (where 1.0 is 100%) * otherwise. */ public RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) { mFromDegrees = fromDegrees; mToDegrees = toDegrees; mPivotXValue = pivotXValue; mPivotXType = pivotXType; mPivotYValue = pivotYValue; mPivotYType = pivotYType; initializePivotPoint(); }
(2)平移动画TranslateAnimation(略)
(3)缩放动画ScaleAnimation(略)
(4)透明度渐变动画AlphaAnimation(略)
上面就以旋转动画为例,将动画的通用属性展示出来,至于平移、缩放、透明度都是一样一样的,就几个特性不一样,但是都可以见名思意的,因此在这里不再赘述。下面直接进入主题,补间动画的实现原理分析,源码分析依然是从RoateAnimation开始。
2.补间动画原理源码初窥
(1)xml文件加载过程分析
通常我们加载xml文件生成一个Animation对象时,是用过Google封装的AnimationUtils的loadAnimation()方法,那么我们就从这个方法看看一个xml文件是如何让一个View动起来的。
/** * Loads an {@link Animation} object from a resource * * @param context Application context used to access resources * @param id The resource id of the animation to load * @return The animation object reference by the specified id * @throws NotFoundException when the animation cannot be loaded */ public static Animation loadAnimation(Context context, int id) throws NotFoundException { //XmlResourceParser接口是继承XmlPullParser解析器和AttributeSet接口的,AttributeSet接口以前说过,就不用再说了, // XmlResourceParser parser = null; try { //这里根据id获取解析器然后调用createAnimationFormXml方法生成一个Animation对象 parser = context.getResources().getAnimation(id); return createAnimationFromXml(context, parser); } catch (XmlPullParserException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } catch (IOException ex) { NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + Integer.toHexString(id)); rnf.initCause(ex); throw rnf; } finally { if (parser != null) parser.close(); } }正如上面的注释那样,loadAnimation()中其实也没啥代码,就是获取一下解析器然后调用createAnimationFromXml()来生成Animation实例:
private static Animation createAnimationFromXml(Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException { //声明一个Animationd对象 Animation anim = null; // Make sure we are on a start tag. int type; int depth = parser.getDepth(); //再说一次,Android中对资源xml文件解析都是使用Pull解析的,这里先判断是否有下一个节点,是否结束 while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); //如果是set节点(就是我们多中组合动画的SetAnimation了),就递归调用自己,直到把所有的动画解析完毕 //然后下面是一系列的判断根据alpha、scale、rotate、translate来生成不同的动画 if (name.equals("set")) { anim = new AnimationSet(c, attrs); createAnimationFromXml(c, parser, (AnimationSet)anim, attrs); } else if (name.equals("alpha")) { anim = new AlphaAnimation(c, attrs); } else if (name.equals("scale")) { anim = new ScaleAnimation(c, attrs); } else if (name.equals("rotate")) { anim = new RotateAnimation(c, attrs); } else if (name.equals("translate")) { anim = new TranslateAnimation(c, attrs); } else { throw new RuntimeException("Unknown animation name: " + parser.getName()); } if (parent != null) { parent.addAnimation(anim); } } return anim; }由上面的代码可以看出来,在使用xml文件加载动画时,其本质上还是通过解析xml文件根据节点来生成相应的XXXAnimation实例,因此,如果Android项目中不用xml文件,所有布局、动画都直接用代码实现那么速度会快一些,但是也得不偿失,因为代码布局很生硬啊!一个项目得多长时间呢。好了,接下来就看看RoateAnimation中是怎么实现动画的吧!
首先看下这个类中定义的属性字段,不多,所以全贴出来:
private float mFromDegrees; private float mToDegrees; //注意这里,看到,默认的mPivotXType是绝对的,就是相对于坐标系的,这个ABSLUTE是在RoateAnimation的父类Animation中 //定义的 private int mPivotXType = ABSOLUTE; private int mPivotYType = ABSOLUTE; private float mPivotXValue = 0.0f; private float mPivotYValue = 0.0f; private float mPivotX; private float mPivotY;属性字段就那么几个,各个字段的含义上面的xml文件都有说道,下面看下加载xml文件解析后使用到的RoateAnimation(Context context, AttributeSet attrs)构造器:
public RotateAnimation(Context context, AttributeSet attrs) { super(context, attrs); //获取类型数组,就是xml中属性值得类型,int、float等 TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.RotateAnimation); //获取到旋转后的位置角度以及开始的角度 mFromDegrees = a.getFloat( com.android.internal.R.styleable.RotateAnimation_fromDegrees, 0.0f); mToDegrees = a.getFloat(com.android.internal.R.styleable.RotateAnimation_toDegrees, 0.0f); //这个不用看,就封装了旋转类型和旋转角度的一系列转换 Description d = Description.parseValue(a.peekValue( com.android.internal.R.styleable.RotateAnimation_pivotX)); mPivotXType = d.type; mPivotXValue = d.value; d = Description.parseValue(a.peekValue( com.android.internal.R.styleable.RotateAnimation_pivotY)); mPivotYType = d.type; mPivotYValue = d.value; a.recycle(); //这个方法只有当mPivotXType为ABSOLUTE时才会起作用 initializePivotPoint(); }上面的代码几乎就是RoateAnimation所有的代码了,还有几个构造器上面已经介绍过。哦额,忘记了,这个类里面还有两个很重要的方法,是重写了其父类Animation的,那就是applyTransformation()方法和·initialize()方法。先说initialize()方法,顾名思义就是初始化的操作,源码如下:
@Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth); mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight); }可以看到,在RoateAnimation中该方法首先调用了父类的initialize()方法,其父类中的initialize()中会调用Animation的reset()方法,该方法中只有简单几行动画状态参数的初始化,再无其他。那么现在问题来了,动画是怎么被触发的呢?不激动,这得从我们如何开始一个动画说起,我们启动一个动画不就是一个mView.startAnimation()吗!那我们就去这个方法里找找去,startAnimation()源码如下:
/** * Start the specified animation now. * * @param animation the animation to start now */ public void startAnimation(Animation animation) { animation.setStartTime(Animation.START_ON_FIRST_FRAME); //给该View设置一个动画 setAnimation(animation); //使父容器缓存失效 invalidateParentCaches(); //意思就是使这个View失效,那么它就需要进行重绘,上面设置了一个setAnimation(),那么重绘时就会执行相应的动画了 //关于动画的一切都从这里开始,下一篇博客会从这个方法开始讲起 invalidate(true); }在RoateAnimation中还有一个方法applyTransformation(),这个方法是很关键的,名字直译过来就是应用变换,也就是开始动画的变换,动画的真正实现也是从这里开始,下面我们看下其源码:
@Override protected void applyTransformation(float interpolatedTime, Transformation t) { float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime); float scale = getScaleFactor(); if (mPivotX == 0.0f && mPivotY == 0.0f) { t.getMatrix().setRotate(degrees); } else { //如果有旋转点就设置旋转的中心点,先获取一个Matrix(矩阵) t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale); } }首先说下Transformation是个什么类,看其解释:
/** * Defines the transformation to be applied at * one point in time of an Animation. *意思就是:定义一个动画执行时的某个时间点的变换,这个变换是一个名词哈!稍后深入原理我们会讲到这个类。 */再看下Matrix的setRotate里面是个什么东东:
/** * Set the matrix to rotate about (0,0) by the specified number of degrees. * 这里调用了native_setRotate()方法,这个方法并不是java实现的,而是C,这个方法声明在Matrix如: * private static native void native_setRotate(int native_object,float degrees, float px, float py); */ public void setRotate(float degrees) { native_setRotate(native_instance, degrees); }可以看到,动画的实现实际上是调用底层方法实现的,java本身并不实现。这里不对Matrix做过多介绍,我们只是初窥,下一篇博客会有详细的介绍。补间动画就到此为止了,本文以RoateAnimation为例,将了补间动画在xml中和java代码中的使用。下面接着将属性动画。
二、属性动画(Property Animation)
属性动画是3.0后才有的特性,它比比补间动画要灵活且实现的功能更多一些,首先看·下关于属性动画的几个类:
(1)ValueAnimator: 动画执行类
(2)ObjectAnimator:动画执行类,是ValueAnimator的子类
(3)AnimatorSet:复杂组合动画,可以同时完成多个属性动画
(4) AnimatorInflater:用于加载xml的属性文件动画,和AnimationUtils.loadAnimation()作用一样
(5) TypeEvaluator : 类型估值,主要用于设置动画操作属性的值
(6)TimeInterpolator:时间插值,也就是插值器
1、ObjectAnimator的用法
(1)从xml加载属性动画
xml的旋转属性动画文件如下:
<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="3000" android:propertyName="rotationX" android:repeatCount="120" android:valueFrom="0.0" android:valueTo="360.0" android:valueType="floatType" />上面的动画是让一个View绕X轴完成360°旋转,每次动画执行3秒,重复120次,值得注意的是propertyName属性,该属性是一个View中所必须有的属性,并且为该属性提供了getter和setter方法,在xml文件中,该属性是一个enum类型,用alt+/可以查看 - -、如果在代码中不确定VIew有哪些属性,也可以用这种方式查看啊。。。在xml中布局完毕,就需要在代码中加载了,实现如下:
ObjectAnimator objAnima = (ObjectAnimator) AnimatorInflater.loadAnimator(this, R.animator.property_anima); objAnima.setTarget(mImageView); objAnima.start();很简单吧,一定要注意,在xml中不能为动画设置View目标,必须在代码中设置。下面看下在布局文件中使用set实现多动画组合,文件如下:
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" > <!-- 绕X轴旋转 --> <objectAnimator android:duration="30000" android:propertyName="rotationX" android:repeatCount="120" android:valueFrom="0.0" android:valueTo="360.0" android:valueType="floatType" /> <!-- 绕Y轴旋转 --> <objectAnimator android:duration="30000" android:propertyName="rotationY" android:repeatCount="120" android:valueFrom="0.0" android:valueTo="360.0" android:valueType="floatType" /> </set>这个动画完成的就是同时完成X轴和Y轴旋转,3D效果,在set节点中有一个ordering属性,该属性有两个可选值:sequentially表示动画一个个执行,together表示同时执行。在代码中加载也是一样的,只是把上面的ObjectAnimator改成AnimatorSet即可。
(2)在代码中直接创建ObjectAnimator动画
代码如下,实现和上面的一样绕着X轴旋转的效果
ObjectAnimator objAnima = ObjectAnimator.ofFloat(mImageView, "rotationX", 0.0F,360F); objAnima.setDuration(3000); objAnima.start();是一个代码使用需要注意的是,第二个参数,这个参数必须是View的一个具有getter和setter方法的参数,后面两个参数表示起始和结束角度(如果是平移则是距离了),可以是只有一个参数,默认一个起始位置参数。现在需求又变了,让VIew绕X轴、Y轴同时旋转并且期间有一个透明度变换,代码实现如下:
ObjectAnimator objAnima = ObjectAnimator.ofFloat(mImageView, "rotationX", 0.0F,360F); objAnima.setDuration(3000); ObjectAnimator objAnima1 = ObjectAnimator.ofFloat(mImageView, "rotationY", 0.0F,360F); objAnima1.setDuration(3000); ObjectAnimator objAnima2 = ObjectAnimator.ofFloat(mImageView, "alpha",1.0F,0.2F); objAnima2.setDuration(3000); AnimatorSet animaSet = new AnimatorSet(); //设置objAnima和objAnima1、objAnima2同时执行 animaSet.play(objAnima).with(objAnima1); animaSet.play(objAnima1).with(objAnima2); animaSet.start();
2、ValueAnimator用法
ValueaNnimator并不需要指定属性名称,而是通过监听来手动设置属性的,使用如下:
ValueAnimator valueAnima = ValueAnimator.ofFloat(0.0F,360F); valueAnima.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator va) { float value = (Float) va.getAnimatedValue(); mImageView.setRotationX(value); mImageView.setRotationY(value); } }); valueAnima.setDuration(3000); valueAnima.start();
现在我们知道了属性动画的基本用法,用法很是灵活,可以根据需求来实现很多丰富的效果,在Animator中还有两个重要的方法,那就是setEvaluator()和setInterpolator(null);关于这两个方法我们在动画下一篇博客里会说到。
三、动画的高级用法
1、补间动画的差之器Interpolator
插值器是一个时间插值类,实际上是一个速率的控制类,就是控制了动画在执行的过程中速率的控制,Interpolator是一个空的接口,源码如下:
/** * An interpolator defines the rate of change of an animation. This allows * the basic animation effects (alpha, scale, translate, rotate) to be * accelerated, decelerated, repeated, etc. */ public interface Interpolator extends TimeInterpolator { // A new interface, TimeInterpolator, was introduced for the new android.animation // package. This older Interpolator interface extends TimeInterpolator so that users of // the new Animator-based animations can use either the old Interpolator implementations or // new classes that implement TimeInterpolator directly. }可以看到,里面没有任何方法,但是实现该接口的有很多类,这里我直接引用别人的博客的差之器:
/** * An interpolator where the rate of change is constant * */ public class LinearInterpolator implements Interpolator { public LinearInterpolator() { } public LinearInterpolator(Context context, AttributeSet attrs) { } //这个参数就是输入的 public float getInterpolation(float input) { return input; } }以上就是他的全部源码了,啥也没有,是不是很简单,再来一个稍微复杂一点的CycleInterpolator,其源码如下:
/** * Repeats the animation for a specified number of cycles. The * rate of change follows a sinusoidal pattern. * */ public class CycleInterpolator implements Interpolator { public CycleInterpolator(float cycles) { mCycles = cycles; } public CycleInterpolator(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.CycleInterpolator); mCycles = a.getFloat(com.android.internal.R.styleable.CycleInterpolator_cycles, 1.0f); a.recycle(); } //这个值input就是一个小数,在0~1之间,0表示动画的开始,1表示动画结束, public float getInterpolation(float input) { return (float)(Math.sin(2 * mCycles * Math.PI * input)); } private float mCycles; }代码如此简单,我们自定义一个插值器当然也很简单了,如下是我定义的一个插值器
/** * 自定义差之器 * @author Administrator * */ private class MyInterpolator implements Interpolator{ @Override public float getInterpolation(float input) { //这是我随便写的一个“算法”.... float range = (float) (Math.cos(input)*Math.PI); return range; } }
本文完结。