前言
- 動畫時Android開發中使用頻率比較高的功能。
- 對Android提供的補間動畫,幀動畫以及屬性動畫做出歸納總結。
目錄
1. 幀動畫
幀動畫總體實現比較簡單,其實現本身是實現一個圖片集的連續播放,從而達到動畫的效果。
實現幀動畫就必須將大量圖片資源加入到APK當中,從而增加APK的大小,但是卻可以實現比較復雜的動畫效果。
幀動畫使用比較簡單的,具體操作過程如下
- 將圖片集導入到對應目錄下
- 在drawable文件夾下新建文件anim_chat. xml,的代碼實現如下
<?xml version="1.0" encoding="UTF-8"?>
<animation-list android:oneshot="false"
xmlns:android="http://schemas.android.com/apk/res/android">
//duraction字段可以用來設置該圖片播放時長,drawable用來設置要顯示的圖片
<item android:duration="230" android:drawable="@drawable/ic_chat_recording1" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording2" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording3" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording4" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording5" />
<item android:duration="230" android:drawable="@drawable/ic_chat_recording6" />
</animation-list>
- 在布局文件activity_main.xml當中添加組件
<Button
android:id="@+id/frame_animation_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="幀動畫測試" />
<ImageView
android:id="@+id/frame_animation_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_chat_recording1"
android:layout_marginTop="20dp"/>
- 在activity當中加入java代碼實現
public class MainActivity extends AppCompatActivity {
private Button frameButton;
private ImageView frameImage;
private AnimationDrawable frameAnimation;
boolean isStart = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFrame();
}
private void initFrame() {
//初始化控件
frameButton = (Button) findViewById(R.id.frame_animation_test);
frameImage = (ImageView) findViewById(R.id.frame_animation_img);
//給ImageView設置drawable
frameImage.setImageResource(R.drawable.anim_chat_recording);
//給動畫資源賦值
frameAnimation = (AnimationDrawable) frameImage.getDrawable();
//給按鈕添加點擊事件用來控制動畫
frameButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//當isStart=false時表示動畫沒有在播放,點擊按鈕開始播放
if (!isStart) {
frameAnimation.start();
isStart = true;
} else {//當isStart=true時表示動畫正在播放,點擊按鈕停止播放
frameAnimation.stop();
isStart = false;
}
}
});
}
}
運行代碼進行測試
幀動畫也存在使用純java代碼的實現方式,但是在應用當中并不多見,有興趣可以了解一下,這里不做介紹。
2. 補間動畫
與按幀播放的幀動畫不同,補間動畫只需要定義初始和結束時的狀態,中間的動畫過程將由系統自動補齊。
-
特點:
- 補間動畫作用于View,可以實現視覺上的動畫效果,但是并沒有真正對視圖做出改變。
- 使用簡單,可以使用非常簡單的方式實現動畫效果。
實現方式可以有jave代碼實現和XML代碼實現兩種。
分類:補間動畫可分為四類
jave | xml | 效果 |
---|---|---|
AlphaAnimation | alph | 漸變透明度動畫效果 |
ScaleAnimation | scale | 漸變尺寸伸縮動畫效果 |
TranslateAnimation1 | translate | 畫面轉換位置移動動畫效果 |
RotateAnimation | rotate | 畫面轉移旋轉動畫效果 |
后文將對四種補間動畫效果做具體說明。
2.1 alph動畫
特有屬性:
- android:fromAlpha:動畫開始時的透明度。
- android:toAlpha:動畫結束時的透明度。
Java代碼實現
直接上代碼
- 在activity_main.xml當中定義布局資源
<ImageView
android:id="@+id/alph_animation_img"
android:layout_width="150dp"
android:layout_height="200dp"
android:src="@drawable/animation_test1"/>
后續內容的動畫效果基本針對圖片涉及到的xml布局都基本類似,將不再進行說明。
- 在activity當中進行實現
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initAlphJava();
}
private void initAlphJava() {
alphImage = (ImageView) findViewById(R.id.alph_animation_img);
alphImage.setVisibility(View.VISIBLE);
//構造參數中,第一個參數為動畫開始時候透明度,第二個參數toAlpha為 動畫結束時候透明度
//對于fromAlpha和toAlpha,1.0代表完全不透明狀態,0.0代表完全透明狀態
AlphaAnimation mAlpha = new AlphaAnimation(1.0f,0.0f);
//設置動畫播放時間,2000MS=2S
mAlpha.setDuration(2000);
//設置動畫循環次數,-1為一直循環
mAlpha.setRepeatCount(-1);
//設置動畫循環方式Animation.REVERSE為倒敘播放,Animation.RESTART為重復播放
mAlpha.setRepeatMode(Animation.REVERSE);
//alphImage開始播放動畫
alphImage.startAnimation(mAlpha);
}
動畫效果為:由原圖顯示漸變為隱藏狀態。
XML實現
- 在res目錄中新建anim文件夾。
- 在anim目錄中新建一個alph_anim.xml文件(注意文件名小寫)。
- 在alph_anim.xml文件當中對動畫進行定義。
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!--透明度控制動畫效果 alpha-->
<!--fromAlpha 屬性為動畫起始時透明度 0.0表示完全透明 1.0表示完全不透明-->
<!--toAlpha 屬性為動畫結束時透明度-->
<!--duration 屬性為動畫持續時間-->
<alpha
android:duration="2000" //播放時間為2秒
android:fromAlpha="1.0" //初始透明度為完全顯示
android:toAlpha="0.0" //結束透明度為完全透明
android:repeatCount= "-1" /> //重復播放次數為無限循環
</set>
fromAlpha和toAlpha為alph動畫的特有屬性,1.0代表完全不透明狀態,0.0代表完全透明狀態
- 在activity當中對動畫資源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initAlphXml();
}
private void initAlphXml() {
alphImage = (ImageView) findViewById(R.id.alph_animation_img);
Animation animation = AnimationUtils.loadAnimation(this,R.anim.alph_anim);
alphImage.startAnimation(animation);
}
運行程序,動畫效果與上述java實現相同。
補間動畫中有一些公共屬性,其說明如下:
android:duration: 動畫執行的時間,以毫秒為單位
android:fillEnabled:true|false 動畫結束時還原到開始動畫前的狀態
android:fillBefore:true|false 動畫結束后視圖會停留在動畫開始的狀態,如果fillEnabled的值為true,它的值才有意義,否則沒有意義。默認值是true。
android:fillAfter:true|false 動畫結束后是否保留這個動畫的最后一幀的效果,它的設置不受fillEnabled的影響
android:repeatMode:reverse|restart 重復類型,reverse:表示倒序回放,restart:表示重新放一遍,這個屬性必須與repeatCount聯合使用,因為它的前提是重復,即重復播放時的播放類型。
android:repeatCount:動畫重復的次數(注意是重復的次數),設定具體數值,也是可以是infinite,表示無限循環
android:interpolator:設定的插值器,它主要用來為動畫設置一些特殊的效果,比方說:加速運動、減速運動等等。
2.2 scale動畫
特有屬性:
- android:fromXScale起始的X方向上相對自身的縮放比例,類型float
- android:toXScale:結尾的X方向上相對自身的縮放比例,類型float
- android:fromYScale:起始的Y方向上相對自身的縮放比例,類型float
- android:toYScale:結尾的Y方向上相對自身的縮放比例,類型float
- android:pivotX: 縮放起點X軸坐標,可以是數值、百分數、百分數p。
- android:pivotY: 縮放起點Y軸坐標,同pivotX。
java代碼實現
- 在activity_main.xml當中定義布局資源
- 在java代碼當中的代碼實現如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initScaleJava();
}
private void initScaleJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
ScaleAnimation scaleAnimation = new ScaleAnimation(1,2,1,2,Animation.RELATIVE_TO_SELF,
0.5f,Animation.RELATIVE_TO_SELF,0.5f);
// fromX:動畫起始X軸縮放倍數
// toX:動畫結束時X軸縮放倍數
// fromY:動畫起始Y軸縮放倍數
// toY:動畫結束時Y軸縮放倍數
// pivotXType:X軸縮放原點參數類型
// pivotXValue:X軸縮放原點參數
// pivotYType:Y軸縮放原點參數類型
// pivotYValue:Y軸縮放原點參數
scaleAnimation.setDuration(3000);
scaleAnimation.setRepeatMode(Animation.REVERSE);
//Animation.INFINITE與-1取值相同,為無限重播
scaleAnimation.setRepeatCount(Animation.INFINITE);
mImage.startAnimation(scaleAnimation);
}
動畫實現效果:以自身中心點為原點,縮放為原大小的兩倍。
構造方法
ScaleAnimation(float fromX, float toX, float fromY, float toY,
int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
參數取值介紹
對于前四個參數:0.0表示收縮到沒有 1.0表示正常無伸縮 值小于1.0表示收縮 值大于1.0表示放大
pivotXType存在三種取值
pivotXType = Animation.ABSOLUTE:縮放軸點的x坐標 = View左上角的原點 在x方向 + pivotXValue數值的點(y方向同理)pivotXType = Animation.RELATIVE_TO_SELF:縮放軸點的x坐標 = View左上角的原點 在x方向 + 自身寬度乘上pivotXValue數值的值(y方向同理)
pivotXType = Animation.RELATIVE_TO_PARENT:縮放軸點的x坐標 = View左上角的原點 在x方向 + 父控件寬度乘上pivotXValue數值的值 (y方向同理)
例子中所選參數為從原圖大小X軸和Y軸縮放到原大小的兩倍,縮放參照點為以自身寬高比例的50%處,也就是中心點。
XML實現
- 在res目錄中新建anim文件夾。
- 在anim目錄中新建一個scale_anim.xml文件(注意文件名小寫)。
- 在scale_anim.xml文件當中對動畫進行定義。
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<scale
android:duration="3000" //設置播放時長為3秒
android:fillAfter="false" //設置不保存播放完畢之后的畫面
android:fromXScale="1.0" //起始畫面X軸縮放倍數
android:fromYScale="1.0" //起始畫面Y軸縮放倍數
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
//使用accelerate_decelerate_interpolator加速--減速差值器
android:pivotX="50%" //X軸縮放原點為自身寬度的50%
android:pivotY="50%" //Y軸縮放原點為自身寬度的50%
android:toXScale="2.0" //結束畫面X軸縮放倍數
android:toYScale="2.0" //結束畫面Y軸縮放倍數
android:repeatCount= "-1" //動畫循環次數為無限循環
android:repeatMode="reverse"/> //循環模式為倒播
</set>
pivotX與pivotY相同,有三種取值方式:
- 取值為數值:當為數值時,表示在當前View的左上角,加上參數值即原點處,做為旋轉點X坐標,單位為px。
- 取值為百分數:如果是50%表示在當前控件的左上角加上自己寬度的50%做為旋轉點X坐標。
- 取值為百分數p:如果是50%p,那么就是表示在當前的左上角加上父控件寬度的50%做為旋轉點X坐標。
- 在activity當中對動畫資源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initScaleXML();
}
private void initAlphXml() {
private void initScaleXML() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animation animation = AnimationUtils.loadAnimation(this,R.anim.scale_anim);
mImage.startAnimation(animation);
}
}
運行程序,動畫效果與上述java實現相同。
2.3 translate動畫
特有屬性:
- android:fromXDelta:起始點X軸坐標。
- android:fromYDelta:起始點Y坐標。
- android:toXDelta:結束點X坐標
- android:toYDelta:結束點Y坐標
java代碼實現
- 在activity_main.xml當中定義布局資源
- 在java代碼當中的代碼實現如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initTranslateJava();
}
private void initTranslateJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
TranslateAnimation translateAnimation = new TranslateAnimation(0,300,0,300);
//fromXDelta:起始點X軸的坐標
//toXDelta:終止點X軸的坐標
//fromYDelta:起始點Y軸的坐標
//toYDelta:終止點Y軸的坐標
translateAnimation.setDuration(2000);
translateAnimation.setRepeatMode(Animation.REVERSE);
//Animation.INFINITE與-1取值相同,為無限重播
translateAnimation.setRepeatCount(Animation.INFINITE);
mImage.startAnimation(translateAnimation);
}
上述代碼實現效果:View向右下角45°移動,最終坐標為原左上角坐標的X軸正方向300,Y軸正方向300.
動畫效果如下
對于構造方法
public TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue,
int fromYType, float fromYValue, int toYType, float toYValue) {
參數類型可大體分為兩種: value和type
Type參數取值介紹
fromXType = Animation.ABSOLUTE:縮放軸點的x坐標 = View左上角的原點 在x方向 加上 pivotXValue數值的點(y方向同理) 默認為這種方式。
fromXType = Animation.RELATIVE_TO_SELF:縮放軸點的x坐標 = View左上角的原點 在x方向 加上 自身寬度乘上pivotXValue數值的值(y方向同理)
fromXType = Animation.RELATIVE_TO_PARENT:縮放軸點的x坐標 = View左上角的原點 在x方向 加上 父控件寬度乘上pivotXValue數值的值 (y方向同理)
Value取值介紹:
有三種取值方式:
- 取值為數值:當為數值時,表示在當前View的左上角,即原點處加上參數值,做為旋轉點X坐標,單位為px。
- 取值為百分數:如果是50%表示在當前控件的左上角加上自己寬度的50%做為原點X坐標。
- 取值為百分數p:如果是50%p,那么就是表示在當前的左上角加上父控件寬度的50%做為原點X坐標。
XML實現
- 在res目錄中新建anim文件夾。
- 在anim目錄中新建一個translate_anim.xml文件(注意文件名小寫)。
- 在translate_anim.xml文件當中對動畫進行定義。
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="2000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="300"
android:toYDelta="300" />
</set>
參數值類型介紹
整型值:
fromXDelta 屬性為動畫起始時 X坐標上的位置
toXDelta 屬性為動畫結束時 X坐標上的位置
fromYDelta 屬性為動畫起始時 Y坐標上的位置
toYDelta 屬性為動畫結束時 Y坐標上的位置
注意:
沒有指定fromXType toXType fromYType toYType 時候, 默認是以自己為相對參照物
長整型值:
duration 屬性為動畫持續時間
說明: 時間以毫秒為單位
- 在activity當中對動畫資源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initTranslateXML();
}
private void initTranslateXML() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animation animation = AnimationUtils.loadAnimation(this,R.anim.translate_anim);
mImage.startAnimation(animation);
}
運行程序,動畫效果與上述java實現相同。
2.4 Rotate動畫
特有屬性:
- android:fromDegrees:動畫開始時旋轉的角度位置,float類型,正值代表順時針方向度數,負值代碼逆時針方向度數
- android:toDegrees: 動畫結束時旋轉到的角度位置,float類型,正值代表順時針方向度數,負值代碼逆時針方向度數
- android:pivotX:旋轉點X軸坐標,float類型,可以是數值、百分數、百分數p三種樣式。
- android:pivotY:旋轉點Y軸坐標,取值及意義跟android:pivotX一樣。
Java實現
- 在activity_main.xml當中定義布局資源
- 在java代碼當中的代碼實現如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initRotateJava();
}
private void initRotateJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
RotateAnimation rotateAnimation = new RotateAnimation(0,90,Animation.RELATIVE_TO_SELF,
0.5f,Animation.RELATIVE_TO_SELF,0.5f);
// 參數說明:
// fromDegrees :動畫開始時 視圖的旋轉角度(正數 = 順時針,負數 = 逆時針)
// toDegrees :動畫結束時 視圖的旋轉角度(正數 = 順時針,負數 = 逆時針)
// pivotXType:旋轉軸點的x坐標的模式
// pivotXValue:旋轉軸點x坐標的相對值
// pivotYType:旋轉軸點的y坐標的模式
// pivotYValue:旋轉軸點y坐標的相對值
rotateAnimation.setRepeatMode(Animation.REVERSE);
// 視頻循環模式
rotateAnimation.setDuration(2000);
// 視頻播放時長
rotateAnimation.setRepeatCount(Animation.INFINITE);
// 視頻循環次數
mImage.startAnimation(rotateAnimation);
上述代碼實現效果:以自身中心點為坐標,順時針旋轉90度。
參數取值介紹
fromDegrees :動畫開始時 視圖的旋轉角度(正數 = 順時針,負數 = 逆時針)
toDegrees :動畫結束時 視圖的旋轉角度(正數 = 順時針,負數 = 逆時針)
XML實現
- 在res目錄中新建anim文件夾。
- 在anim目錄中新建一個set_anim.xml文件(注意文件名小寫)。
- 在set_anim.xml文件當中對動畫進行定義。
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
android:duration="3000"
android:fromDegrees="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="90" />
</set>
<!--
rotate 旋轉動畫效果
屬性:interpolator 指定一個動畫的插入器
在我試驗過程中,使用android.res.anim中的資源時候發現
有三種動畫插入器:
accelerate_decelerate_interpolator 加速-減速 動畫插入器
accelerate_interpolator 加速-動畫插入器
decelerate_interpolator 減速- 動畫插入器
其他的屬于特定的動畫效果
浮點數型值:
fromDegrees 屬性為動畫起始時物件的角度
toDegrees 屬性為動畫結束時物件旋轉的角度 可以大于360度
說明:
當角度為負數——表示逆時針旋轉
當角度為正數——表示順時針旋轉
(負數from——to正數:順時針旋轉)
(負數from——to負數:逆時針旋轉)
(正數from——to正數:順時針旋轉)
(正數from——to負數:逆時針旋轉)
pivotX 屬性為動畫相對于物件的X坐標的開始位置
pivotY 屬性為動畫相對于物件的Y坐標的開始位置
說明: 以上兩個屬性值 從0%-100%中取值
50%為物件的X或Y方向坐標上的中點位置
長整型值:
duration 屬性為動畫持續時間
說明: 時間以毫秒為單位
-->
- 在activity當中對動畫資源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initRotateXML();
}
private void initRotateXML() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.rotate_anim);
mImage.startAnimation(animation);
}
運行程序,動畫效果與上述java實現相同。
2.5 組合動畫(set標簽)
使用set標簽可以做到多種動畫類型的組合效果具體步驟如下
- 在res目錄中新建anim文件夾。
- 在anim目錄中新建一個set_anim.xml文件(注意文件名小寫)。
- 在set_anim.xml文件當中對動畫進行定義。
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:fromAlpha= "0.0"
android:toAlpha= "1.0"
android:duration= "3000" />
//alph動畫 實現效果:從透明狀態漸變到顯示狀態
<scale
android:fromXScale= "0.0"
android:toXScale= "1.0"
android:fromYScale= "0.0"
android:toYScale= "1.0"
android:pivotX= "50%"
android:pivotY= "50%"
android:duration= "3000" />
//以自身原點為坐標從0縮放原圖大小
<rotate
android:fromDegrees= "0"
android:toDegrees= "720"
android:pivotX= "50%"
android:pivotY= "50%"
android:duration= "3000"/>
//旋轉720°
<translate
android:startOffset= "3000"
android:fromXDelta= "0"
android:fromYDelta= "0"
android:toXDelta= "85"
android:toYDelta= "0"
android:duration= "1000" />
//3000毫秒以后,水平方向移動85距離
<alpha
android:startOffset= "4000"
android:fromAlpha= "1.0"
android:toAlpha= "0.0"
android:duration= "1000" />
//4000毫秒以后圖片漸變消失
</set>
- 在==activity==當中對動畫資源信息引用。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initSet();
}
private void initSet() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.set_anim);
animation.setRepeatCount(-1);
mImage.startAnimation(animation);
}
動畫效果如下:
補間動畫使用方便,但是其針對view進行操作的特性,以及只能實現視圖上的動畫而不能改變實際的view位置大小等缺點使其功能受到限制。下面將介紹功能更加強大的屬性動畫。
3.屬性動畫
- 優勢:
- 在Android3.0推出了屬性動畫。相比于屬性動畫,View動畫一個非常大的缺陷就是其不具備交互性。當某個元素發生View動畫之后,其相應事件的位置依然在進行前的地方,所以View動畫只能做到普通的動畫效果,盡量避免交互操作。View動畫的優勢也十分明顯:效率比較高,使用也方便。
- View動畫只能實現比較單一的View的移動縮放等效果,效果比較單一,屬性動畫則是通過屬性值變化來重新布局View,理論上可以實現任何動畫效果。
- 問題:
- 在使用上不如View動畫方便。
3.1 原理解析
屬性動畫有兩個非常重要的類:ValueAnimator 類 和 ObjectAnimator 類
- ValueAnimator:通過不斷控制值的變化,再不斷手動賦給對象的屬性,從而實現動畫效果。
- ObjectAnimator:直接對對象的屬性值進行改變操作,通過不斷控制值的變化,再不斷自動賦給對象的屬性,從而實現動畫效果。其實現繼承了ValueAnimator。
3.2 差值器和估值器
- 插值器(Interpolator)前面我們在介紹補間動畫時就有提到過,它主要用來為動畫設置一些特殊的效果,比方說:加速運動、減速運動、動畫結束的時候彈等等。主要有
- accelerate_decelerate_interpolator 加速-減速 動畫插入器
- accelerate_interpolator 加速-動畫插入器
- decelerate_interpolator 減速- 動畫插入器
- 估值器(TypeEvaluator)決定值的具體變化數值
我們先來看一下系統提供的估值器:
public class FloatEvaluator implements TypeEvaluator<Number> {
/**
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type <code>float</code> or
* <code>Float</code>
* @param endValue The end value; should be of type <code>float</code> or <code>Float</code>
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public Float evaluate(float fraction, Number startValue, Number endValue) {
<!--fraction 動畫的完成度-->
<!--startValue 動畫的初始值-->
<!--endValue 動畫的結束值-->
float startFloat = startValue.floatValue();
<!--計算返回結果的公式為result = x0 + t * (v1 - v0)-->
<!--x0=startValue-->
<!--x1 = endValue-->
<!--t = fraction-->
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
這是由系統提供的操作float類型數據的估值器,可以根據我們提供的fraction、startValue以及endValue不斷地產float數值對象返回給我們,我們可以根據返回的數據對我們的View進行更改。
但是很多情況下我們需要操作的不只是float或者int類型等基本數據類型,所以我們就需要自定義==ObjectEvaluator==。
模仿==FloatEvaluator==,自定義==ObjectEvaluator==如下:
// 實現TypeEvaluator接口
public class ObjectEvaluator implements TypeEvaluator<Object>{
// 復寫evaluate()
// 在evaluate()里寫入對象動畫過渡的邏輯
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
... // 寫入對象動畫過渡的邏輯
// 返回對象動畫過渡的邏輯計算后的值
return value;
}
后面會進行實例說明。再回過頭來看我們的主角:ValueAnimator和ObjectAnimator類
3.3 ObjectAnimator類
本質原理: 通過不斷控制值的變化,再不斷自動賦給對象的屬性,從而實現動畫效果。
先來看一個簡單的例子:
private void initObjectAnimationJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
ObjectAnimator alpha = ObjectAnimator.ofFloat(mImage, "alpha", 0f, 1f);
alpha.setDuration(2000);//設置動畫時間
alpha.setInterpolator(new DecelerateInterpolator());//設置動畫插入器,減速
alpha.setRepeatCount(-1);//設置動畫重復次數,這里-1代表無限
alpha.setRepeatMode(ValueAnimator.REVERSE);//設置動畫循環模式,注意這里是ValueAnimator.REVERSE而不是Animator.REVERSE
alpha.start();//啟動動畫。
}
實現效果:圖片由隱藏漸漸顯示。
動畫對象定義完后的操作與View動畫基本一致,最主要的區別在于構造方法,這里我使用了ObjectAnimator.ofFloat,ofFloat()方法主要有兩個作用:設置參數以及返回ObjectAnimator對象,其方法如下:
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
//1. 需要操作的對象,注意這里與補間動畫不同,不止限于View對象,而可以是任意操作對象。
//2. propertyName需要操作的屬性,這個屬性必須要有get和set方法才可以。
//3. 這是一個可變參數,為后續的變化趨勢。如設置兩個參數v1,v2,則屬性由v1變化到v2,如果設置的是v1,v2,v3,則由v1變化到v2再變化到v3以此類推。
-
透明度:
ObjectAnimator alpha = ObjectAnimator.ofFloat(text, "alpha", 0f, 1f);
透明度由0.0變為1.0。
-
伸縮X軸:
ObjectAnimator scaleX = ObjectAnimator.ofFloat(text, "scaleX", 0f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(text, "scaleY", 0f, 1f);
X軸(Y軸)方向由0縮放到原圖大小。
-
移動:
ObjectAnimator translationUp = ObjectAnimator.ofFloat(text, "Y",0f,300f, 0f);
Y軸方向:0f-> -300f,向左;-300f-> 0f,向右。
-
旋轉
ObjectAnimator animator = ObjectAnimator.ofFloat(text, "rotation", 0f, 360f, 0f);
旋轉動畫:0f -> 360f ,順時針; 360f -> 0f,逆時針。
可以操作的動畫屬性propertyName的取值可以為:
屬性 | 作用 | 數值類型 |
---|---|---|
Alpha | 控制View的透明度 | float |
TranslationX | 控制X方向的位移 | float |
TranslationY | 控制Y方向的位移 | float |
ScaleX | 控制X方向的縮放倍數 | float |
ScaleY | 控制Y方向的縮放倍數 | float |
Rotation | 控制以屏幕方向為軸的旋轉度數 | float |
RotationX | 控制以X軸為軸的旋轉度數 | float |
RotationY | 控制以Y軸為軸的旋轉度數 | float |
ObjectAnimation組合動畫
Java代碼
使用AnimatorSet完成組合動畫播放。
- anim1.with(anim2):anim1與anim2同時播放。
- anim1.befor(anim2):在anim2之前播放anim1。
- anim1.after(anim2):在anim2之后播放anim1。
- anim1.after(long delay):delay時間之后播放anim1。
具體使用代碼如下:
private void initObjectAnimationSetJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
AnimatorSet set = new AnimatorSet() ;
//X軸旋轉180°
ObjectAnimator rotationX = ObjectAnimator .ofFloat(mImage, "rotationX", 0f, 180f);
rotationX.setDuration(2000);
//Y軸旋轉180°
ObjectAnimator rotationY = ObjectAnimator .ofFloat(mImage, "rotationY", 0f, 180f);
rotationY.setDuration(2000);
//X軸方向縮放到原圖大小
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mImage, "scaleX", 0f, 1f);
scaleX.setDuration(2000);
//Y軸方向縮放到原圖大小
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mImage, "scaleY", 1f, 0f);
scaleY.setDuration(2000);
set.play(rotationX).with(scaleX); //X軸旋轉與X軸縮放動畫同時進行
set.play(rotationX).before(rotationY); //X軸旋轉動畫完成以后開始Y軸旋轉
set.play(rotationY).with(scaleY);//Y軸旋轉動畫與Y軸縮放同時開始
set.start(); //開始播放動畫
}
動畫效果:X軸旋轉與X軸縮放動畫同時進行,結束之后Y軸旋轉動畫與Y軸縮放同時開始。實際效果如下:
XML實現
set中的屬==性android:ordering==:規定了這個set中的動畫的執行順序,包括:
- together(默認):set中的動畫同時執行
- sequentially:set中的動畫按順序執行
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<set android:ordering="together">
<objectAnimator
android:duration="2000"
android:propertyName="rotationX"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="180" />
<objectAnimator
android:duration="2000"
android:propertyName="scaleX"
android:repeatMode="reverse"
android:valueFrom="0.0"
android:valueTo="1.0" />
</set>
<set android:ordering="together">
<objectAnimator
android:duration="2000"
android:propertyName="rotationY"
android:repeatMode="reverse"
android:valueFrom="0"
android:valueTo="180" />
<objectAnimator
android:duration="2000"
android:propertyName="scaleY"
android:repeatMode="reverse"
android:valueFrom="1.0"
android:valueTo="0.0" />
</set>
</set>
java代碼:
private void initObjectAnimationSetXML() {
mImage = (ImageView) findViewById(R.id.animation_img);
Animator animator = AnimatorInflater.loadAnimator(this,R.animator.object_set_anim);
animator.setTarget(mImage);
animator.start();
}
實現效果與java代碼一致。
3.4 ValueAnimator類
ValueAnimator類本質上是一種值得操作機制,想要實現動畫,是需要開發者手動將這些值 賦給對象的屬性值。
ValueAnimator動畫同樣存在java和xml兩種使用方式。建議使用java代碼的方式,這里只介紹java方式,xml有興趣的可以自己了解一下。
ValueAnimator類常用的方法與ObjectAnimation類似,主要方法有
- public static ValueAnimator ofInt(int... values)
- public static ValueAnimator ofArgb(int... values)
- public static ValueAnimator ofFloat(float... values)
- public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
前三種方法使用類似,后面的參數都表示變化趨勢,即:設置兩個參數v1,v2,則屬性由v1變化到v2,如果設置的是v1,v2,v3,則由v1變化到v2再變化到v3以此類推。
以ofFloat為例,使用方法如下
private void initValueAnimationJava() {
mImage = (ImageView) findViewById(R.id.animation_img);
//步驟1. 定義ValueAnimator方法
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f,1.0f);
//獲取原控件寬高
final int width = mImage.getLayoutParams().width;
final int height = mImage.getLayoutParams().height;
//設置動畫屬性
valueAnimator.setDuration(2000);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
//步驟2. 添加AnimatorUpdateListener方法永聯監聽值得變化趨勢
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
float currentValue = (Float) animator.getAnimatedValue();
// 獲得每次變化后的屬性值
// 步驟3:每次值變化時,將值手動賦值給對象的屬性
// 即將每次變化后的值 賦 給按鈕的寬度,這樣就實現了按鈕寬度屬性的動態變化
mImage.getLayoutParams().width = (int) (width*currentValue);
mImage.getLayoutParams().height = (int) (height*currentValue);
// 步驟4:刷新視圖,即重新繪制,從而實現動畫效果
mImage.requestLayout();
}
});
valueAnimator.start();
}
實際動畫效果如下:
如果我們想實現一些比較負載的動畫效果,而系統提供的估值器滿足不來我們時怎么辦,這時候我們之前提到的估值器TypeEvaluator就該登場了,通過方法public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values),設置估值器以后將對數據按照估值器進行計算并返回值,通過點贊動畫例子了解實戰應用。
4. 屬性動畫實戰--點贊動畫
不多說,直接上代碼
- 首先是點贊動畫的自定義==PraiseLayout==
public class PraiseLayout extends RelativeLayout {
//底部點贊按鈕寬高
private int dHeight;
private int dWidth;
//整個View空間寬高
private int mHeight;
private int mWidth;
private LayoutParams layoutParams;
private Random random = new Random();
//圖片資源數組
private Drawable[] drawables;
public PraiseLayout(Context context) {
super(context);
init();
}
public PraiseLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PraiseLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//初始化圖片資源
drawables = new Drawable[4];
Drawable red = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_red);
Drawable blue = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_blue);
Drawable orange = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_orange);
Drawable pink = ContextCompat.getDrawable(getContext(), R.drawable.nemo_heart_pink);
drawables[0] = red;
drawables[1] = blue;
drawables[2] = orange;
drawables[3] = pink;
dHeight = red.getIntrinsicHeight();
dWidth = red.getIntrinsicWidth();
layoutParams = new LayoutParams(dWidth, dHeight);
//點贊動畫開始于布局底部
layoutParams.addRule(CENTER_HORIZONTAL, TRUE);
layoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE);
}
//獲取組件大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mHeight = getMeasuredHeight();
mWidth = getMeasuredWidth();
}
//點擊動畫事件
public void addFavorWithoutBiz() {
ImageView imageView = new ImageView(getContext());
imageView.setImageDrawable(drawables[random.nextInt(4)]);
imageView.setLayoutParams(layoutParams);
addView(imageView);
if (mWidth > 0 && dHeight > 0) {
Animator set = getBezierValueAnimator(imageView);
set.addListener(new AnimEndListener(imageView));
set.start();
}
}
//添加點贊動畫
private ValueAnimator getBezierValueAnimator(View target) {
//點贊動畫的估值器,參數為貝塞爾曲線兩個中間點
BezierEvaluator evaluator = new BezierEvaluator(getPointF(2), getPointF(1));
//參數1:估值器對象 參數2、3:三重貝塞爾曲線的起點和終止點。
ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((mWidth - dWidth) / 2, mHeight - dHeight - 20), new PointF(random.nextInt(getWidth()), 0));
animator.addUpdateListener(new BezierListenr(target));
animator.setTarget(target);
animator.setDuration(3000);
return animator;
}
private PointF getPointF(int scale) {
PointF pointF = new PointF();
pointF.x = random.nextInt((mWidth - 50));//減去50 是為了控制 x軸活動范圍,看效果 隨意~~
//再Y軸上 為了確保第二個點 在第一個點之上,我把Y分成了上下兩半 這樣動畫效果好一些 也可以用其他方法
pointF.y = random.nextInt((mHeight - 150)) / scale;
return pointF;
}
//終止動畫監聽
class AnimEndListener extends AnimatorListenerAdapter {
private View target;
public AnimEndListener(View target) {
this.target = target;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
removeView((target));
}
}
//屬性動畫的監聽類,當數值更新時觸發
class BezierListenr implements ValueAnimator.AnimatorUpdateListener {
private View target;
public BezierListenr(View target) {
this.target = target;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//這里獲取到貝塞爾曲線計算出來的的x y值 賦值給view 這樣就能讓愛心隨著曲線走啦
PointF pointF = (PointF) animation.getAnimatedValue();
target.setX(pointF.x);
target.setY(pointF.y);
// alpha動畫
target.setAlpha(1 - animation.getAnimatedFraction());
}
}
}
- 估值器==BezierEvaluator==的實現,這里使用三重貝塞爾曲線的公式計算。
public class BezierEvaluator implements TypeEvaluator<PointF> {
private PointF pointF1;
private PointF pointF2;
public BezierEvaluator(PointF pointF1, PointF pointF2) {
this.pointF1 = pointF1;
this.pointF2 = pointF2;
}
@Override
public PointF evaluate(float time, PointF startValue, PointF endValue) {
float timeLeft = 1.0f - time;
PointF point = new PointF();//結果
point.x = timeLeft * timeLeft * timeLeft * (startValue.x)
+ 3 * timeLeft * timeLeft * time * (pointF1.x)
+ 3 * timeLeft * time * time * (pointF2.x)
+ time * time * time * (endValue.x);
point.y = timeLeft * timeLeft * timeLeft * (startValue.y)
+ 3 * timeLeft * timeLeft * time * (pointF1.y)
+ 3 * timeLeft * time * time * (pointF2.y)
+ time * time * time * (endValue.y);
return point;
}
}
- 在xml布局文件中引入自定義布局
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.liubohua.myapplication.PraiseLayout
android:id="@+id/animation_praise"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.example.liubohua.myapplication.PraiseLayout>
<ImageView
android:id="@+id/animation_praise_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/nemo_heart_blue"
android:layout_gravity="center|bottom"/>
</FrameLayout>
- 在java代碼實現
private void initPraise() {
animation_praise_img = (ImageView) findViewById(R.id.animation_praise_img);
animation_praise = (PraiseLayout) findViewById(R.id.animation_praise);
animation_praise_img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
animation_praise.addFavorWithoutBiz();
}
});
}
實現效果圖:
尾聲
差不多Android常見的動畫操作也就這些,通過簡單的學習再加上龐大的腦洞就可以實現非常炫酷的效果,趕緊嘗試一下吧。