本文知识点
ValueAnimator的认识与使用
估值器TypeEvaluator的自定义与使用
插值器TimeInterpolator的自定义与使用
Path于Animator的结合使用
ObjectAnimator的自定义与使用
TimeAnimator的使用
AnimatorSet动画集合的使用
Animator家族的监听器介绍与使用
Animator家族在xml中的使用
一直用动画,貌似还没有好好地总结一下,趁有空,总结一波
所谓动画,就是不停变化,在视觉上达到连续的效果
Animator的体系并不复杂,但内部实现挺复杂的,很多类常年埋没于底层,不见天日
如:PropertyValuesHolder及其子类
、Keyframes族
、Keyframe族
、KeyframeSet族
今天试着读了一下源码,基本上读的懵懵懂懂,总的思路算是把握了
Animator是一个抽象类,不可用,只能找它的子类
现在先看非常常用的ValueAnimator
ValueAnimator animator = ValueAnimator.ofInt(0, 10);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.e(TAG, animation.getAnimatedValue()+"---");
}
});
animator.start();
打印结果分析:
2018-12-26 12:04:09.290 ~ 2018-12-26 12:04:09.584---->584-290=294
默认持续时间是300(源码中定义的),基本一致,在这段时间内不断回调onAnimationUpdate方法
并且animation的值从预定的0~10之间不断变化,这就是ValueAnimator的基本用处
2018-12-26 12:04:09.290 2001-2001/com.toly1994.animator_test E/MainActivity: 0---
2018-12-26 12:04:09.335 2001-2001/com.toly1994.animator_test E/MainActivity: 0---
2018-12-26 12:04:09.351 2001-2001/com.toly1994.animator_test E/MainActivity: 1---
2018-12-26 12:04:09.373 2001-2001/com.toly1994.animator_test E/MainActivity: 1---
2018-12-26 12:04:09.412 2001-2001/com.toly1994.animator_test E/MainActivity: 3---
2018-12-26 12:04:09.439 2001-2001/com.toly1994.animator_test E/MainActivity: 5---
2018-12-26 12:04:09.450 2001-2001/com.toly1994.animator_test E/MainActivity: 5---
2018-12-26 12:04:09.468 2001-2001/com.toly1994.animator_test E/MainActivity: 6---
2018-12-26 12:04:09.484 2001-2001/com.toly1994.animator_test E/MainActivity: 7---
2018-12-26 12:04:09.502 2001-2001/com.toly1994.animator_test E/MainActivity: 8---
2018-12-26 12:04:09.517 2001-2001/com.toly1994.animator_test E/MainActivity: 8---
2018-12-26 12:04:09.534 2001-2001/com.toly1994.animator_test E/MainActivity: 9---
2018-12-26 12:04:09.568 2001-2001/com.toly1994.animator_test E/MainActivity: 9---
2018-12-26 12:04:09.584 2001-2001/com.toly1994.animator_test E/MainActivity: 10---
1).不断调用onAnimationUpdate回调
2).可以获取有规律变化的不同的数值
在自定义View中onAnimationUpdate刷新界面,并动态改变数值
/**
* 作者:张风捷特烈
* 时间:2018/12/26 0026:7:50
* 邮箱:[email protected]
* 说明:Animator测试View
*/
public class AnimatorView extends View {
private static final String TAG = "AnimatorView";
private Paint mPaint;//画笔
private int mRadius = 100;//小球初始半径
private ValueAnimator mAnimator;//动画器
public AnimatorView(Context context) {
this(context, null);
}
public AnimatorView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xff94E1F7);
mAnimator = ValueAnimator.ofInt(100, 300);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mRadius= (int) animation.getAnimatedValue();
invalidate();
}
});
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(400, 400);//移动坐标
canvas.drawCircle(0, 0, mRadius, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent: ");
mAnimator.start();//点击开启动画
break;
case MotionEvent.ACTION_UP:
}
return super.onTouchEvent(event);
}
}
其实道理很简单,就是把打印输出换成了刷新视图,而且半径在不断变化
看一下RESTART(默认)和REVERSE的区别
RESTART | REVERSE |
---|---|
|
|
mAnimator.setStartDelay(1000);//设置延迟
mAnimator.setRepeatCount(2);//设置重复执行次数
// mAnimator.setRepeatMode(ValueAnimator.RESTART);//重新开始100->300 100->300
mAnimator.setRepeatMode(ValueAnimator.REVERSE);//反转开始100->300 300->100
mAnimator.setDuration(1000);//设置时长
ofArgb
与ofObject
颜色变化 | 颜色大小 |
---|---|
|
|
ofArgb
传入两个颜色(起始色和终止色)
mColorAnimator = ValueAnimator.ofArgb(0xff94E1F7, 0xffF35519);
mColorAnimator.setDuration(500);//设置时长
mColorAnimator.setRepeatCount(1);//设置重复执行次数
mColorAnimator.setRepeatMode(ValueAnimator.REVERSE);
mColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPaint.setColor((Integer) animation.getAnimatedValue());
invalidate();
}
});
ValueAnimator.ofObject
+TypeEvaluator
public class Ball {
public int color;
public int r;
public Ball() {
}
public Ball(int r, int color) {
this.color = color;
this.r = r;
}
}
TypeEvaluator是确定对象的各个属性如何变化,看下面例子:
这里fraction是分率,startValue和endValue分别是起始和终止对象的状态
public class BallEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Ball start = (Ball) startValue;//小球初始状态
Ball end = (Ball) endValue;//小球终止状态
Ball ball = new Ball();//当前小球
//半径=初始+分率*(结尾-初始) 比如运动到一半,分率是0.5
ball.r = (int) (start.r + fraction * (end.r - start.r));
//颜色怎么渐变?
ball.color = evaluateColor(fraction, start.color, end.color);
return null;
}
/**
* 根据分率计算颜色
*/
private int evaluateColor(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = (startInt & 0xff) / 255.0f;
int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = (endInt & 0xff) / 255.0f;
// convert from sRGB to linear
startR = (float) Math.pow(startR, 2.2);
startG = (float) Math.pow(startG, 2.2);
startB = (float) Math.pow(startB, 2.2);
endR = (float) Math.pow(endR, 2.2);
endG = (float) Math.pow(endG, 2.2);
endB = (float) Math.pow(endB, 2.2);
// compute the interpolated color in linear space
float a = startA + fraction * (endA - startA);
float r = startR + fraction * (endR - startR);
float g = startG + fraction * (endG - startG);
float b = startB + fraction * (endB - startB);
// convert back to sRGB in the [0..255] range
a = a * 255.0f;
r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;
return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
}
}
看源码中怎么渐变颜色的:
ArgbEvaluator.getInstance()
可以看到有个计算颜色的方法,拿来用呗(我直接拷过去用)
public static ValueAnimator ofArgb(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
anim.setEvaluator(ArgbEvaluator.getInstance());
return anim;
}
---->[计算颜色方法evaluate]---------------
public Object evaluate(float fraction, Object startValue, Object endValue) {
//计算颜色方法详情......
}
该方程是二次曲线:y=x*x/800 当然你也可以定义自己喜欢的方程
public class Ball {
public int color;
public int r;
public int x;
public int y;
public Ball() {
}
public Ball(int r, int color) {
this.color = color;
this.r = r;
}
public Ball(int r, int color, int x, int y) {
this.color = color;
this.r = r;
this.x = x;
this.y = y;
}
}
估值器修改:
public class BallEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Ball start = (Ball) startValue;
Ball end = (Ball) endValue;
Ball ball = new Ball();
ball.color = evaluateColor(fraction, start.color, end.color);
ball.r = (int) (start.r + fraction * (end.r - start.r));
ball.x = (int) (start.x + fraction * (end.x - start.x));
ball.y= ball.x*ball.x/800;//此处依赖x确定y值
return ball;
}
}
AnimatorView
public class AnimatorView extends View {
private static final String TAG = "AnimatorView";
private Paint mPaint;
private int mRadius = 50;
private int dx;
private int dy;
private ValueAnimator mAnimator;
private ValueAnimator mColorAnimator;
private ValueAnimator mObjAnimator;
public AnimatorView(Context context) {
this(context, null);
}
public AnimatorView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xff94E1F7);
Ball startBall = new Ball(50, 0xff94E1F7,0,0);
Ball endBall = new Ball(100, 0xffF35519,500,1000);
mObjAnimator = ValueAnimator.ofObject(new BallEvaluator(), startBall, endBall);
mObjAnimator.setRepeatMode(ValueAnimator.REVERSE);//反转开始100->300 300->100
mObjAnimator.setDuration(1000);//设置时长
mObjAnimator.setRepeatCount(1);//设置重复执行次数
mObjAnimator.setRepeatMode(ValueAnimator.REVERSE);
mObjAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Ball ball = (Ball) animation.getAnimatedValue();
mRadius = ball.r;
mPaint.setColor(ball.color);
dx=ball.x;
dy=ball.y;
Log.e(TAG, "onAnimationUpdate: "+dx+":"+dy);
invalidate();
}
});
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(dx, dy);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mObjAnimator.start();
break;
case MotionEvent.ACTION_UP:
}
return super.onTouchEvent(event);
}
}
基本套路就是这样,有了ofObject,属性随意变,还怕动画吗?
核心就是估值器的定义,其实ofInt,ofFloat,ofArgb只是适用了内置估值器而已
本质上和ofObject并没有什么不同,可以看成单属性的简易版ofObject
如果估值器TypeEvaluator告诉你给怎么跑,那么插值器则告诉你跑多快
下面演示一下三个内置插值器(内置还有几个,自己试试)和自定义的三个插值器
这里的input是从0~1变化的值,插值器就是改变input值的变化情况
public class D_Sin_Inter implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
//input是一个从0~1均匀变化的值
//从0到PI/2均匀变化的值
float rad = (float) (Math.PI/2 * input);
//返回这个弧度的sin值--sin曲线在0~PI/2区域是增长越来越缓慢,小球运动越来越缓慢
return (float) (Math.sin(rad));
}
}
public class A_Sin_Inter implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
//input是一个从0~1均匀变化的值
//从0到PI/2均匀变化的值
float rad = (float) (Math.PI/2 * input+Math.PI/2);
//返回这个弧度的sin值--sin曲线在PI/2~PI区域是降低越来越快
return (float) (1-(Math.sin(rad)));//返回1-
}
}
/**
* 作者:张风捷特烈
* 时间:2018/12/26 0026:20:41
* 邮箱:[email protected]
* 说明:Log型先快后慢
*/
public class D_Log_Inter implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
return (float) (Math.log10(1 + 9 * input));
}
}
插值器实际上就是基于input加工,时间流动(每次刷新间隔)是基本恒定的,
input是从0~1均匀变化的,通过input将其映射到一组对应关系上,就像数学中的函数
input是x,称为自变量,因变量y由函数式和x确定,返回值便是y,供代码中使用(D_Sin_Inter如下)
LinearInterpolator线性插值器也就是x=y,而已,本质是一样的
只需在名字数组和插值器数组里对应添加即可,其他会自动处理
public class AnimatorInterView extends View {
private static final String TAG = "AnimatorView";
private Paint mPaint;
private int mRadius = 50;
private int dx[];
private String[] mStrings;
private TimeInterpolator[] mInterpolators;
public AnimatorInterView(Context context) {
this(context, null);
}
public AnimatorInterView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xff94E1F7);
mPaint.setTextSize(40);
mStrings = new String[]{"Linear", "Bounce", "AOI", "OI", "D_sin", "D_log", "A_sin", "A_log"};
mInterpolators = new TimeInterpolator[]{
new LinearInterpolator(),
new BounceInterpolator(),
new AnticipateOvershootInterpolator(),
new OvershootInterpolator(),
new D_Sin_Inter(),
new D_Log_Inter(),
new A_Sin_Inter()};
dx = new int[mInterpolators.length];
}
private ValueAnimator createAnimator(int index, TimeInterpolator interpolator) {
ValueAnimator mAnimator = ValueAnimator.ofInt(0, 800);
mAnimator.setRepeatCount(1);//设置重复执行次数
mAnimator.setRepeatMode(ValueAnimator.REVERSE);//反转开始100->300 300->100
mAnimator.setDuration(3000);//设置时长
mAnimator.setInterpolator(interpolator);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
dx[index] = (int) animation.getAnimatedValue();
invalidate();
}
});
return mAnimator;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < dx.length; i++) {
canvas.translate(0, 120);
mPaint.setColor(0xff94E1F7);
canvas.drawCircle(mRadius + dx[i], mRadius, mRadius, mPaint);
mPaint.setColor(0xff000000);
mPaint.setStrokeWidth(4);
canvas.drawLine(mRadius, mRadius, 800 + mRadius, mRadius, mPaint);
canvas.drawText(mStrings[i], 800 + 3 * mRadius, mRadius, mPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
for (int i = 0; i < mInterpolators.length; i++) {
createAnimator(i, mInterpolators[i]).start();
}
break;
case MotionEvent.ACTION_UP:
}
return super.onTouchEvent(event);
}
}
核心是使用PathMeasure和DashPathEffect对路径的长度进行控制
关于Path的这方面知识,这里不做详解,详见:Android关于Path你所知道的和不知道的一切
/**
* 作者:张风捷特烈
* 时间:2018/12/26 0026:7:50
* 邮箱:[email protected]
* 说明:Animator与Path
*/
public class AnimatorPathView extends View {
private static final String TAG = "AnimatorView";
private Paint mPaint;
private Path mPath;
private PathMeasure pathMeasure;
public AnimatorPathView(Context context) {
this(context, null);
}
public AnimatorPathView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xff94E1F7);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mPaint.setStrokeJoin(Paint.Join.ROUND);
//测量路径
mPath = new Path();
mPath = nStarPath(mPath, 8, 250, 160);//八角形路径
pathMeasure = new PathMeasure(mPath, false);
}
private ValueAnimator createAnimator() {
ValueAnimator mAnimator = ValueAnimator.ofInt(0, 800);
mAnimator.setRepeatCount(1);//设置重复执行次数
mAnimator.setRepeatMode(ValueAnimator.REVERSE);//反转开始100->300 300->100
mAnimator.setDuration(3000);//设置时长
mAnimator.setInterpolator(new AnticipateOvershootInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = animation.getAnimatedFraction();
//核心:创建DashPathEffect
DashPathEffect effect = new DashPathEffect(
new float[]{
pathMeasure.getLength(),
pathMeasure.getLength()},
value * pathMeasure.getLength());
mPaint.setPathEffect(effect);
invalidate();
}
});
return mAnimator;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(250, 250);
canvas.drawPath(mPath, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
createAnimator().start();
break;
case MotionEvent.ACTION_UP:
}
return super.onTouchEvent(event);
}
/**
* n角星路径
*
* @param num 几角星
* @param R 外接圆半径
* @param r 内接圆半径
* @return n角星路径
*/
public static Path nStarPath(Path path, int num, float R, float r) {
float perDeg = 360 / num;
float degA = perDeg / 2 / 2;
float degB = 360 / (num - 1) / 2 - degA / 2 + degA;
path.moveTo((float) (Math.cos(rad(degA)) * R), (float) (-Math.sin(rad(degA)) * R));
for (int i = 0; i < num; i++) {
path.lineTo(
(float) (Math.cos(rad(degA + perDeg * i)) * R),
(float) (-Math.sin(rad(degA + perDeg * i)) * R));
path.lineTo(
(float) (Math.cos(rad(degB + perDeg * i)) * r),
(float) (-Math.sin(rad(degB + perDeg * i)) * r));
}
path.close();
return path;
}
/**
* 角度制化为弧度制
*
* @param deg 角度
* @return 弧度
*/
public static float rad(float deg) {
return (float) (deg * Math.PI / 180);
}
}
ObjectAnimator和TimeAnimator
:作为孩子,它老爸能做的它也能做,并且还会有一些自己的特长
ObjectAnimator针对有setXxx方法的属性,进行的"Xxx"属性变化动画
注:Xxx的首字母大小写都可以
private ObjectAnimator mMoveDown;//下移动画
mMoveDown = ObjectAnimator//创建实例
//(View,属性名,初始化值,结束值)
.ofFloat(this, "translationY", 0, 300)
.setDuration(1000);//设置时常
@Override//绘制方法
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(50, 50, 50, mPaint);
}
mMoveDown.start();//开启动画
加上背景看一下,可以看出是整个View进行了变化。
属性名 | 演示 | 解释 |
---|---|---|
alpha |
|
透明度1~0 |
translationX |
|
X方向移动 |
translationY |
|
Y方向移动 |
rotation |
|
旋转(默认View中心点) |
rotationX |
|
X轴旋转(默认View中心横轴) |
rotationY |
|
Y轴旋转(默认View中心纵轴) |
scaleX |
|
X缩放 倍数 |
scaleY |
|
Y缩放 倍数 |
setPivotX(200);
setPivotY(200);
多参情况Animator家族皆适用
)0-->360 360-->0 0-->90
.ofFloat(this, "rotation", 0, 360,360,0,0,90)
内置的只是一些常用的,我们也可以自定义自己的属性
必须用一个setXxx的方法,属性名则为xxx,调用重绘方法
public void setRadius(int radius) {
mRadius = radius;
invalidate();//记得重绘
}
ObjectAnimator//创建实例
//(View,属性名,初始化值,结束值)
.ofInt(this, "Radius", 100, 50,100,20,100)
.setDuration(3000);//设置时常
public void setColor(int color) {
mColor = color;
mPaint.setColor(mColor);
invalidate();//记得重绘
}
colorAnimator = ObjectAnimator//创建实例
//(View,属性名,初始化值,结束值)
.ofInt(this, "color", 0xff0000ff, 0xffF2BA38, 0xffDD70BC)
.setDuration(3000);
colorAnimator.setEvaluator(new ArgbEvaluator());//颜色的估值器
1.ValueAnimator需要手动添加监听,手动获取ValueAnimator的数据,手动书写变更逻辑
2.ObjectAnimator可以不用进行更新监听,核心在`setXxx`里进行,
也就是每次更新时会自己走setXxx里的方法,这样方便在外部使用来动态改变属性
3.ValueAnimator的灵活性要好,毕竟自己动手,可以脑洞大开,想怎么玩怎么玩
4.ObjectAnimator针对有setXxx的属性进行动画,两者的侧重点不同
5.总的来说ObjectAnimator向于应用(简洁,快速),ValueAnimator偏向于操作(灵活,多变)
这个类总共代码100行,而且几乎一半都是注释
它继承自ValueAnimator,可谓也是Animator家族的掌上明珠,但非常纯真与专注
她想做的只有一件事:提供一条时间流(每个16或17ms回调一次方法)
mAnimator = new TimeAnimator();
////(自己,运行总时长,每次回调的时间间隔)
mAnimator.setTimeListener((animation, totalTime, deltaTime) -> {
Log.e(TAG, "totalTime:" + totalTime + ", deltaTime:" + deltaTime);
if (totalTime > 300) {
animation.pause();
}
});
运行结果:
2018-12-27 10:09:35.047 E/TimeAnimatorView: totalTime:0, deltaTime:0
2018-12-27 10:09:35.051 E/TimeAnimatorView: totalTime:2, deltaTime:2
2018-12-27 10:09:35.068 E/TimeAnimatorView: totalTime:19, deltaTime:17
2018-12-27 10:09:35.085 E/TimeAnimatorView: totalTime:36, deltaTime:17
2018-12-27 10:09:35.101 E/TimeAnimatorView: totalTime:52, deltaTime:16
2018-12-27 10:09:35.118 E/TimeAnimatorView: totalTime:69, deltaTime:17
2018-12-27 10:09:35.135 E/TimeAnimatorView: totalTime:86, deltaTime:17
2018-12-27 10:09:35.151 E/TimeAnimatorView: totalTime:102, deltaTime:16
2018-12-27 10:09:35.167 E/TimeAnimatorView: totalTime:119, deltaTime:17
2018-12-27 10:09:35.184 E/TimeAnimatorView: totalTime:136, deltaTime:17
2018-12-27 10:09:35.200 E/TimeAnimatorView: totalTime:152, deltaTime:16
2018-12-27 10:09:35.218 E/TimeAnimatorView: totalTime:169, deltaTime:17
2018-12-27 10:09:35.234 E/TimeAnimatorView: totalTime:186, deltaTime:17
2018-12-27 10:09:35.251 E/TimeAnimatorView: totalTime:202, deltaTime:16
2018-12-27 10:09:35.268 E/TimeAnimatorView: totalTime:219, deltaTime:17
2018-12-27 10:09:35.284 E/TimeAnimatorView: totalTime:236, deltaTime:17
2018-12-27 10:09:35.300 E/TimeAnimatorView: totalTime:252, deltaTime:16
2018-12-27 10:09:35.318 E/TimeAnimatorView: totalTime:269, deltaTime:17
2018-12-27 10:09:35.334 E/TimeAnimatorView: totalTime:286, deltaTime:17
2018-12-27 10:09:35.350 E/TimeAnimatorView: totalTime:303, deltaTime:17
这样关于ValueAnimator基本上就结束了(还有几个监听,最后一起将)
综合前几次的动画效果,拼装在一起,AnimatorSet本身并不难
源码一翻,可见里面有个Builder,可就是建造者模式了,
每个动画在AnimatorSet中是一个Node,Budiler中的方法就是:
为处理当前节点和插入节点的关系,看下面一组动画 :
mSet//半径-->移动+渐变-->变色
.play(translationX)//移动
.with(alpha)//渐变
.after(radiusAnimator)//半径
.before(colorAnimator);//变色
测试源码:
public class AnimatorSetView extends View {
private static final String TAG = "AnimatorView";
private Paint mPaint;
private int mRadius = 50;
private int mColor = 50;
private ObjectAnimator colorAnimator;
private ObjectAnimator radiusAnimator;
ObjectAnimator translationX;
ObjectAnimator alpha;
private AnimatorSet mSet;
public AnimatorSetView(Context context) {
this(context, null);
}
public AnimatorSetView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xff94E1F7);
mSet = new AnimatorSet();
translationX = ObjectAnimator//创建实例
//(View,属性名,初始化值,结束值)
.ofFloat(this, "translationX", 0, 300, 150, 100, 20, 100)
.setDuration(3000);//设置时常
alpha = ObjectAnimator//创建实例
//(View,属性名,初始化值,结束值)
.ofFloat(this, "alpha", 1, 0.5f, 1, 0, 1)
.setDuration(3000);//设置时常
radiusAnimator = ObjectAnimator//创建实例
//(View,属性名,初始化值,结束值)
.ofInt(this, "Radius", 50, 100, 50, 100, 20, 100)
.setDuration(3000);//设置时常
colorAnimator = ObjectAnimator//创建实例
//(View,属性名,初始化值,结束值)
.ofInt(this, "color", 0xff0000ff, 0xffF2BA38, 0xffDD70BC)
.setDuration(3000);
colorAnimator.setEvaluator(new ArgbEvaluator());//颜色的估值器
mSet//半径-->移动+渐变-->变色
.play(translationX)
.with(alpha)
.after(radiusAnimator)
.before(colorAnimator);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mSet.start();
break;
case MotionEvent.ACTION_UP:
}
return super.onTouchEvent(event);
}
public void setRadius(int radius) {
mRadius = radius;
setMeasuredDimension(mRadius * 2, mRadius * 2);
invalidate();//记得重绘
}
public void setColor(int color) {
mColor = color;
mPaint.setColor(mColor);
invalidate();//记得重绘
}
}
顾名思义:也就是一起运动还是分批运动
mSet.playTogether(translationX,alpha,radiusAnimator,colorAnimator);
mSet.playSequentially(translationX,alpha,radiusAnimator,colorAnimator);
可见Animator有两个内部接口,
AnimatorListener
和AnimatorPauseListener
。AnimatorListenerAdapter
是两个接口的空实现类,标准适配器模式。
ValueAnimator作为孩子,有自己的一个接口AnimatorUpdateListener
AnimatorListener
:动画监听Animator中的监听器两个孩子也都能用
//动画开启时回调
void onAnimationStart(Animator animation);
//动画结束时回调
void onAnimationEnd(Animator animation);
//动画取消时回调
void onAnimationCancel(Animator animation);
//重复时回调
void onAnimationRepeat(Animator animation);
开始时设为绿色-->重复时设为随机色-->取消是大小变为50-->结束时设为蓝色
mTranslationX = translationX();
mTranslationX.setRepeatMode(ValueAnimator.REVERSE);
mTranslationX.setRepeatCount(ValueAnimator.INFINITE);
mTranslationX.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//开始时设为绿色
setColor(Color.GREEN);
}
@Override
public void onAnimationEnd(Animator animation) {
//结束时设为蓝色
setColor(Color.BLUE);
}
@Override
public void onAnimationCancel(Animator animation) {
//取消时大小变为50
setCircleR(50);
}
@Override
public void onAnimationRepeat(Animator animation) {
//重复时设为随机色
setColor(ColUtils.randomColor());
}
});
mTranslationX.start();
mTranslationX.cancel();//取消动画
AnimatorPauseListener
:动画暂停监听//暂停回调
void onAnimationPause(Animator animation);
//恢复回调
void onAnimationResume(Animator animation);
效果如下:点击运动,右滑暂停颜色变黄,下滑恢复颜色变蓝
mTranslationX.addPauseListener(new Animator.AnimatorPauseListener() {
@Override
public void onAnimationPause(Animator animation) {
setColor(Color.YELLOW);//暂停黄色
}
@Override
public void onAnimationResume(Animator animation) {
setColor(Color.BLUE);//恢复蓝色
}
});
AnimatorUpdateListener
: ValueAnimator一系专有监听//更新时回调
void onAnimationUpdate(ValueAnimator animation);
效果如下:每当更新是将半径和位移联动
mTranslationX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCircleR = (Float) animation.getAnimatedValue();
invalidate();
}
});
在res下创建:
animator文件夹
直接用animator标签感觉也有点麻烦,这里看一下吧
xml中属性 | 含义 | 代码中对应 |
---|---|---|
duration | 播放的时长 | setDuration() |
valueType | 参数值类型 | ofXXX |
valueFrom | 初始值 | ofXXX(第1参) |
valueTo | 结束值 | ofXXX(第2参) |
startOffset | 延时 | startDelay() |
repeatCount | 重复次数 | setRepeatCount() |
interpolator | 插值器 | setRepeatMode() |
MainActivity
由Xml获取ValueAnimator,之后的事,就自己动手,感觉有点麻烦
View button = findViewById(R.id.id_btn_go);
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.animator);
animator.addUpdateListener(anim->{
float animatedValue = (float) anim.getAnimatedValue();
button.setTranslationX(animatedValue);
});
button.setOnClickListener((v)->{
animator.start();
});
set
与objectAnimator
标签objectAnimator多了一个propertyName属性,其余一致
set_obj_animator.xml
View button = findViewById(R.id.id_btn_go);
Animator set_obj = AnimatorInflater.loadAnimator(this, R.animator.set_obj_animator);
et_obj.setTarget(button);
button.setOnClickListener((v)->{
set_obj.start();
});
objectAnimator
变换路径详情可见:Android资源res之矢量图完全指南(加SVG-path命令分析)
箭头:M8,50, l100,0 M0,47, l40,40 M0,52 l40 -40
菜单:M0,50, l80,0 M0,80, l80,0 M0,20 l80 0
path变形 | 变形+旋转 |
---|---|
|
|
直接写也可以,但复用不方便
test
M8,50, l100,0 M0,47, l40,40 M0,52 l40 -40
M0,50, l80,0 M0,80, l80,0 M0,20 l80 0
icon_path.xml
//点击时:
Drawable drawable = mIdIv.getDrawable();
if (drawable instanceof Animatable){
((Animatable) drawable).start();
}
ok,这样就行了,你可以随意定制两个路径,但必须保证两个路径的指令相同,不然会崩
项目源码 | 日期 | 备注 |
---|---|---|
V0.1--github | 2018-12-27 | Android动画Animator家族使用指南 |
笔名 | 微信 | 爱好 | |
---|---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 | 语言 |
我的github | 我的简书 | 我的掘金 | 个人网站 |
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持