网站首页 > 技术文章 正文
零、前言: 本文知识点
- ValueAnimator的认识与使用
- 估值器TypeEvaluator的自定义与使用
- 插值器TimeInterpolator的自定义与使用
- Path与Animator的结合使用
- ObjectAnimator的自定义与使用
- TimeAnimator的使用
- AnimatorSet动画集合的使用
- Animator家族的监听器介绍与使用
- Animator家族在xml中的使用
一直用动画,貌似还没有好好地总结一下,趁有空,总结一波
所谓动画,就是不停变化,在视觉上达到连续的效果
Animator的体系并不复杂,但内部实现挺复杂的,很多类常年埋没于底层,不见天日
如: PropertyValuesHolder及其子类、 Keyframes族、 Keyframe族、 KeyframeSet族
今天试着读了一下源码,基本上读的懵懵懂懂,总的思路算是把握了
第一节:ValueAnimator的使用
一、简单的使用
0.Animator家族简单认识:
Animator是一个抽象类,不可用,只能找它的子类
现在先看非常常用的ValueAnimator
1.下面是一段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---
2.从中衍生的想法
1).不断调用onAnimationUpdate回调
2).可以获取有规律变化的不同的数值
在自定义View中onAnimationUpdate刷新界面,并动态改变数值
/** * 作者:张风捷特烈<br/> * 时间:2018/12/26 0026:7:50<br/> * 邮箱:1981462002@qq.com<br/> * 说明: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); } }
其实道理很简单,就是把打印输出换成了刷新视图,而且半径在不断变化
3.常规配置
看一下RESTART(默认)和REVERSE的区别
RESTARTREVERSE
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
颜色变化颜色大小
1.改变颜色: 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(); } });
2.如何即改变大小又改变颜色:
ValueAnimator.ofObject + TypeEvaluator
2.1先定义一个类承载数据:Ball(为了演示简洁,使用public属性)
public class Ball { public int color; public int r; public Ball() { } public Ball(int r, int color) { this.color = color; this.r = r; } }
2.2.创建TypeEvaluator(类型估值器)
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) { //计算颜色方法详情...... }
3.使用估值器指定曲线方程运动
该方程是二次曲线: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告诉你给怎么跑,那么插值器则告诉你跑多快
下面演示一下三个内置插值器(内置还有几个,自己试试)和自定义的三个插值器
1.自定义插值器:sin型先快后慢
这里的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)); } }
2.自定义插值器:sin型先满后快
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- } }
3.自定义插值器:log型
/** * 作者:张风捷特烈<br/> * 时间:2018/12/26 0026:20:41<br/> * 邮箱:1981462002@qq.com<br/> * 说明: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,供代码中使用(DSinInter如下)
LinearInterpolator线性插值器也就是x=y,而已,本质是一样的
4.优雅的实现测试代码
只需在名字数组和插值器数组里对应添加即可,其他会自动处理
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); } }
[插曲]:路径于Animator的结合
核心是使用PathMeasure和DashPathEffect对路径的长度进行控制
关于Path的这方面知识,这里不做详解,详见:Android关于Path你所知道的和不知道的一切
/** * 作者:张风捷特烈<br/> * 时间:2018/12/26 0026:7:50<br/> * 邮箱:1981462002@qq.com<br/> * 说明: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); } }
第二节:ValueAnimator之子 ObjectAnimator和TimeAnimator:
作为孩子,它老爸能做的它也能做,并且还会有一些自己的特长
ObjectAnimator针对有setXxx方法的属性,进行的"Xxx"属性变化动画
注: Xxx的首字母大小写都可以
一、View内置属性的测试
1.简单入门--下移示例:
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进行了变化。
2.常用属性一览:
属性名演示解释alpha
透明度1~0translationX
X方向移动translationY
Y方向移动rotation
旋转(默认View中心点)rotationX
X轴旋转(默认View中心横轴)rotationY
Y轴旋转(默认View中心纵轴)scaleX
X缩放 倍数scaleY
Y缩放 倍数
3.旋转、缩放中心点设置:
setPivotX(200); setPivotY(200);
4.多参数情况( 多参情况Animator家族皆适用)
0-->360 360-->0 0-->90
.ofFloat(this, "rotation", 0, 360,360,0,0,90)
二、自定义ObjectAnimator属性
内置的只是一些常用的,我们也可以自定义自己的属性
1.自定义圆的大小动画
必须用一个setXxx的方法,属性名则为xxx,调用重绘方法
public void setRadius(int radius) { mRadius = radius; invalidate();//记得重绘 } ObjectAnimator//创建实例 //(View,属性名,初始化值,结束值) .ofInt(this, "Radius", 100, 50,100,20,100) .setDuration(3000);//设置时常
2.自定义颜色动画
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());//颜色的估值器
3.ValueAnimator和ObjectAnimator的区别在哪?
1.ValueAnimator需要手动添加监听,手动获取ValueAnimator的数据,手动书写变更逻辑 2.ObjectAnimator可以不用进行更新监听,核心在`setXxx`里进行, 也就是每次更新时会自己走setXxx里的方法,这样方便在外部使用来动态改变属性 3.ValueAnimator的灵活性要好,毕竟自己动手,可以脑洞大开,想怎么玩怎么玩 4.ObjectAnimator针对有setXxx的属性进行动画,两者的侧重点不同 5.总的来说ObjectAnimator向于应用(简洁,快速),ValueAnimator偏向于操作(灵活,多变)
三、TimeAnimator
这个类总共代码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
综合前几次的动画效果,拼装在一起,AnimatorSet本身并不难
1.Builder模式的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();//记得重绘 } }
2.AnimatorSet自身方法:
顾名思义:也就是一起运动还是分批运动
mSet.playTogether(translationX,alpha,radiusAnimator,colorAnimator); mSet.playSequentially(translationX,alpha,radiusAnimator,colorAnimator);
四、Animator的监听:
可见Animator有两个内部接口, AnimatorListener和 AnimatorPauseListener。AnimatorListenerAdapter是两个接口的空实现类,标准适配器模式。
ValueAnimator作为孩子,有自己的一个接口 AnimatorUpdateListener
1、 AnimatorListener:动画监听
Animator中的监听器两个孩子也都能用
//动画开启时回调 void onAnimationStart(Animator animation); //动画结束时回调 void onAnimationEnd(Animator animation); //动画取消时回调 void onAnimationCancel(Animator animation); //重复时回调 void onAnimationRepeat(Animator animation);
2.动画测试
开始时设为绿色-->重复时设为随机色-->取消是大小变为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();//取消动画
3、 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);//恢复蓝色 } });
4、 AnimatorUpdateListener: ValueAnimator一系专有监听
//更新时回调 void onAnimationUpdate(ValueAnimator animation);
效果如下:每当更新是将半径和位移联动
mTranslationX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCircleR = (Float) animation.getAnimatedValue(); invalidate(); } });
五、Animator家族在xml中的使用:
在res下创建: animator文件夹
1.Animator标签
直接用animator标签感觉也有点麻烦,这里看一下吧
xml中属性含义代码中对应duration播放的时长setDuration()valueType参数值类型ofXXXvalueFrom初始值ofXXX(第1参)valueTo结束值ofXXX(第2参)startOffset延时startDelay()repeatCount重复次数setRepeatCount()interpolator插值器setRepeatMode()
1.1.animator.xml
<?xml version="1.0" encoding="utf-8"?> <animator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000" android:repeatCount="2" android:repeatMode="reverse" android:startOffset="1000" android:valueFrom="0dp" android:valueType="floatType" android:valueTo="200dp"> </animator>
1.2.布局
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <View android:id="@+id/id_btn_go" android:layout_width="50dp" android:layout_height="50dp" android:layout_marginStart="24dp" android:layout_marginTop="32dp" android:background="#3ED7FA" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> </android.support.constraint.ConstraintLayout>
1.3.代码中使用: 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(); });
2. set与 objectAnimator标签
objectAnimator多了一个propertyName属性,其余一致
2.1 set_obj_animator.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="sequentially"> <objectAnimator android:duration="1500" android:propertyName="rotationY" android:valueFrom="0" android:valueTo="180"/> <objectAnimator android:duration="1500" android:propertyName="alpha" android:valueFrom="0.3f" android:valueTo="1f"/> <objectAnimator android:duration="1500" android:propertyName="translationX" android:valueFrom="0" android:valueTo="180dp"/> </set>
2.2:代码中使用
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(); });
3、最后看一下我大 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变形变形+旋转
1.将两个path字符串放入string.xml
直接写也可以,但复用不方便
<resources> <string name="app_name">test</string> <string name="path_from">M8,50, l100,0 M0,47, l40,40 M0,52 l40 -40 </string> <string name="path_to">M0,50, l80,0 M0,80, l80,0 M0,20 l80 0</string> </resources>
2.矢量图:path_test.xml
<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="48dp" android:height="48dp" android:viewportWidth="100" android:viewportHeight="100"> <group android:translateX="4" android:translateY="4"> <path android:pathData="M0,0 A30,50,90,0,1,50,50" android:strokeWidth="4" android:strokeColor="@color/black"/> </group> </vector>
3.旋转动画:rotation_animator.xml
<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:propertyName="rotation" android:valueFrom="0" android:valueTo="180"/>
4.路径动画:path_animator.xml
<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:interpolator="@android:interpolator/linear" android:propertyName="pathData" android:valueFrom="@string/path_from" android:valueTo="@string/path_to" android:valueType="pathType"/>
5.矢量图文件: icon_path.xml
<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="48dp" android:height="48dp" android:viewportWidth="100" android:viewportHeight="100"> <group android:name="container" android:translateX="8" android:pivotX="50" android:scaleY="0.8" android:scaleX="0.8" android:pivotY="50"> <path android:name="alpha_anim" android:pathData="@string/path_from" android:strokeWidth="8" android:strokeColor="#000"/> </group> </vector>
6.整合动画:anima_path.xml
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/icon_path"> <target android:name="alpha_anim" android:animation="@animator/path_animator"/> <target android:name="container" android:animation="@animator/rotation_animator"> </target> </animated-vector>
7.使用动画:
<ImageView android:id="@+id/id_iv" android:layout_width="200dp" android:layout_height="200dp" android:src="@drawable/anima_path" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> //点击时: Drawable drawable = mIdIv.getDrawable(); if (drawable instanceof Animatable){ ((Animatable) drawable).start(); }
ok,这样就行了,你可以随意定制两个路径,但必须保证两个路径的指令相同,不然会崩
后记:捷文规范
1.本文成长记录及勘误表
项目源码日期备注V0.1--github2018-12-27Android动画Animator家族使用指南
2.更多关于我
笔名QQ微信爱好张风捷特烈1981462002zdl1994328语言我的github我的简书我的掘金个人网站
3.声明
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持
猜你喜欢
- 2024-10-24 初探animation中steps()属性(animation steps属性)
- 2024-10-24 HTML5(九)——超强的 SVG 动画(htmlsvg动画代码)
- 2024-10-24 自定义日历(二)(自定义日历控件)
- 2024-10-24 Flutter简单动画Animation运用(flutter 视频教程)
- 2024-10-24 css3中动画animation中的steps()函数
- 2024-10-24 移动端渲染原理浅析(移动端渲染原理浅析设计)
- 2024-10-24 iOS 事件处理机制与图像渲染过程(简述ios中的事件响应机制)
- 2024-10-24 Android 开机问题分析(android无法开机)
- 2024-10-24 GoogleCTF + zer0ptsCTF + ImaginaryCTF 2023 笔记
- 2024-10-24 决战“金三银四”,中高级Web前端大厂面试秘籍:CSS篇
- 11-26Win7\8\10下一条cmd命令可查得笔记本电脑连接过的Wifi密码
- 11-26一文搞懂MySQL行锁、表锁、间隙锁详解
- 11-26电脑的wifi密码忘记了?一招教你如何找回密码,简单明了,快收藏
- 11-26代码解决忘记密码问题 教你用CMD命令查看所有连接过的WIFI密码
- 11-26CMD命令提示符能干嘛?这些功能你都知道吗?
- 11-26性能测试之慢sql分析
- 11-26论渗透信息收集的重要性
- 11-26如何查看电脑连接过的所有WiFi密码
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)