零、前言:本文知識點
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的區別
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
顏色變化 | 顏色大小 |
---|---|
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,供程式碼中使用(D_Sin_Inter如下)
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~0 | |
translationX | 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 | 引數值型別 | ofXXX |
valueFrom | 初始值 | 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.1set_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
變換路徑
箭頭: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--github | 2018-12-27 | Android動畫Animator家族使用指南 |
2.更多關於我
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
我的github | 我的簡書 | 我的掘金 | 個人網站 |
3.宣告
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援