上篇給大家講了有關(guān) AnimatorSet 的代碼實現(xiàn)方法,這篇我們就分別來看看如何利用 xml 來實現(xiàn) ValueAnimator、ObjectAnimator 和 AnimatorSet; 在文章最后,將利用 AnimatorSet 來實現(xiàn)一個路徑動畫,效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/106.gif" alt="" />
(這里實現(xiàn)的是一個動畫菜單,在點擊菜單按鈕時,彈出各個菜單)
在 xml 中對應(yīng) animator 總共有三個標簽,分別是
<animator />:對應(yīng) ValueAnimator
<objectAnimator />:對應(yīng) ObjectAnimator
<set />:對應(yīng) AnimatorSet
下面我們逐個來看各個標簽的用法
(1)、animator 所有字段及意義
下面是完整的 animator 所有的字段及取值范圍:
<animator
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]
android:interpolator=["@android:interpolator/XXX"]/>
http://wiki.jikexueyuan.com/project/android-animation/images/19.png" alt="" />
(2)、將 xml 加載到程序中
在定義了一個 xml 后,我們需要將其加載到程序中,使用的方法如下:
ValueAnimator valueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(MyActivity.this,R.animator.animator);
valueAnimator.start();
通過 loadAnimator 將 animator 動畫的 xml 文件,加載進來,根據(jù)類型進行強轉(zhuǎn)。
(3)、簡單示例
下面我們就舉個例子來看看如何來使用 xml 生成對應(yīng)的 animator 動畫 先看看整體效果圖:
http://wiki.jikexueyuan.com/project/android-animation/images/107.gif" alt="" />
在效果圖中可以看到,我們生成了一個動畫,動態(tài)了改變了當前控件的坐標位置。 我們先在 res/animator 文件夾下生成一個動畫的 xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="300"
android:duration="1000"
android:valueType="intType"
android:interpolator="@android:anim/bounce_interpolator"/>
在這里,我們將 valueType 設(shè)置為 intType,所以對應(yīng)的 android:valueFrom、android:valueTo 都必須是 int 類型的值;插值器使用 bounce 回彈插值器 然后看看加載到程序中過程:
ValueAnimator valueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(MyActivity.this,
R.animator.animator);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int offset = (int)animation.getAnimatedValue();
mTv1.layout( offset,offset,mTv1.getWidth()+offset,mTv1.getHeight() + offset);
}
});
valueAnimator.start();
由于我們 xml 中根屬性是
(1)字段意義及使用方法
同樣,我們先來看看它的所有標簽的意義:
<objectAnimator
android:propertyName="string"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]
android:interpolator=["@android:interpolator/XXX"]/>
意義:
下面我們就看看如何使用:
ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(MyActivity.this,
R.animator.object_animator);
animator.setTarget(mTv1);
animator.start();
同樣是使用 loadAnimator 加載對應(yīng)的 xml 動畫。然后使用 animator.setTarget(mTv1);綁定上動畫目標。因為在 xml 中,沒有設(shè)置目標的參數(shù),所以我們必須通過代碼將目標控件與動畫綁定。
(2)、使用示例
我們先寫一個動畫的 xml:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="TranslationY"
android:duration="2000"
android:valueFrom="0.0"
android:valueTo="400.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:valueType="floatType"
android:repeatCount="1"
android:repeatMode="reverse"
android:startOffset="2000"/>
在這個 xml 中,我們定義了更改屬性為 TranslationY,即改變縱坐標;時長為 2000 毫秒。從 0 變到 400;使用的插值器是加速插值器,對應(yīng)的值類型為 float 類型。 有些同學(xué)可能會問,為什么是 float 類型,因為 setTranslationY 函數(shù)的參數(shù)是 float 類型的,聲明如下:
public void setTranslationY(float translationY)
最后是設(shè)置重復(fù)次數(shù)和重復(fù)模式。將動畫激活延時設(shè)置為 2000 毫秒; 然后是加載動畫:
ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(MyActivity.this,
R.animator.object_animator);
animator.setTarget(mTv1);
animator.start();
效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/108.gif" alt="" />
在點擊后,延時 2000 毫秒后,開始運行。逆序重復(fù)運行一次。 源碼在文章底部給出
(3)、使用 color 屬性示例
這里我們就演示一下如何使用 android:valueFrom、android:valueTo 的 color 屬性用法, 我們建立一個 objectAnimator 的動畫文件:
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="BackgroundColor"
android:duration="5000"
android:valueFrom="#ffff00ff"
android:valueTo="#ffffff00"/>
設(shè)置屬性名為 BackgroundColor,即對應(yīng)的 set 函數(shù)為 setBackgroundColor(int color); android:valueFrom 和 android:valueTo 的取值都為顏色值,即#開頭的八位數(shù)值;即 ARGB 值; 使用方法不變:
ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(MyActivity.this,
R.animator.color_animator);
animator.setTarget(mTv1);
animator.start();
效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/109.gif" alt="" />
從效果圖中可以看到,雖然實現(xiàn)了顏色變化,但會一直閃;所以直接利用 xml 實現(xiàn)的動畫效果并不怎么好,所以如果想要實現(xiàn)顏色變化,還是利用代碼來實現(xiàn)吧。前面的文章中,我們已經(jīng)講過如何利用 ValueAnimator 和 ObjectAnimator 來實現(xiàn)顏色過渡和原理了。大家可以翻看下。 源碼在文章底部給出
(1)字段意義及使用方法
這個是 AnimatorSet 所對應(yīng)的標簽。它只有一個屬性:
<set
android:ordering=["together" | "sequentially"]>
android:ordering:表示動畫開始順序。together 表示同時開始動畫,sequentially 表示逐個開始動畫; 加載方式為:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(MyActivity.this,
R.animator.set_animator);
set.setTarget(mTv1);
set.start();
同樣是通過 loadAnimator 加載動畫,然后將其強轉(zhuǎn)為 AnimatorSet;
(2)、示例
在 res/animator 文件夾下新建一個文件(set_animator.xml):
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueFrom="0"
android:valueTo="400"
android:valueType="floatType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueFrom="0"
android:valueTo="300"
android:valueType="floatType"/>
</set>
這里有兩個 objectAnimator 動畫,一個改變值 x 坐標,一個改變值 y 坐標;取值分別為 0-400 和 0-300; 然后在代碼中加載:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(MyActivity.this,
R.animator.set_animator);
set.setTarget(mTv1);
set.start();
動畫效果如下:
http://wiki.jikexueyuan.com/project/android-animation/images/110.gif" alt="" />
源碼在文章底部給出
最后總結(jié)一下,所有 animator 標簽及取值范圍如下:
<set
android:ordering=["together" | "sequentially"]>
<objectAnimator
android:propertyName="string"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>
<animator
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>
<set>
...
</set>
</set>
各字段的取值意義在上面講解時已經(jīng)給出,大家可以翻回去看看。
在講完了 XML 使用方法之后,AnimatorSet 的部分就完全結(jié)束了,下面我們就利用學(xué)到的知識來看一下開篇時的那個效果是如何實現(xiàn)的吧。
http://wiki.jikexueyuan.com/project/android-animation/images/111.gif" alt="" />
我們先來分析下這個效果,在用戶點擊按鈕時,把菜單彈出來;彈出來的時候,動畫一點從小變到大,一邊透明度從 0 變到 1.關(guān)鍵問題是,怎么樣實現(xiàn)各個菜單以當前點擊按鈕為圓心排列在圓形上;
在開始寫代碼之前,我們先講講,如何根據(jù)圓半徑來定位每個圖片的位置,先看下圖:
http://wiki.jikexueyuan.com/project/android-animation/images/20.png" alt="" />
在上面的圖中,我們可以清晰的看到,假如當前菜單與 Y 軸的夾角是 a 度,那么這個菜單所移動的 X 軸距離為 radius sin(a);Y 軸的移動距離為 radius cos(a); 這是非常簡單的三角函數(shù)的計算。想必這塊大家理解起來是沒有問題的。 那么第一個問題來了,這個夾角 a 是多少度呢? 很顯然,這里所有的菜單的夾角之和是 90 度。我們總共有五個菜單項,把 90 度夾角做了 4 等分。所以夾角 a 的度數(shù)為 90/4 = 22;所以這五個菜單,第一個菜單的夾角是 0 度,第二個菜單的夾角是 22 度,第三個菜單的夾角是 222 度,第四個夾角是 223 度,第五個的夾角是 224 度. 我們假設(shè) index 表示當前菜單的位置索引,從 0 開始,即第一個菜單的索引是 0,第二個菜單的索引是 1,第三個菜單的索引是 2……,而當前的菜單與 y 軸的夾角恰好占了 22 度的 index 份;所以當前菜單與 Y 軸的夾角為 22 index;這個公式非常重要,大家在這里一定要理解,下面代碼中會用到。 第二個問題來了,如何求對應(yīng)角度的 sin,cos 值 想必很多同學(xué)都知道,JAVA 中有一個 Math 類,它其中有四個函數(shù):
/**
* 求對應(yīng)弧度的正弦值
*/
double sin(double d)
/**
* 求對應(yīng)弧度的余弦值
*/
double cos(double d)
/**
* 求對應(yīng)弧度的正切值
*/
double tan(double d)
這里要非常注意的是,這三個函數(shù)的輸入?yún)?shù)不是度數(shù),而是對應(yīng)的度數(shù)的弧度值! 角度與其對應(yīng)的弧度值對應(yīng)關(guān)系如下:
http://wiki.jikexueyuan.com/project/android-animation/images/21.png" alt="" />
在 Math 中有兩種方法可以得到弧度值: 第一種方法:在 Math 中,Math.PI 不僅代表圓周率π,也代表 180 度角所對應(yīng)的弧度值。所以 Math.sin(Math.PI)就表示 180 度的正弦值,Math.sin(Math.PI/2)就表示 90 度的正弦值。 第二種方法:根據(jù)度數(shù)獲得弧度值 在 Math 中也提供了一個方法
/**
* Math 中根據(jù)度數(shù)得到弧度值的函數(shù)
*/
double toRadians(double angdeg)
這個函數(shù)就是 Math 中根據(jù)度數(shù)得到弧度值的函數(shù),參數(shù) angdeg 指度數(shù),返回值是對應(yīng)的弧度值。 所以比如我們要求 22 度對應(yīng)的弧度值就是 Math.toRadians(22);所以如果我們要求 22 度所對應(yīng)的正弦值就是 Math.sin(Math.toRadians(22)) 在講了如何根據(jù)半徑求得每個菜單項的位置之后,我們來看看示例工程的代碼。
布局代碼很簡單,就是利用 FrameLayout 將所有的菜單都蓋在按鈕的下面,效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/22.png" alt="" />
對應(yīng)代碼為:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:layout_marginRight="10dp">
<Button
android:id="@+id/menu"
style="@style/MenuStyle"
android:background="@drawable/menu"/>
<Button
android:id="@+id/item1"
style="@style/MenuItemStyle"
android:background="@drawable/circle1"
android:visibility="gone"/>
<Button
android:id="@+id/item2"
style="@style/MenuItemStyle"
android:background="@drawable/circle2"
android:visibility="gone"/>
<Button
android:id="@+id/item3"
style="@style/MenuItemStyle"
android:background="@drawable/circle3"
android:visibility="gone"/>
<Button
android:id="@+id/item4"
style="@style/MenuItemStyle"
android:background="@drawable/circle4"
android:visibility="gone"/>
<Button
android:id="@+id/item5"
style="@style/MenuItemStyle"
android:background="@drawable/circle5"
android:visibility="gone"/>
</FrameLayout>
其中的 style 代碼為:
<resources>
<style name="MenuStyle">
<item name="android:layout_width">50dp</item>
<item name="android:layout_height">50dp</item>
<item name="android:layout_gravity">right|bottom</item>
</style>
<style name="MenuItemStyle">
<item name="android:layout_width">45dp</item>
<item name="android:layout_height">45dp</item>
<item name="android:layout_gravity">right|bottom</item>
</style>
</resources>
布局是沒什么難度的,下面我們就來看看 MyActivity 中的處理。
(1)、先看看框架部分:
public class MyActivity extends Activity implements View.OnClickListener{
private static final String TAG = "MainActivity";
private Button mMenuButton;
private Button mItemButton1;
private Button mItemButton2;
private Button mItemButton3;
private Button mItemButton4;
private Button mItemButton5;
private boolean mIsMenuOpen = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initView();
}
private void initView() {
mMenuButton = (Button) findViewById(R.id.menu);
mMenuButton.setOnClickListener(this);
mItemButton1 = (Button) findViewById(R.id.item1);
mItemButton1.setOnClickListener(this);
mItemButton2 = (Button) findViewById(R.id.item2);
mItemButton2.setOnClickListener(this);
mItemButton3 = (Button) findViewById(R.id.item3);
mItemButton3.setOnClickListener(this);
mItemButton4 = (Button) findViewById(R.id.item4);
mItemButton4.setOnClickListener(this);
mItemButton5 = (Button) findViewById(R.id.item5);
mItemButton5.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v == mMenuButton) {
if (!mIsMenuOpen) {
mIsMenuOpen = true;
doAnimateOpen(mItemButton1, 0, 5, 300);
doAnimateOpen(mItemButton2, 1, 5, 300);
doAnimateOpen(mItemButton3, 2, 5, 300);
doAnimateOpen(mItemButton4, 3, 5, 300);
doAnimateOpen(mItemButton5, 4, 5, 300);
} else {
mIsMenuOpen = false;
doAnimateClose(mItemButton1, 0, 5, 300);
doAnimateClose(mItemButton2, 1, 5, 300);
doAnimateClose(mItemButton3, 2, 5, 300);
doAnimateClose(mItemButton4, 3, 5, 300);
doAnimateClose(mItemButton5, 4, 5, 300);
}
} else {
Toast.makeText(this, "你點擊了" + v, Toast.LENGTH_SHORT).show();
}
}
………………
}
這部分代碼很簡單,就是利用 findviewById 來找到每個菜單的實例,然后對他們添加點擊響應(yīng):
public void onClick(View v) {
if (v == mMenuButton) {
if (!mIsMenuOpen) {
mIsMenuOpen = true;
doAnimateOpen(mItemButton1, 0, 5, 300);
…………
} else {
mIsMenuOpen = false;
doAnimateClose(mItemButton1, 0, 5, 300);
…………
}
} else {
Toast.makeText(this, "你點擊了" + v, Toast.LENGTH_SHORT).show();
}
}
其中彈出主菜單的按鈕是 mMenuButton,當點擊 mMenuButton 時,利用 mIsMenuOpen 來標識當前是否已經(jīng)彈出菜單;如果沒有彈出,則利用 doAnimateOpen(mItemButton1, 0, 5, 300)將 mItemButton1 彈出來;其它按鈕類似。如果已經(jīng)彈出來,則利用 doAnimateClose(mItemButton1, 0, 5, 300);將 mItemButton1 收回。 下面我們就分別來看看 doAnimateOpen()和 doAnimateClose()的代碼;
(2)、doAnimateOpen()——彈出菜單
先貼出完整代碼:
private void doAnimateOpen(View view, int index, int total, int radius) {
if (view.getVisibility() != View.VISIBLE) {
view.setVisibility(View.VISIBLE);
}
double degree = Math.toRadians(90)/(total - 1) * index;
int translationX = -(int) (radius * Math.sin(degree));
int translationY = -(int) (radius * Math.cos(degree));
AnimatorSet set = new AnimatorSet();
//包含平移、縮放和透明度動畫
set.playTogether(
ObjectAnimator.ofFloat(view, "translationX", 0, translationX),
ObjectAnimator.ofFloat(view, "translationY", 0, translationY),
ObjectAnimator.ofFloat(view, "scaleX", 0f, 1f),
ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f),
ObjectAnimator.ofFloat(view, "alpha", 0f, 1));
//動畫周期為 500ms
set.setDuration(1 * 500).start();
}
我們倒過來看,先看動畫部分:
set.playTogether(
ObjectAnimator.ofFloat(view, "translationX", 0, translationX),
ObjectAnimator.ofFloat(view, "translationY", 0, translationY),
ObjectAnimator.ofFloat(view, "scaleX", 0f, 1f),
ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f),
ObjectAnimator.ofFloat(view, "alpha", 0f, 1));
這里構(gòu)造的動畫是利用 translationX 和 translationY 將控件移動到指定位置。同時,scaleX、scaleY、alpha 都從 0 變到 1;最關(guān)鍵的部分是如何得到 translationX 和 translationY 的值。 在這部分的開篇,我們首先講了,如何講了
translationX = radius * sin(a)
translationY = radius * cos(a)
我們來看看在代碼中如何去做的:
double degree = Math.toRadians(90)/(total - 1) * index;
int translationX = -(int) (radius * Math.sin(degree));
int translationY = -(int) (radius * Math.cos(degree));
首先,是求得兩個菜單的夾角,即公式里的 a 值。Math.toRadians(90)/(total - 1)表示 90 度被分成了 total-1 份,其中每一份的弧度值; 我們前面講過,假設(shè)每一份的弧度值是 22 度,那么當前菜單與 Y 軸的夾角就是 22 index 度。這里類似,當前菜單與 y 軸的弧度值就是 Math.toRadians(90)/(total - 1) index 在求得夾角以后,直接利用 translationX = radius * sin(a)就可以得到 x 軸的移動距離,但又因為菜單是向左移動了 translationX 距離。所以根據(jù)坐標系向下為正,向右為正的原則。這里的移動距離 translationX 應(yīng)該是負值。我們需要的 translationY,因為是向上移動,所以也是負值:
int translationX = -(int) (radius * Math.sin(degree));
int translationY = -(int) (radius * Math.cos(degree));
在理解了彈出的部分之后,收回的代碼就好理解了
(3)、doAnimateClose()——收回菜單
收回菜單就是把彈出菜單的動畫反過來,讓它從 translateX,translateY 的位置上回到 0 點,scaleX、scaleY、alpha 的值從 1 變到 0 即可:
private void doAnimateClose(final View view, int index, int total,
int radius) {
if (view.getVisibility() != View.VISIBLE) {
view.setVisibility(View.VISIBLE);
}
double degree = Math.PI * index / ((total - 1) * 2);
int translationX = -(int) (radius * Math.sin(degree));
int translationY = -(int) (radius * Math.cos(degree));
AnimatorSet set = new AnimatorSet();
//包含平移、縮放和透明度動畫
set.playTogether(
ObjectAnimator.ofFloat(view, "translationX", translationX, 0),
ObjectAnimator.ofFloat(view, "translationY", translationY, 0),
ObjectAnimator.ofFloat(view, "scaleX", 1f, 0f),
ObjectAnimator.ofFloat(view, "scaleY", 1f, 0f),
ObjectAnimator.ofFloat(view, "alpha", 1f, 0f));
set.setDuration(1 * 500).start();
}
這段代碼是很容易理解的,但我在這里求 degree 的時候,換了一種方法:
double degree = Math.PI * index / ((total - 1) * 2);
其實這句代碼與上面的
double degree = Math.toRadians(90)/(total - 1) * index;
是同一個意思。 還記得,我們在講原理的時候,提到過 Math.PI 不僅表示圓周率,也表示 180 度所對應(yīng)的弧度。 所以 Math.toRadians(90)就等于 Math.PI/2,這樣,這兩個公式就是完全一樣的了。 源碼在文章底部給出 好了,到這里有關(guān) AnimatorSet 的部分就講完了,下篇給大家講講有關(guān) viewGroup 動畫相關(guān)的知識。
源碼內(nèi)容: 1、BlogXMLAnimator:第一部分:聯(lián)合動畫的 XML 實現(xiàn)對應(yīng)源碼 2、BlogAnimatorSetDemo:第二部分:開篇示例——AnimatorSet 應(yīng)用對應(yīng)源碼
如果本文有幫到你,記得加關(guān)注哦 源碼下載地址: csdn:http://download.csdn.net/detail/harvic880925/9448719 github: 請大家尊重原創(chuàng)者版權(quán),轉(zhuǎn)載請標明出處:http://blog.csdn.net/harvic880925/article/details/50763286 謝謝