关于Android的动画我们都有所接触,Android的动画有三种,分别是View动画、帧动画,还有就是本篇要说的属性动画。View动画我们很常见就是平移动画(TranslateAnimation)、缩放(ScaleAnimation)、旋转(RotateAnimation)、透明动画(AlphaAnimation),帧动画就是通过顺序依次播放的动画。这些动画都是使用在View上面的,也就是我们在已知的控件上面进行动画的操作。但是有时候我们需要的动画没有View怎么办?属性动画对View动画做了扩展,属性动画可以作用在对象上面,没有对象也可以。
上面的效果就是用的属性动画来实现的。我们要想实现上面的动画效果是离不了ValueAnimator的。ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过度,
public static ValueAnimator ofFloat(float... values) {
throw new RuntimeException("Stub!");
}
float… values为可变参数,就是参数的数量是不确定的相当于float[]values。具体用法就是ValueAnimator.ofFloat(startValue,endValue);
此外还有ValueAnimator.ofInt()此方法跟ValueAnimator.ofFloat()使用方式一样、ValueAnimator.ofArgb(),这是一个关于颜色以及透明度变化的具体使用为ValueAnimator animator = ValueAnimator.ofArgb(0xFFFF0000, 0xFF0000FF, 0xFFFF0000, 0xFF0000FF);、ValueAnimator.ofObject()是用于对任意对象进行动画操作的。
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
throw new RuntimeException("Stub!");
}
根据源码可以了解到使用的时候还需要实现一个自己的TypeEvaluator,那么怎么实现自己的TypeEvaluator呢?TypeEvaluator就是一个接口,请看下面的代码
public class ColorEvaluator implements TypeEvaluator<Integer>{
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int alpha = (int) (Color.alpha(startValue) + fraction *
(Color.alpha(endValue) - Color.alpha(startValue)));
int red = (int) (Color.red(startValue) + fraction *
(Color.red(endValue) - Color.red(startValue)));
int green = (int) (Color.green(startValue) + fraction *
(Color.green(endValue) - Color.green(startValue)));
int blue = (int) (Color.blue(startValue) + fraction *
(Color.blue(endValue) - Color.blue(startValue)));
return Color.argb(alpha, red, green, blue);
}
}
这是一个关于颜色以及透明度的,他的作用其实跟ValueAnimator.ofArgb()是一样的。其实属性动画不只是有ValueAnimator他还有ObjectAnimator,AnimatorSet。由于ObjectAnimator继承自ValueAnimator,所以说ValueAnimator的那几个方法ObjectAnimator也同样拥有,而且ObjectAnimator还有自己的一些方法.
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setIntValues(values);
return anim;
}
通过上面的源码我们可以知道ObjectAnimator.ofInt()还可以传入对象以及属性名。使用方式为
ObjectAnimator.ofInt(this,”color”,Color.BLUE, Color.RED);至于propertyName命名有什么要求为什么我传的是color,这是根据你的需求而定的,也不是随意命名的,而是有一定的规则的,请看下面的源码
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);
setPropertyName(propertyName);
}
通过setPropertyName(propertyName);我们再想想setColor(int color);至于那个propertyName参数该怎样传自己就知晓了吧。关于属性动画也会在下面的如何实现上面的效果里面多少涉及一些的知识进行阐述。
上面的是关于属性动画的一些方法,下面就说一说上面的效果如何实现吧。
上面的效果就是三个小圆点起始点在圆环的最上面,我们都知道关于圆的画法,那么圆的起点也就知道了
那么我们如何实现小圆点在上面的位置呢?我们可以吧小圆的起点位置定位3π/2,旋转一周需要2π,那么起始点的位置也就确定了ValueAnimator.ofFloat(3π/2, 7π/2);我们的小圆在转动的过程中我们如何绘制呢?这里就需要用到一个getAnimatedValue()这个方法了这个方法就是小圆在转动的过程中的值,它的变化范围是有我们的起点和终点来决定的也就是起点的值3π/2≈ 4.712,终点的时候的值为7π/2≈ 10.996。getAnimatedValue()返回的是一个对象,我们可以转换为float类型,我们知道这些就可以确定圆在转动过程中的位置,
movingCircleCenterX1 = (float) (circleCentre[0] + circleR * Math.cos((float) circleAnimator1.getAnimatedValue()));
movingCircleCenterY1 = (float) (circleCentre[1] + circleR * Math.sin((float) circleAnimator1.getAnimatedValue()));
这两行代码就是确定转动圆的位置的,当getAnimatedValue()的值为3π/2时,Math.sin(3π/2)=-1,Math.cos(3π/2)=0,当然了我是把屏幕的原点移动到圆心的位置了,所以当值为3π/2时,小圆的位置在最上面了。我们看到的小圆转动其实是假象,是我们把时间设置的过短造成的,如果时间很长的话你就会发现小圆是在不停的跳动的。
当然了getAnimatedValue()并不会直接获取我们的转动过程中的值,这还需要ValueAnimator.ofFloat()这个方法了,我们可以通过这个方法设置我们的起始值,只有设置了这个方法我们的getAnimatedValue()才能获取到当前的值。不过这个可不是ValueAnimator.ofFloat(3π/2, 7π/2);我们的起始值可以是3π/2,但是我们的终点值可不是7π/2,而是我们需要不停的获取小圆的位置,那么我们又该怎么设置呢?
circleR = (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
float fraction = (float) (Math.abs(y1 - y0) / circleR);
if (fraction < -1.0) {
fraction = -1.0f;
} else if (fraction > 1.0) {
fraction = 1.0f;
}
float movePosition = (float) Math.asin(fraction);
movePosition = 2 * pi - movePosition;
ValueAnimator circleAnimator = ValueAnimator.ofFloat(3*pi/2, movePosition + 2 * pi);
Math.abs(y1 - y0) / circleR获取的就是y的变化率,也就是小圆在圆环上的y位置的变化,circleR也就是圆环的半径了。小圆在移动的过程中y的变化是有-y—->0—->y—->0—->-y的反复变化的。x的变化则是0—->x—->0—->-x—->0的反复变化的。因为反余弦函数的变化范围是在-1到1所以我们要限制fraction的范围,否则会出现NAN的现象。根据fraction反余弦函数得到的就是当前的旋转弧度。ValueAnimator.ofFloat(3*pi/2, movePosition + 2 * pi);这个才是真正的传递小圆移动的位置的。其他的想必大家也都知道了,比如说延时了,设置旋转周期什么的,至于为什么会出现几个小圆的情况就是延时的效果,如果几个小圆不设置延时的话,你看到的只是一个小圆,其它的也就不多说了,具体的实现代码如下
package demo.liuyongxiang.com.demo.activities;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import demo.liuyongxiang.com.demo.R;
class CustomView1 extends View {
private float pi = (float) Math.PI;
private Paint paint;
private int movingCircleRadius;
private float circleR;
private ValueAnimator circleAnimator1;
private ValueAnimator circleAnimator2;
private ValueAnimator circleAnimator3;
private boolean isInit = true;
float movingCircleCenterX1, movingCircleCenterX2, movingCircleCenterX3, movingCircleCenterY1, movingCircleCenterY2, movingCircleCenterY3;
public CustomView1(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
movingCircleRadius = 10;
}
float[] circleCentre;
float[] movingCircle;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float originPointX = getWidth()*3/8;
float originPointY = getWidth()*3/8;
canvas.translate(originPointX, originPointY);
//初始化
Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.RED);
float centerCircleX = getWidth() / 7;
float centerCircleY = getHeight() / 7;
canvas.drawCircle(centerCircleX,centerCircleY,circleR,mPaint);
Bitmap bitmap;
bitmap=((BitmapDrawable)getResources().getDrawable(R.drawable.test)).getBitmap();
canvas.drawBitmap(bitmap, centerCircleX-bitmap.getWidth()/2, centerCircleY-bitmap.getHeight()/2, null);
if (isInit) {
circleCentre = new float[]{centerCircleX, centerCircleY};
paint.setColor(Color.RED);
movingCircle = new float[]{centerCircleX, movingCircleRadius};
movingCircle = onCiecleOriginalPostion(0f, movingCircle, circleCentre);
isInit = false;
loadingAnimator();
}
if (!circleAnimator1.isRunning()) {
canvas.drawCircle(movingCircle[0], movingCircle[1], movingCircleRadius, paint);
}
if (circleAnimator1.isRunning()) {
movingCircleCenterX1 = (float) (circleCentre[0] + circleR * Math.cos((float) circleAnimator1.getAnimatedValue()));
movingCircleCenterY1 = (float) (circleCentre[1] + circleR * Math.sin((float) circleAnimator1.getAnimatedValue()));
canvas.drawCircle(movingCircleCenterX1, movingCircleCenterY1, movingCircleRadius, paint);
}
if (circleAnimator2.isRunning()) {
movingCircleCenterX2 = (float) (circleCentre[0] + circleR * Math.cos((float) circleAnimator2.getAnimatedValue()));
movingCircleCenterY2 = (float) (circleCentre[1] + circleR * Math.sin((float) circleAnimator2.getAnimatedValue()));
canvas.drawCircle(movingCircleCenterX2, movingCircleCenterY2, movingCircleRadius, paint);
}
if (circleAnimator3.isRunning()) {
movingCircleCenterX3 = (float) (circleCentre[0] + circleR * Math.cos((float) circleAnimator3.getAnimatedValue()));
movingCircleCenterY3 = (float) (circleCentre[1] + circleR * Math.sin((float) circleAnimator3.getAnimatedValue()));
canvas.drawCircle(movingCircleCenterX3, movingCircleCenterY3, movingCircleRadius, paint);
}
if (circleAnimator1.isRunning() || circleAnimator2.isRunning() || circleAnimator3.isRunning()) {
invalidate();
}
}
private void loadingAnimator() {
circleAnimator1 = getCircleMovePosition(movingCircle, circleCentre, 0);
circleAnimator1.start();
circleAnimator2 = getCircleMovePosition(movingCircle, circleCentre, 300);
circleAnimator2.start();
circleAnimator3 = getCircleMovePosition(movingCircle, circleCentre, 600);
circleAnimator3.start();
postDelayed(new Runnable() {
@Override
public void run() {
loadingAnimator();
invalidate();
}
}, circleAnimator3.getDuration() + 600);
}
private ValueAnimator getCircleMovePosition(float[] start, float[] center, int delay) {
float x1 = start[0];
float y1 = start[1];
float x0 = center[0];
float y0 = center[1];
circleR = (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
float fraction = (float) (Math.abs(y1 - y0) / circleR);
if (fraction < -1.0) {
fraction = -1.0f;
} else if (fraction > 1.0) {
fraction = 1.0f;
}
float movePosition= (float) Math.asin(fraction);
movePosition= 2 * pi - movePosition;
ValueAnimator circleAnimator = ValueAnimator.ofFloat(3*pi/2, movePosition+ 2 * pi);
circleAnimator.setDuration(1000);
circleAnimator.setInterpolator(new DecelerateInterpolator());
circleAnimator.setStartDelay(delay);
return circleAnimator;
}
private float[] onCiecleOriginalPostion (float angle, float[] start, float[] center) {
float x1 = start[0];
float y1 = start[1];
float x0 = center[0];
float y0 = center[1];
float R = (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
float param = (float) (Math.abs(y1 - y0) / R);
if (param < -1.0) {
param = -1.0f;
} else if (param > 1.0) {
param = 1.0f;
}
float a = (float) Math.asin(param);
a = 2 * pi - a;
float x = (float) (center[0] + R * Math.cos(a + angle));
float y = (float) (center[1] + R * Math.sin(a + angle));
return new float[]{x, y};
}
}
xml布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<demo.liuyongxiang.com.demo.activities.CustomView1
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
点击下载源码
如果有什么疑问欢迎留言。