屬性動畫簡單介紹
作用對象:任意 Java 對象
不再侷限於 視圖View對象
實現的動畫效果:可自定義各種動畫效果
不再侷限於4種基本變換:平移、旋轉、縮放 & 透明度
特點
作用對象進行了擴展:不只是View對象,甚至沒對象也可以
動畫效果:不只是4種基本變換,還有其他動畫效果
作用領域:API11後引入的
工作原理:
在一定時間間隔內,通過不斷對值進行改變,並不斷將該值賦給對象的屬性,從而實現該對象在該屬性上的動畫效果
屬性動畫有兩個非常重要的類:
ValueAnimator 類 & ObjectAnimator 類
ObjectAnimator類
具體使用
設置方式1:Java 設置
ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);
// ofFloat()作用有兩個
// 1. 創建動畫實例
// 2. 參數設置:參數說明如下
// Object object:需要操作的對象
// String property:需要操作的對象的屬性
// float ....values:動畫初始值 & 結束值(不固定長度)
// 若是兩個參數a,b,則動畫效果則是從屬性的a值到b值
// 若是三個參數a,b,c,則則動畫效果則是從屬性的a值到b值再到c值
// 以此類推
// 至於如何從初始值 過渡到 結束值,同樣是由估值器決定,此處ObjectAnimator.ofFloat()是有系統內置的浮點型估值器FloatEvaluator,同ValueAnimator講解
anim.setDuration(500); // 設置動畫運行的時長
anim.setStartDelay(500);// 設置動畫延遲播放時間
anim.setRepeatCount(0);
// 設置動畫重複播放次數 = 重放次數+1
// 動畫播放次數 = infinite時,動畫無限重複
anim.setRepeatMode(ValueAnimator.RESTART);
// 設置重複播放動畫模式
// ValueAnimator.RESTART(默認):正序重放
// ValueAnimator.REVERSE:倒序回放
animator.start();
// 啓動動畫
設置方法2:在XML 代碼中設置
步驟1:在路徑 res/animator 的文件夾裏創建動畫效果.xml文件
此處設置爲res/animator/set_animation.xml
步驟2:設置動畫參數
set_animation.xml
// ObjectAnimator 採用<animator> 標籤
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1" // 初始值
android:valueTo="0" // 結束值
android:valueType="floatType" // 變化值類型 :floatType & intType
android:propertyName="alpha" // 對象變化的屬性名稱
/>
在Java代碼中啓動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.view_animation);
// 載入XML動畫
animator.setTarget(view); // 設置動畫對象
animator.start(); // 啓動動畫
四種基本Demo
四種基本變換:透明度,旋轉,平移,縮放
(1)透明度:
佈局文件activity_main.xml:
<?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">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World 動畫!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
實現代碼MainActivity.java:
import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onDoAnimator();
}
});
}
private void onDoAnimator() {
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "alpha", 1f, 0f, 1f);
// 動畫作用對象是mButton
// 動畫作用的對象的屬性是透明度alpha
// 動畫效果是:常規 - 全透明 - 常規
animator.setDuration(5000);
animator.start();
}
}
動畫效果:
(2)旋轉:
private void onDoAnimator() {
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);
// 動畫作用對象是mButton
// 動畫作用的對象的屬性是旋轉alpha
// 動畫效果是:0 - 360
animator.setDuration(5000);
animator.start();
}
動畫效果:
(3)平移
private void onDoAnimator() {
float curTranslationX = mButton.getTranslationX();
// 獲得當前按鈕的位置
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX,-300,curTranslationX);
// 動畫作用對象是mButton
// 動畫作用的對象的屬性是X軸平移(在Y軸上平移同理,採用屬性"translationY"
// 動畫效果是:從當前位置平移到 x=300 再平移到初始位置
animator.setDuration(5000);
animator.start();
}
動畫效果:
(4)縮放
private void onDoAnimator() {
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "scaleX", 1f, 1.5f, 1f);
// 表示的是:
// 動畫作用對象是mButton
// 動畫作用的對象的屬性是X軸縮放
// 動畫效果是:放大到3倍,再縮小到初始大小
animator.setDuration(5000);
animator.start();
}
動畫效果:
在ObjectAnimator.ofFloat()的第二個參數String property傳入alpha、rotation、translationX 和 scaleY 等:
屬性 | 作用 | 數值類型 |
---|---|---|
Alpha | 控制View的透明度 | float |
TranslationX | 控制X方向的位移 | float |
TranslationY | 控制Y方向的位移 | float |
ScaleX | 控制X方向的縮放倍數 | float |
ScaleY | 控制Y方向的縮放倍數 | float |
Rotation | 控制以屏幕方向爲軸的旋轉度數 | float |
RotationX | 控制以X軸爲軸的旋轉度數 | float |
RotationY | 控制以Y軸爲軸的旋轉度數 | float |
問題:那麼ofFloat()的第二個參數還能傳入什麼屬性值呢?
答案:任意屬性值。
因爲:
- ObjectAnimator 類 對 對象屬性值 進行改變從而實現動畫效果的本質是:通過不斷控制 值 的變化,再不斷 自動
賦給對象的屬性,從而實現動畫效果 - 而自動賦給對象的屬性的本質是調用該對象屬性的set() & get()方法進行賦值
- 所以,ObjectAnimator.ofFloat(Object object, String property, float
…values)的第二個參數傳入值的作用是:讓ObjectAnimator類根據傳入的屬性名 去尋找 該對象對應屬性名的 set()& get()方法,從而進行對象屬性值的賦值
通過自定義對象屬性實現動畫效果
對於屬性動畫,其拓展性在於:不侷限於系統限定的動畫,可以自定義動畫,即自定義對象的屬性,並通過操作自定義的屬性從而實現動畫。
那麼,該如何自定義屬性呢?本質上,就是:
爲對象設置需要操作屬性的set() & get()方法
通過實現TypeEvaluator類從而定義屬性變化的邏輯
實現的動畫效果:一個圓的顏色漸變
(1)定義MyView2 :
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class MyView2 extends View {
// 設置需要用到的變量
public static final float RADIUS = 100f;// 圓的半徑 = 100
private Paint mPaint;// 繪圖畫筆
private String color;// 設置背景顏色屬性
// 設置背景顏色的get() & set()方法
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
mPaint.setColor(Color.parseColor(color));
// 將畫筆的顏色設置成方法參數傳入的顏色
invalidate();
// 調用了invalidate()方法,即畫筆顏色每次改變都會刷新視圖,然後調用onDraw()方法重新繪製圓
// 而因爲每次調用onDraw()方法時畫筆的顏色都會改變,所以圓的顏色也會改變
}
// 構造方法(初始化畫筆)
public MyView2(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
// 繪製邏輯:先在初始點畫圓,通過監聽當前座標值(currentPoint)的變化,每次變化都調用onDraw()重新繪製圓,從而實現圓的平移動畫效果
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(500, 500, RADIUS, mPaint);
}
}
(2)在佈局文件activity_main.xml中引用MyView2:
<?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">
<com.readygo.valueanimatordemo.MyView2
android:id="@+id/myView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
/>
</android.support.constraint.ConstraintLayout>
(3)實現TypeEvaluator需求接口,實現 顏色過渡的邏輯
import android.animation.TypeEvaluator;
public class ColorEvaluator implements TypeEvaluator {
// 實現TypeEvaluator接口
private int mCurrentRed;
private int mCurrentGreen ;
private int mCurrentBlue ;
// 在evaluate()裏寫入對象動畫過渡的邏輯:此處是寫顏色過渡的邏輯
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 獲取到顏色的初始值和結束值
String startColor = (String) startValue;
String endColor = (String) endValue;
// 通過字符串截取的方式將初始化顏色分爲RGB三個部分,並將RGB的值轉換成十進制數字
// 那麼每個顏色的取值範圍就是0-255
int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);
int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);
// 將初始化顏色的值定義爲當前需要操作的顏色值
mCurrentRed = startRed;
mCurrentGreen = startGreen;
mCurrentBlue = startBlue;
// 計算初始顏色和結束顏色之間的差值
// 該差值決定着顏色變化的快慢:初始顏色值和結束顏色值很相近,那麼顏色變化就會比較緩慢;否則,變化則很快
// 具體如何根據差值來決定顏色變化快慢的邏輯寫在getCurrentColor()裏.
int redDiff = Math.abs(startRed - endRed);
int greenDiff = Math.abs(startGreen - endGreen);
int blueDiff = Math.abs(startBlue - endBlue);
int colorDiff = redDiff + greenDiff + blueDiff;
if (mCurrentRed != endRed) {
mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0, fraction);
// getCurrentColor()決定如何根據差值來決定顏色變化的快慢 ->>關注1
} else if (mCurrentGreen != endGreen) {
mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,redDiff, fraction);
} else if (mCurrentBlue != endBlue) {
mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,redDiff + greenDiff, fraction);
}
// 將計算出的當前顏色的值組裝返回
String currentColor = "#" + getHexString(mCurrentRed) + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
// 由於我們計算出的顏色是十進制數字,所以需要轉換成十六進制字符串:調用getHexString()->>關注2
// 最終將RGB顏色拼裝起來,並作爲最終的結果返回
return currentColor;
}
// 關注1:getCurrentColor()
// 具體是根據fraction值來計算當前的顏色。
private int getCurrentColor(int startColor, int endColor, int colorDiff,
int offset, float fraction) {
int currentColor;
if (startColor > endColor) {
currentColor = (int) (startColor - (fraction * colorDiff - offset));
if (currentColor < endColor) {
currentColor = endColor;
}
} else {
currentColor = (int) (startColor + (fraction * colorDiff - offset));
if (currentColor > endColor) {
currentColor = endColor;
}
}
return currentColor;
}
// 關注2:將10進制顏色值轉換成16進制。
private String getHexString(int value) {
String hexString = Integer.toHexString(value);
if (hexString.length() == 1) {
hexString = "0" + hexString;
}
return hexString;
}
}
(4)調用ObjectAnimator.ofObject()方法
import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
private MyView2 myView2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
myView2 = (MyView2) findViewById(R.id.myView2);
ObjectAnimator anim = ObjectAnimator.ofObject(myView2, "color", new ColorEvaluator(),
"#0000FF", "#FF0000");
// 設置自定義View對象、背景顏色屬性值 & 顏色估值器
// 本質邏輯:
// 步驟1:根據顏色估值器不斷 改變 值
// 步驟2:調用set()設置背景顏色的屬性值(實際上是通過畫筆進行顏色設置)
// 步驟3:調用invalidate()刷新視圖,即調用onDraw()重新繪製,從而實現動畫效果
anim.setDuration(8000);
anim.start();
}
}
ValueAnimator類
具體使用
屬性動畫機制中最核心的一個類,實現動畫的原理:通過不斷控制值的變化,再不斷手動賦給對象的屬性,從而實現動畫效果.
ValueAnimator類中有3個重要方法:
ValueAnimator.ofInt(int values)
ValueAnimator.ofFloat(float values)
ValueAnimator.ofObject(int values)
ValueAnimator.ofInt(int values)
具體使用
作用:將初始值 以整型數值的形式 過渡到結束值
即估值器是整型估值器 - IntEvaluator
設置方式1:Java代碼設置
// 步驟1:設置動畫屬性的初始值 & 結束值
ValueAnimator anim = ValueAnimator.ofInt(0, 3);
// ofInt()作用有兩個
// 1. 創建動畫實例
// 2. 將傳入的多個Int參數進行平滑過渡:此處傳入0和1,表示將值從0平滑過渡到1
// 如果傳入了3個Int參數 a,b,c ,則是先從a平滑過渡到b,再從b平滑過渡到C,以此類推
// ValueAnimator.ofInt()內置了整型估值器,直接採用默認的.不需要設置,即默認設置瞭如何從初始值 過渡到 結束值
// 關於自定義插值器我將在下節進行講解
// 下面看看ofInt()的源碼分析 ->>關注1
// 步驟2:設置動畫的播放各種屬性
anim.setDuration(500);
// 設置動畫運行的時長
anim.setStartDelay(500);
// 設置動畫延遲播放時間
anim.setRepeatCount(0);
// 設置動畫重複播放次數 = 重放次數+1
// 動畫播放次數 = infinite時,動畫無限重複
anim.setRepeatMode(ValueAnimator.RESTART);
// 設置重複播放動畫模式
// ValueAnimator.RESTART(默認):正序重放
// ValueAnimator.REVERSE:倒序回放
// 步驟3:將改變的值手動賦值給對象的屬性值:通過動畫的更新監聽器
// 設置 值的更新監聽器
// 即:值每次改變、變化一次,該方法就會被調用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
// 獲得改變後的值
System.out.println(currentValue);
// 輸出改變後的值
// 步驟4:將改變後的值賦給對象的屬性值,下面會詳細說明
View.setproperty(currentValue);
// 步驟5:刷新視圖,即重新繪製,從而實現動畫效果
View.requestLayout();
}
});
anim.start();
// 啓動動畫
}
// 關注1:ofInt()源碼分析
public static ValueAnimator ofInt(int... values) {
// 允許傳入一個或多個Int參數
// 1. 輸入一個的情況(如a):從0過渡到a;
// 2. 輸入多個的情況(如a,b,c):先從a平滑過渡到b,再從b平滑過渡到C
ValueAnimator anim = new ValueAnimator();
// 創建動畫對象
anim.setIntValues(values);
// 將傳入的值賦值給動畫對象
return anim;
}
設置方法2:在XML 代碼中設置
具備重用性,即將通用的動畫寫到XML裏,可在各個界面中去重用它
- 步驟1:在路徑 res/animator的文件夾裏創建相應的動畫 .xml文件
此處設置爲res/animator/set_animation.xml - 步驟2:設置動畫參數
set_animation.xml
// ValueAnimator採用<animator> 標籤
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0" // 初始值
android:valueTo="100" // 結束值
android:valueType="intType" // 變化值類型 :floatType & intType
android:duration="3000" // 動畫持續時間(ms),必須設置,動畫纔有效果
android:startOffset ="1000" // 動畫延遲開始時間(ms)
android:fillBefore = “true” // 動畫播放完後,視圖是否會停留在動畫開始的狀態,默認爲true
android:fillAfter = “false” // 動畫播放完後,視圖是否會停留在動畫結束的狀態,優先於fillBefore值,默認爲false
android:fillEnabled= “true” // 是否應用fillBefore值,對fillAfter值無影響,默認爲true
android:repeatMode= “restart” // 選擇重複播放動畫模式,restart代表正序重放,reverse代表倒序回放,默認爲restart|
android:repeatCount = “0” // 重放次數(所以動畫的播放次數=重放次數+1),爲infinite時無限重複
android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影響動畫的播放速度,下面會詳細講
/>
- 步驟3:在Java代碼中啓動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
// 載入XML動畫
animator.setTarget(view);
// 設置動畫對象
animator.start();
// 啓動動畫
實現的動畫效果:按鈕的寬度從 150px 放大到 500px
我將結合 手動賦值給對象屬性 這一步驟,從而實現一個完整的動畫效果。
public class MainActivity extends AppCompatActivity {
private Button mButton;
private ValueAnimator valueAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init() {
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onDoAnimator();
}
});
// 步驟1:設置屬性數值的初始值 & 結束值
System.out.println(mButton.getLayoutParams().width);
valueAnimator = ValueAnimator.ofInt(150, 500);
// 初始值 150
// 結束值 = 500
// ValueAnimator.ofInt()內置了整型估值器,直接採用默認的.不需要設置
// 即默認設置瞭如何從初始值150 過渡到 結束值500
// 步驟2:設置動畫的播放各種屬性
valueAnimator.setDuration(2000);
// 設置動畫運行時長:1s
// 步驟3:將屬性數值手動賦值給對象的屬性:此處是將 值 賦給 按鈕的寬度
// 設置更新監聽器:即數值每次變化更新都會調用該方法
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
int currentValue = (Integer) animator.getAnimatedValue();
// 獲得每次變化後的屬性值
System.out.println(currentValue);
// 輸出每次變化後的屬性值進行查看
mButton.getLayoutParams().width = currentValue;
// 每次值變化時,將值手動賦值給對象的屬性
// 即將每次變化後的值 賦 給按鈕的寬度,這樣就實現了按鈕寬度屬性的動態變化
// 步驟4:刷新視圖,即重新繪製,從而實現動畫效果
mButton.requestLayout();
}
});
}
private void onDoAnimator() {
// 啓動動畫
valueAnimator.start();
}
}
動畫效果圖:
ValueAnimator.oFloat(float values)
作用:將初始值 以浮點型數值的形式 過渡到結束值
具體使用:
分爲 XML 設置 / Java 代碼設置
設置方法1:在 Java 代碼中設置
ValueAnimator anim = ValueAnimator.ofFloat(0, 3);
// 其他使用類似ValueAnimator.ofInt(int values),此處不作過多描述
設置方法2:在XML 代碼中設置
步驟1:在路徑 res/animator的文件夾裏創建相應的動畫.xml文件
此處設置爲res/animator/set_animation.xml
步驟2:設置動畫參數
set_animation.xml
// ValueAnimator採用<animator> 標籤
<animator xmlns:android="http://schemas.android.com/apk/res/android"
// 設置屬性同上
android:valueFrom="0"
android:valueTo="100"
android:valueType="floatType"/>
步驟3:在Java代碼中啓動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
// 載入XML動畫
animator.setTarget(view);
// 設置動畫對象
animator.start();
// 啓動動畫
ValueAnimator.oFloat()採用默認的浮點型估值器 (FloatEvaluator)
ValueAnimator.ofInt()採用默認的整型估值器(IntEvaluator)
ValueAnimator.ofObject()
作用:將初始值 以對象的形式 過渡到結束值
即通過操作 對象 實現動畫效果
具體使用:
// 創建初始動畫時的對象 & 結束動畫時的對象
myObject object1 = new myObject();
myObject object2 = new myObject();
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), object1, object2);
// 創建動畫對象 & 設置參數
// 參數說明
// 參數1:自定義的估值器對象(TypeEvaluator 類型參數) - 下面會詳細介紹
// 參數2:初始動畫的對象
// 參數3:結束動畫的對象
anim.setDuration(5000);
anim.start();
先講一下估值器(TypeEvaluator)
估值器(TypeEvaluator) 介紹
作用:設置動畫 如何從初始值 過渡到 結束值 的邏輯
插值器(Interpolator)決定 值 的變化模式(勻速、加速等)
估值器(TypeEvaluator)決定 值 的具體變化數值
我們來看一下 FloatEvaluator的代碼實現:
public class FloatEvaluator implements TypeEvaluator {
// FloatEvaluator實現了TypeEvaluator接口
// 重寫evaluate()
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 參數說明
// fraction:表示動畫完成度(根據它來計算當前動畫的值)
// startValue、endValue:動畫的初始值和結束值
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
// 初始值 過渡 到結束值 的算法是:
// 1. 用結束值減去初始值,算出它們之間的差值
// 2. 用上述差值乘以fraction係數
// 3. 再加上初始值,就得到當前動畫的值
}
}
自定義實現的邏輯如下
// 實現TypeEvaluator接口
public class ObjectEvaluator implements TypeEvaluator{
// 複寫evaluate()
// 在evaluate()裏寫入對象動畫過渡的邏輯
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 參數說明
// fraction:表示動畫完成度(根據它來計算當前動畫的值)
// startValue、endValue:動畫的初始值和結束值
... // 寫入對象動畫過渡的邏輯
return value;
// 返回對象動畫過渡的邏輯計算後的值
}
實現的動畫效果:一個圓從一個點 移動到 另外一個點
步驟1:定義對象類
public class Point {
// 設置兩個變量用於記錄座標的位置
private float x;
private float y;
// 構造方法用於設置座標
public Point(float x, float y) {
this.x = x;
this.y = y;
}
// get方法用於獲取座標
public float getX() {
return x;
}
public float getY() {
return y;
}
}
步驟2:根據需求實現TypeEvaluator接口
實現TypeEvaluator接口的目的是自定義如何 從初始點座標 過渡 到結束點座標;
本例實現的是一個從左上角到右下角的座標過渡邏輯。
PointEvaluator.java
import android.animation.TypeEvaluator;
// 實現TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {
// 複寫evaluate()
// 在evaluate()裏寫入對象動畫過渡的邏輯
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 將動畫初始值startValue 和 動畫結束值endValue 強制類型轉換成Point對象
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
// 根據fraction來計算當前動畫的x和y的值
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
// 將計算後的座標封裝到一個新的Point對象中並返回
Point point = new Point(x, y);
return point;
}
}
步驟3:將屬性動畫作用到自定義View當中
MyView.java
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
public class MyView extends View {
// 設置需要用到的變量
public static final float RADIUS = 70f;// 圓的半徑 = 70
private Point currentPoint;// 當前點座標
private Paint mPaint;// 繪圖畫筆
// 構造方法(初始化畫筆)
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
// 複寫onDraw()從而實現繪製邏輯
// 繪製邏輯:先在初始點畫圓,通過監聽當前座標值(currentPoint)的變化,每次變化都調用onDraw()重新繪製圓,從而實現圓的平移動畫效果
@Override
protected void onDraw(Canvas canvas) {
// 如果當前點座標爲空(即第一次)
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
// 創建一個點對象(座標是(70,70))
// 在該點畫一個圓:圓心 = (70,70),半徑 = 70
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
// (重點關注)將屬性動畫作用到View中
// 步驟1:創建初始動畫時的對象點 & 結束動畫時的對象點
Point startPoint = new Point(RADIUS, RADIUS);// 初始點爲圓心(70,70)
Point endPoint = new Point(700, 1000);// 結束點爲(700,1000)
// 步驟2:創建動畫對象 & 設置初始值 和 結束值
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
// 參數說明
// 參數1:TypeEvaluator 類型參數 - 使用自定義的PointEvaluator(實現了TypeEvaluator接口)
// 參數2:初始動畫的對象點
// 參數3:結束動畫的對象點
// 步驟3:設置動畫參數
anim.setDuration(5000);
// 設置動畫時長
// 步驟3:通過 值 的更新監聽器,將改變的對象手動賦值給當前對象
// 此處是將 改變後的座標值對象 賦給 當前的座標值對象
// 設置 值的更新監聽器
// 即每當座標值(Point對象)更新一次,該方法就會被調用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
// 將每次變化後的座標值(估值器PointEvaluator中evaluate()返回的Piont對象值)到當前座標值對象(currentPoint)
// 從而更新當前座標值(currentPoint)
// 步驟4:每次賦值後就重新繪製,從而實現動畫效果
invalidate();
// 調用invalidate()後,就會刷新View,即才能看到重新繪製的界面,即onDraw()會被重新調用一次
// 所以座標值每改變一次,就會調用onDraw()一次
}
});
anim.start();
} else {
// 如果座標值不爲0,則畫圓
// 所以座標值每改變一次,就會調用onDraw()一次,就會畫一次圓,從而實現動畫效果
// 在該點畫一個圓:圓心 = (30,30),半徑 = 30
float x = currentPoint.getX();
float y = currentPoint.getY();
canvas.drawCircle(x, y, RADIUS, mPaint);
}
}
}
步驟4:在佈局文件activity_main.xml 加入自定義View空間
<?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">
<com.readygo.valueanimatordemo.MyView
android:id="@+id/myView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
/>
</android.support.constraint.ConstraintLayout>
步驟5:在主代碼文件設置顯示視圖
MainActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
動畫效果:
ValueAnimator類 & ObjectAnimator 類 總結:
對比ValueAnimator類 & ObjectAnimator 類,其實二者都屬於屬性動畫,本質上都是一致的:先改變值,然後 賦值 給對象的屬性從而實現動畫效果。
ObjectAnimator與 ValueAnimator類的區別:
ValueAnimator 類是先改變值,然後 手動賦值 給對象的屬性從而實現動畫;是 間接 對對象屬性進行操作;
ObjectAnimator 類是先改變值,然後 自動賦值 給對象的屬性從而實現動畫;是 直接 對對象屬性進行操作;
ViewPropertyAnimator用法
具體使用
/ 使用解析
View.animate().xxx().xxx();
// ViewPropertyAnimator的功能建立在animate()上
// 調用animate()方法返回值是一個ViewPropertyAnimator對象,之後的調用的所有方法都是通過該實例完成
// 調用該實例的各種方法來實現動畫效果
// ViewPropertyAnimator所有接口方法都使用連綴語法來設計,每個方法的返回值都是它自身的實例
// 因此調用完一個方法後可直接連綴調用另一方法,即可通過一行代碼就完成所有動畫效果
// 以下是例子
mButton = (Button) findViewById(R.id.Button);
// 創建動畫作用對象:此處以Button爲例
mButton.animate().alpha(0f);
// 單個動畫設置:將按鈕變成透明狀態
mButton.animate().alpha(0f).setDuration(5000).setInterpolator(new BounceInterpolator());
// 單個動畫效果設置 & 參數設置
mButton.animate().alpha(0f).x(500).y(500);
// 組合動畫:將按鈕變成透明狀態再移動到(500,500)處
// 特別注意:
// 動畫自動啓動,無需調用start()方法.因爲新的接口中使用了隱式啓動動畫的功能,只要我們將動畫定義完成後,動畫就會自動啓動
// 該機制對於組合動畫也同樣有效,只要不斷地連綴新的方法,那麼動畫就不會立刻執行,等到所有在ViewPropertyAnimator上設置的方法都執行完畢後,動畫就會自動啓動
// 如果不想使用這一默認機制,也可以顯式地調用start()方法來啓動動畫
監聽動畫
Animation類通過監聽動畫開始 / 結束 / 重複 / 取消時刻來進行一系列操作,如跳轉頁面等等
通過在Java代碼裏addListener()設置
Animation.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animation animation) {
//動畫開始時執行
}
@Override
public void onAnimationRepeat(Animation animation) {
//動畫重複時執行
}
@Override
public void onAnimationCancel()(Animation animation) {
//動畫取消時執行
}
@Override
public void onAnimationEnd(Animation animation) {
//動畫結束時執行
}
});
// 特別注意:每次監聽必須4個方法都重寫。
動畫適配器AnimatorListenerAdapter
背景:有些時候我們並不需要監聽動畫的所有時刻
問題:但addListener(new AnimatorListener())監聽器是必須重寫4個時刻方法,這使得接口方法重寫太累贅
解決方案:採用動畫適配器(AnimatorListenerAdapter),解決實現接口繁瑣 的問題
anim.addListener(new AnimatorListenerAdapter() {
// 向addListener()方法中傳入適配器對象AnimatorListenerAdapter()
// 由於AnimatorListenerAdapter中已經實現好每個接口
// 所以這裏不實現全部方法也不會報錯
@Override
public void onAnimationStart(Animator animation) {
// 如想只想監聽動畫開始時刻,就只需要單獨重寫該方法就可以
}
});
參考資料
1.Android 屬性動畫:這是一篇很詳細的 屬性動畫 總結&攻略
https://www.jianshu.com/p/2412d00a0ce4
2.android開發筆記之animation(一)
https://blog.csdn.net/hfreeman2008/article/details/39138303
3.Android 屬性動畫:這是一篇很詳細的 屬性動畫 總結&攻略
https://www.jianshu.com/p/2412d00a0ce4