前面咱们讲了ofInt()和ofFloat()来定义动画,但ofInt()只能传入Integer类型的值,而ofFloat()则只能传入Float类型的值。那若是咱们须要操做其它类型的变量要怎么办呢?其实ValueAnimator还有一个函数ofObject(),能够传进去任何类型的变量,定义以下:java
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values);
它有两个参数,第一个是自定义的Evaluator,第二个是可变长参数,Object类型的;
你们可能会疑问,为何要强制传进去自定义的Evaluator?首先,你们知道Evaluator的做用是根据当前动画的显示进度,计算出当前进度下把对应的值。那既然Object对象是咱们自定的,那必然从进度到值的转换过程也必须由咱们来作,否则系统哪知道你要转成个什么鬼。
好了,如今咱们先简单看一下ofObject这个怎么用。
咱们先来看看咱们要实现的效果: android
从效果图中能够看到,按钮上的字母从A变化到Z,刚开始变的慢,后来逐渐加速;canvas
ValueAnimator animator = ValueAnimator.ofObject(new CharEvaluator(),new Character('A'),new Character('Z')); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { char text = (char)animation.getAnimatedValue(); tv.setText(String.valueOf(text)); } }); animator.setDuration(10000); animator.setInterpolator(new AccelerateInterpolator()); animator.start();
这里注意三点:
第一,构造时:ide
ValueAnimator animator = ValueAnimator.ofObject(new CharEvaluator(),new Character('A'),new Character('Z'));
咱们自定义的一个CharEvaluator,这个类实现,后面会讲;在初始化动画时,传进去的是Character对象,一个是字母A,一个是字母Z;
咱们这里要实现的效果是,对Character对象来作动画,利用动画自动从字母A变到字母Z,具体怎么实现就是CharEvaluator的事了,这里咱们只须要知道,在构造时传进去的是两个Character对象 函数
第二:看监听:布局
char text = (char)animation.getAnimatedValue(); tv.setText(String.valueOf(text));
经过animation.getAnimatedValue()获得当前动画的字符,而后把字符设置给textview;你们知道咱们构造时传进去的值类型是Character对象,因此在动画过程当中经过Evaluator返回的值类型必然跟构造时的类型是一致的,也是Character 动画
第三:插值器this
animator.setInterpolator(new AccelerateInterpolator());
咱们使用的是加速插值器,加速插值器的特色就是随着动画的进行,速度会愈来愈快,这点跟咱们上面的效果图是一致的。
下面最关键的就是看CharEvaluator是怎么实现的了,先抛开的代码,咱们先讲一个点,ASCII码中数值与字符的转换方法。
咱们知道在ASCII码表中,每一个字符都是有数字跟他一一对应的,字母A到字母Z之间的全部字母对应的数字区间为65到90;
并且在程序中,咱们能经过数字强转成对应的字符。
好比: lua
数字转字符:spa
char temp = (char)65;//获得的temp的值就是大写字母A
字符转数字:
char temp = 'A'; int num = (int)temp;
在这里获得的num值就是对应的ASCII码值65;
好了,在咱们理解了ASCII码数值与对应字符的转换原理以后,再来看看CharEvaluator的实现:
public class CharEvaluator implements TypeEvaluator<Character> { @Override public Character evaluate(float fraction, Character startValue, Character endValue) { int startInt = (int)startValue; int endInt = (int)endValue; int curInt = (int)(startInt + fraction *(endInt - startInt)); char result = (char)curInt; return result; } }
在这里,咱们就利用A-Z字符在ASCII码表中对应数字是连续且递增的原理,先求出来对应字符的数字值,而后再转换成对应的字符。代码难度不大,就再也不细讲了。
好了,到这里,有关ofObject()的使用你们应该就会了,上面咱们说过,ofObject()可以初始化任何对象,下面咱们就稍微加深些难度, 咱们自定义一个类对象,而后利用ofObject()来构造这个对象的动画。
咱们先看看这部分,咱们将实现的效果:
在这里,咱们自定义了一个View,在这个view上画一个圆,但这个圆是有动画效果的。从效果中能够看出使用的插值器应该是回弹插值器(BounceInterpolator)
下面就来看看这个动画是怎么作出来的
public class Point { private int radius; public Point(int radius){ this.radius = radius; } public int getRadius() { return radius; } public void setRadius(int radius) { this.radius = radius; } }
point类内容很简单,只有一个成员变量:radius表示当前point的半径。
public class MyPointView extends View { private Point mCurPoint; public MyPointView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mCurPoint != null){ Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.RED); paint.setStyle(Paint.Style.FILL); canvas.drawCircle(300,300,mCurPoint.getRadius(),paint); } } public void doPointAnim(){ ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),new Point(20),new Point(200)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurPoint = (Point)animation.getAnimatedValue(); invalidate(); } }); animator.setDuration(1000); animator.setInterpolator(new BounceInterpolator()); animator.start(); } }
在这段代码中,首先来看看供外部调用开始动画的doPointAnim()函数:
public void doPointAnim(){ ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),new Point(20),new Point(200)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurPoint = (Point)animation.getAnimatedValue(); invalidate(); } }); animator.setDuration(1000); animator.setInterpolator(new BounceInterpolator()); animator.start(); }
一样,先来看ofObject的构造动画的方法:
ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),new Point(20),new Point(200));
在构造动画时,动画所对应的值的类型是Point对象,那说明咱们自定义的PointEvaluator中的返回值也必然是Point了。有关PointEvaluator的实现后面再讲
而后再来看看动画过程监听:
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCurPoint = (Point)animation.getAnimatedValue(); invalidate(); } });
在监听过程当中,先根据animation.getAnimatedValue()获得当前动画进度所对应的Point实例,保存在mCurPoint中,而后强制刷新
在强制刷新以后,就会走到OnDraw()函数下面:
protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mCurPoint != null){ Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.RED); paint.setStyle(Paint.Style.FILL); canvas.drawCircle(300,300,mCurPoint.getRadius(),paint); } }
onDraw函数没什么难度,就是根据mCurPoint的半径在(300,300)的位置画出来圆形,有关绘图的知识你们能够参考另外一个系列《android Graphics(一):概述及基本几何图形绘制》
在构造ofObject中,咱们也能够知道,初始值和动画中间值的类型都是Point类型,因此PointEvaluator输入的返回类型都应该是Point类型的,先看看PointEvaluator的完整代码:
public class PointEvaluator implements TypeEvaluator<Point> { @Override public Point evaluate(float fraction, Point startValue, Point endValue) { int start = startValue.getRadius(); int end = endValue.getRadius(); int curValue = (int)(start + fraction * (end - start)); return new Point(curValue); } }
这段代码其实比较容易理解,就是根据初始半径和最终半径求出当前动画进程所对应的半径值,而后新建一个Point对象返回。
首先在main.xml中添加对应的控件布局:从效果图中也能够看到,咱们将MyPointView是布局在最下方的,布局代码以下:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:padding="10dp" android:text="start anim" /> <Button android:id="@+id/btn_cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:padding="10dp" android:text="cancel anim" /> <TextView android:id="@+id/tv" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:gravity="center" android:padding="10dp" android:background="#ffff00" android:text="Hello qijian"/> <com.harvic.BlogValueAnimator4.MyPointView android:id="@+id/pointview" android:layout_below="@id/tv" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
其实也没什么难度,就是在原来的布局代码下面加一个MyPointView控件,难度不大,再也不细讲了
而后咱们来看看在MyActivity.java中是怎么来用的吧
public class MyActivity extends Activity { private Button btnStart; private MyPointView mPointView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btnStart = (Button) findViewById(R.id.btn); mPointView = (MyPointView)findViewById(R.id.pointview); btnStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mPointView.doPointAnim(); } }); } }
这段代码没什么难度,就是在点击start anim按钮的时候,调用mPointView.doPointAnim()方法开始动画。