上幾篇給大家講了 ValueAnimator,但 ValueAnimator 有個(gè)缺點(diǎn),就是只能對(duì)數(shù)值對(duì)動(dòng)畫計(jì)算。我們要想對(duì)哪個(gè)控件操作,需要監(jiān)聽動(dòng)畫過程,在監(jiān)聽中對(duì)控件操作。這樣使用起來相比補(bǔ)間動(dòng)畫而言就相對(duì)比較麻煩。 為了能讓動(dòng)畫直接與對(duì)應(yīng)控件相關(guān)聯(lián),以使我們從監(jiān)聽動(dòng)畫過程中解放出來,谷歌的開發(fā)人員在 ValueAnimator 的基礎(chǔ)上,又派生了一個(gè)類 ObjectAnimator; 由于 ObjectAnimator 是派生自 ValueAnimator 的,所以 ValueAnimator 中所能使用的方法,在 ObjectAnimator 中都可以正常使用。 但 ObjectAnimator 也重寫了幾個(gè)方法,比如 ofInt(),ofFloat()等。我們先看看利用 ObjectAnimator 重寫的 ofFloat 方法如何實(shí)現(xiàn)一個(gè)動(dòng)畫:(改變透明度)
ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"alpha",1,0,1);
animator.setDuration(2000);
animator.start();
效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/68.gif" alt="" />
我們這里還是直接使用上一篇的框架代碼;(當(dāng)點(diǎn)擊 start anim 時(shí)執(zhí)行動(dòng)畫)從上面的代碼中可以看到構(gòu)造 ObjectAnimator 的方法非常簡(jiǎn)單:
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotation",0,180,0);
animator.setDuration(2000);
animator.start();
效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/69.gif" alt="" />
從代碼中可以看到,我們只需要改變 ofFloat()的第二個(gè)參數(shù)的值就可以實(shí)現(xiàn)對(duì)應(yīng)的動(dòng)畫; 那么問題來了,我們?cè)趺粗赖诙€(gè)參數(shù)的值是啥呢?
我們?cè)倩貋砜礃?gòu)造改變 rotation 值的 ObjectAnimator 的方法
ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotation",0,180,0);
TextView 控件有 rotation 這個(gè)屬性嗎?沒有,不光 TextView 沒有,連它的父類 View 中也沒有這個(gè)屬性。那它是怎么來改變這個(gè)值的呢?其實(shí),ObjectAnimator 做動(dòng)畫,并不是根據(jù)控件 xml 中的屬性來改變的,而是通過指定屬性所對(duì)應(yīng)的 set 方法來改變的。比如,我們上面指定的改變 rotation 的屬性值,ObjectAnimator 在做動(dòng)畫時(shí)就會(huì)到指定控件(TextView)中去找對(duì)應(yīng)的 setRotation()方法來改變控件中對(duì)應(yīng)的值。同樣的道理,當(dāng)我們?cè)谧铋_始的示例代碼中,指定改變”alpha”屬性值的時(shí)候,ObjectAnimator 也會(huì)到 TextView 中去找對(duì)應(yīng)的 setAlpha()方法。那 TextView 中都有這些方法嗎,有的,這些方法都是從 View 中繼承過來的,在 View 中有關(guān)動(dòng)畫,總共有下面幾組 set 方法:
//1、透明度:alpha
public void setAlpha(float alpha)
//2、旋轉(zhuǎn)度數(shù):rotation、rotationX、rotationY
public void setRotation(float rotation)
public void setRotationX(float rotationX)
public void setRotationY(float rotationY)
//3、平移:translationX、translationY
public void setTranslationX(float translationX)
public void setTranslationY(float translationY)
//縮放:scaleX、scaleY
public void setScaleX(float scaleX)
public void setScaleY(float scaleY)
可以看到在 View 中已經(jīng)實(shí)現(xiàn)了有關(guān) alpha,rotaion,translate,scale 相關(guān)的 set 方法。所以我們?cè)跇?gòu)造 ObjectAnimator 時(shí)可以直接使用。 在開始逐個(gè)看這些函數(shù)的使用方法前,我們先做一個(gè)總結(jié): 1、要使用 ObjectAnimator 來構(gòu)造對(duì)畫,要操作的控件中,必須存在對(duì)應(yīng)的屬性的 set 方法 2、setter 方法的命名必須以駱駝拼寫法命名,即 set 后每個(gè)單詞首字母大寫,其余字母小寫,即類似于 setPropertyName 所對(duì)應(yīng)的屬性為 propertyName 下面我們就來看一下上面中各個(gè)方法的使用方法及作用。 有關(guān) alpha 的用法,上面已經(jīng)講過了,下面我們來看看其它的
(1)、setRotationX、setRotationY 與 setRotation
先來看看 setRotationX 的效果:
ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotationX",0,270,0);
animator.setDuration(2000);
animator.start();
效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/70.gif" alt="" />
從效果圖中明顯看出,textview 的旋轉(zhuǎn)方法是圍繞 X 軸旋轉(zhuǎn)的,我們?cè)O(shè)定為從 0 度旋轉(zhuǎn)到 270 度再返回 0 度。 然后再來看看 setRotationY 的使用方法與效果:
ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotationY",0,180,0);
animator.setDuration(2000);
animator.start();
效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/71.gif" alt="" />
從效果圖中明顯可以看出圍繞 Y 軸旋轉(zhuǎn)的。 我們?cè)賮砜纯?setRotation 的用法與效果:
ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotation",0,270,0);
animator.setDuration(2000);
animator.start();
http://wiki.jikexueyuan.com/project/android-animation/images/72.gif" alt="" />
我們上面說了,setRotation 是圍繞 Z 軸旋轉(zhuǎn)的,可能有些同學(xué)不理解什么是 Z 軸,我們來看一張圖:
http://wiki.jikexueyuan.com/project/android-animation/images/13.png" alt="" />
從這張圖中,綠色框部分表示手機(jī)屏幕,很明顯可以看出 Z 軸就是從屏幕左上角原點(diǎn)向外伸出的一條軸。這樣,我們也就可以理解圍繞 Z 軸旋轉(zhuǎn),為什么是這樣子轉(zhuǎn)了。
(2)、setTranslationX 與 setTranslationY
ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "translationX", 0, 200, -200,0);
animator.setDuration(2000);
animator.start();
效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/73.gif" alt="" />
所以,我們上面在構(gòu)造動(dòng)畫時(shí),指定的移動(dòng)距離是(0, 200, -200,0),所以控件會(huì)從自身所有位置向右移動(dòng) 200 像素,然后再移動(dòng)到距離原點(diǎn)-200 的位置,最后回到原點(diǎn); 然后我們來看看 setTranslateY 的用法:
ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "translationY", 0, 200, -100,0);
animator.setDuration(2000);
animator.start();
效果圖如下:(為了方便看到效果,將 textview 垂直居中)
http://wiki.jikexueyuan.com/project/android-animation/images/74.gif" alt="" />
同樣,移動(dòng)位置的坐標(biāo)也都是以當(dāng)前控件所在位置為中心點(diǎn)的。所以對(duì)應(yīng)的移動(dòng)位置從原點(diǎn)移動(dòng)向下移動(dòng) 200 像素,然后再移動(dòng)到向下到距原點(diǎn) 200 像素的位置,最后再回到(0,0)從效果圖中很明顯可以看出來。 從上面可以看出:每次移動(dòng)距離的計(jì)算都是以原點(diǎn)為中心的;比如初始動(dòng)畫為 ObjectAnimator.ofFloat(tv, “translationY”, 0, 200, -100,0)表示首先從 0 移動(dòng)到正方向 200 的位置,然后再移動(dòng)到負(fù)方向 100 的位置,最后移動(dòng)到原點(diǎn)。
(3)、setScaleX 與 setScaleY
ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "scaleX", 0, 3, 1);
animator.setDuration(2000);
animator.start();
效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/75.gif" alt="" />
在效果圖中,從 0 倍放大到 3 倍,然后再還原到 1 倍的原始狀態(tài)。 然后再來看看 setScaleY 的用法
ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "scaleY", 0, 3, 1);
animator.setDuration(2000);
animator.start();
為了更好的看到效果,我把 textview 垂直居中了,效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/76.gif" alt="" />
源碼在文章底部給出 好了,到這里有關(guān) View 中自帶的 set 函數(shù)講完了,我們來看看 ObjectAnimator 是如何實(shí)現(xiàn)控件動(dòng)畫效果的。
我們先來看張圖:
http://wiki.jikexueyuan.com/project/android-animation/images/14.png" alt="" />
在這張圖中,將 ValueAnimator 的動(dòng)畫流程與 ObjectAnimator 的動(dòng)畫流程做了個(gè)對(duì)比。 可以看到 ObjectAnimator 的動(dòng)畫流程中,也是首先通過加速器產(chǎn)生當(dāng)前進(jìn)度的百分比,然后再經(jīng)過 Evaluator 生成對(duì)應(yīng)百分比所對(duì)應(yīng)的數(shù)字值。這兩步與 ValueAnimator 是完全一樣的,唯一不同的是最后一步,在 ValueAnimator 中,我們要通過添加監(jiān)聽器來監(jiān)聽當(dāng)前數(shù)字值。而在 ObjectAnimator 中,則是先根據(jù)屬性值拼裝成對(duì)應(yīng)的 set 函數(shù)的名字,比如這里的 scaleY 的拼裝方法就是將屬性的第一個(gè)字母強(qiáng)制大寫后,與 set 拼接,所以就是 setScaleY。然后通過反射找到對(duì)應(yīng)控件的 setScaleY(float scaleY)函數(shù),將當(dāng)前數(shù)字值做為 setScaleY(float scale)的參數(shù)將其傳入。 這里在找到控件的 set 函數(shù)以后,是通過反射來調(diào)用這個(gè)函數(shù)的,有關(guān)反射的使用大家可以參考《夯實(shí) JAVA 基本之二 —— 反射(1):基本類周邊信息獲取》 這就是 ObjectAnimator 的流程,最后一步總結(jié)起來就是調(diào)用對(duì)應(yīng)屬性的 set 方法,將動(dòng)畫當(dāng)前數(shù)字值做為參數(shù)傳進(jìn)去。
根據(jù)上面的流程,這里有幾個(gè)注意事項(xiàng): (1)、拼接 set 函數(shù)的方法:上面我們也說了是首先是強(qiáng)制將屬性的第一個(gè)字母大寫,然后與 set 拼接,就是對(duì)應(yīng)的 set 函數(shù)的名字。注意,只是強(qiáng)制將屬性的第一個(gè)字母大寫,后面的部分是保持不變的。反過來,如果我們的函數(shù)名命名為 setScalePointX(float ),那我們?cè)趯憣傩詴r(shí)可以寫成”scalePointX”或者寫成“ScalePointX”都是可以的,即第一個(gè)字母大小寫可以隨意,但后面的部分必須與 set 方法后的大小寫保持一致。 (2)、如何確定函數(shù)的參數(shù)類型:上面我們知道了如何找到對(duì)應(yīng)的函數(shù)名,那對(duì)應(yīng)的參數(shù)方法的參數(shù)類型如何確定呢?我們?cè)谥v ValueAnimator 的時(shí)候說過,動(dòng)畫過程中產(chǎn)生的數(shù)字值與構(gòu)造時(shí)傳入的值類型是一樣的。由于 ObjectAnimator 與 ValueAnimator 在插值器和 Evaluator 這兩步是完全一樣的,而當(dāng)前動(dòng)畫數(shù)值的產(chǎn)生是在 Evaluator 這一步產(chǎn)生的,所以 ObjectAnimator 的動(dòng)畫中產(chǎn)生的數(shù)值類型也是與構(gòu)造時(shí)的類型一樣的。那么問題來了,像我們的構(gòu)造方法。
ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "scaleY", 0, 3, 1);
由于構(gòu)造時(shí)使用的是 ofFloat 函數(shù),所以中間值的類型應(yīng)該是 Float 類型的,所以在最后一步拼裝出來的 set 函數(shù)應(yīng)該是 setScaleY(float xxx)的樣式;這時(shí),系統(tǒng)就會(huì)利用反射來找到 setScaleY(float xxx)函數(shù),并把當(dāng)前的動(dòng)畫數(shù)值做為參數(shù)傳進(jìn)去。 那問題來了,如果沒有類似 setScaleY(float xxx)的函數(shù),我們只實(shí)現(xiàn)了一個(gè) setScaleY(int xxx)的函數(shù)怎么辦?這里雖然函數(shù)名一樣,但參數(shù)類型是不一樣的,那么系統(tǒng)就會(huì)報(bào)一個(gè)錯(cuò)誤:
http://wiki.jikexueyuan.com/project/android-animation/images/15.png" alt="" />
意思就是對(duì)應(yīng)函數(shù)的指定參數(shù)類型沒有找到。 (3)、調(diào)用 set 函數(shù)以后怎么辦?從 ObjectAnimator 的流程可以看到,ObjectAnimator 只負(fù)責(zé)把動(dòng)畫過程中的數(shù)值傳到對(duì)應(yīng)屬性的 set 函數(shù)中就結(jié)束了,注意傳給 set 函數(shù)以后就結(jié)束了!set 函數(shù)就相當(dāng)我們?cè)?ValueAnimator 中添加的監(jiān)聽的作用,set 函數(shù)中的對(duì)控件的操作還是需要我們自己來寫的。
那我們來看看 View 中的 setScaleY 是怎么實(shí)現(xiàn)的吧:
/**
* Sets the amount that the view is scaled in Y around the pivot point, as a proportion of
* the view's unscaled width. A value of 1 means that no scaling is applied.
*
* @param scaleY The scaling factor.
* @see #getPivotX()
* @see #getPivotY()
*
* @attr ref android.R.styleable#View_scaleY
*/
public void setScaleY(float scaleY) {
ensureTransformationInfo();
final TransformationInfo info = mTransformationInfo;
if (info.mScaleY != scaleY) {
invalidateParentCaches();
// Double-invalidation is necessary to capture view's old and new areas
invalidate(false);
info.mScaleY = scaleY;
info.mMatrixDirty = true;
mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
invalidate(false);
}
}
大家不必理解這一坨代碼的意義,因?yàn)檫@些代碼是需要讀懂 View 的整體流程以后才能看得懂的,只需要跟著我的步驟來理解就行。這段代碼總共分為兩部分:第一步重新設(shè)置當(dāng)前控件的參數(shù),第二步調(diào)用 Invalidate()強(qiáng)制重繪; 所以在重繪時(shí),控件就會(huì)根據(jù)最新的控件參數(shù)來繪制了,所以我們就看到當(dāng)前控件被縮放了。 (4)、set 函數(shù)調(diào)用頻率是多少:由于我們知道動(dòng)畫在進(jìn)行時(shí),每隔十幾毫秒會(huì)刷新一次,所以我們的 set 函數(shù)也會(huì)每隔十幾毫秒會(huì)被調(diào)用一次。 講了這么多,就是為了強(qiáng)調(diào)一點(diǎn):ObjectAnimator 只負(fù)責(zé)把當(dāng)前運(yùn)動(dòng)動(dòng)畫的數(shù)值傳給 set 函數(shù)。至于 set 函數(shù)里面怎么來做,是我們自己的事了。 好了,在知道了 ObjectAnimator 的原理以后,下面就來看看如何自定義一個(gè) ObjectAnimator 的屬性吧。
上面我們已經(jīng)看了使用 View 自帶的 set 函數(shù)所對(duì)應(yīng)屬性的方法,而且理解了 ObjectAnimator 的動(dòng)畫實(shí)現(xiàn)原理,下面我們來自定義一個(gè)屬性來看看實(shí)現(xiàn)效果吧。 我們?cè)陂_始之前再來捋一下 ObjectAnimator 的動(dòng)畫設(shè)置流程:ObjectAnimator 需要指定操作的控件對(duì)象,在開始動(dòng)畫時(shí),到控件類中去尋找設(shè)置屬性所對(duì)應(yīng)的 set 函數(shù),然后把動(dòng)畫中間值做為參數(shù)傳給這個(gè) set 函數(shù)并執(zhí)行它。 所以,我們說了,控件類中必須所要設(shè)置屬性所要對(duì)應(yīng)的 set 函數(shù)。所以為了自由控制控件的實(shí)現(xiàn),我們這里自定義一個(gè)控件。大家知道在這個(gè)自定義控件中,肯定存在一個(gè) set 函數(shù)與我們自定義的屬性相對(duì)應(yīng)。 我們先來看看這段要實(shí)現(xiàn)的效果:
http://wiki.jikexueyuan.com/project/android-animation/images/77.gif" alt="" />
這個(gè)效果圖與我們上篇自定義控件實(shí)現(xiàn)的效果差不多,這個(gè)控件中存在一個(gè)圓形,也是在動(dòng)畫時(shí)先將這個(gè)圓形放大,然后再將圓形還原。
為了,保存圓形的信息,我們先定義一個(gè)類:(Point.java)
public class Point {
private int mRadius;
public Point(int radius){
mRadius = radius;
}
public int getRadius() {
return mRadius;
}
public void setRadius(int radius) {
mRadius = radius;
}
}
這個(gè)類很好理解,只有一個(gè)成員變量 mRadius,表示圓的半徑。
然后我們自定義一個(gè)控件 MyPointView,完整代碼如下:
public class MyPointView extends View {
private Point mPoint = new Point(100);
public MyPointView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
if (mPoint != null){
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(300,300,mPoint.getRadius(),paint);
}
super.onDraw(canvas);
}
void setPointRadius(int radius){
mPoint.setRadius(radius);
invalidate();
}
}
在這段代碼中,首先來看我們前面講到的 set 函數(shù):
void setPointRadius(int radius){
mPoint.setRadius(radius);
invalidate();
}
第一點(diǎn),這個(gè) set 函數(shù)所對(duì)應(yīng)的屬性應(yīng)該是 pointRadius 或者 PointRadius。前面我們已經(jīng)講了第一個(gè)字母大小寫無所謂,后面的字母必須保持與 set 函數(shù)完全一致。 第二點(diǎn),在 setPointRadius 中,先將當(dāng)前動(dòng)畫傳過來的值保存到 mPoint 中,做為當(dāng)前圓形的半徑。然后強(qiáng)制界面刷新 在界面刷新后,就開始執(zhí)行 onDraw()函數(shù):
@Override
protected void onDraw(Canvas canvas) {
if (mPoint != null){
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(300,300,mPoint.getRadius(),paint);
}
super.onDraw(canvas);
}
在 onDraw 函數(shù)中,就是根據(jù)當(dāng)前 mPoint 的半徑值在(300,300)點(diǎn)外畫一個(gè)圓;有關(guān)畫圓的知識(shí),大家可以參考《android Graphics(一):概述及基本幾何圖形繪制》
首先,在 MyActivity 的布局中添加 MyPointView 的使用(main.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:padding="10dp"
android:text="start anim"
/>
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:padding="10dp"
android:text="cancel anim"
/>
<TextView
android:id="@+id/tv"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center"
android:padding="10dp"
android:background="#ffff00"
android:text="Hello qijian"/>
<com.example.BlogObjectAnimator1.MyPointView
android:id="@+id/pointview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tv"/>
</RelativeLayout>
布局代碼很好理解,根據(jù)效果圖中的布局效果來理解,非常容易,就不再多講 然后看看在 MyActivity 中,點(diǎn)擊 start anim 后的處理方法:
public class MyActivity extends Activity {
private Button btnStart;
private MyPointView mPointView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnStart = (Button) findViewById(R.id.btn);
mPointView = (MyPointView)findViewById(R.id.pointview);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doPointViewAnimation();
}
});
}
…………
}
在點(diǎn)擊 start anim 按鈕后,開始執(zhí)行 doPointViewAnimation()函數(shù),doPointViewAnimation()函數(shù)代碼如下:
private void doPointViewAnimation(){
ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius", 0, 300, 100);
animator.setDuration(2000);
animator.start();
}
在這段代碼中,著重看 ObjectAnimator 的構(gòu)造方法,首先要操作的控件對(duì)象是 mPointView,然后對(duì)應(yīng)的屬性是 pointRadius,然后值是從 0 到 300 再到 100; 所以在動(dòng)畫開始以后,ObjectAnimator 就會(huì)實(shí)時(shí)地把動(dòng)畫中產(chǎn)生的值做為參數(shù)傳給 MyPointView 類中的 setPointRadius(int radius)函數(shù),然后調(diào)用 setPointRadius(int radius)。由于我們?cè)?setPointRadius(int radius)中實(shí)時(shí)地設(shè)置圓形的半徑值然后強(qiáng)制重繪當(dāng)前界面,所以可以看到圓形的半徑會(huì)隨著動(dòng)畫的進(jìn)行而改變。 源碼在文章底部給出
我們?cè)賮砜匆幌?ObjectAinimator 的下面三個(gè)構(gòu)造方法:
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
public static ObjectAnimator ofInt(Object target, String propertyName, int... values)
public static ObjectAnimator ofObject(Object target, String propertyName,TypeEvaluator evaluator, Object... values)
前面我們已經(jīng)分別講過三個(gè)函數(shù)的使用方法,在上面的三個(gè)構(gòu)造方法中最后一個(gè)參數(shù)都是可變長(zhǎng)參數(shù)。我們也講了,他們的意義就是從哪個(gè)值變到哪個(gè)值的。 那么問題來了:前面我們都是定義多個(gè)值,即至少兩個(gè)值之間的變化,那如果我們只定義一個(gè)值呢,如下面的方式:(同樣以 MyPointView 為例)
ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);
我們?cè)谶@里只傳遞了一個(gè)變化值 100;那它從哪里開始變化呢?我們來看一下效果: 代碼如下:
ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);
animator.setDuration(2000);
animator.start();
效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/78.gif" alt="" />
從效果圖中看起來是從 0 開始的,但是看 log 可以看出來已經(jīng)在出警告了:
http://wiki.jikexueyuan.com/project/android-animation/images/16.png" alt="" />
我們點(diǎn)了三次 start anim 按鈕,所以這里也報(bào)了三次,意思就是沒找到 pointRadius 屬性所對(duì)應(yīng)的 getPointRadius()函數(shù); 僅且僅當(dāng)我們只給動(dòng)畫設(shè)置一個(gè)值時(shí),程序才會(huì)調(diào)用屬性對(duì)應(yīng)的 get 函數(shù)來得到動(dòng)畫初始值。如果動(dòng)畫沒有初始值,那么就會(huì)使用系統(tǒng)默認(rèn)值。比如 ofInt()中使用的參數(shù)類型是 int 類型的,而系統(tǒng)的 Int 值的默認(rèn)值是 0,所以動(dòng)畫就會(huì)從 0 運(yùn)動(dòng)到 100;也就是系統(tǒng)雖然在找到不到屬性對(duì)應(yīng)的 get 函數(shù)時(shí),會(huì)給出警告,但同時(shí)會(huì)用系統(tǒng)默認(rèn)值做為動(dòng)畫初始值。 如果通過給自定義控件 MyPointView 設(shè)置了 get 函數(shù),那么將會(huì)以 get 函數(shù)的返回值做為初始值:
public class MyPointView extends View {
private Point mPoint = new Point(100);
public MyPointView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
if (mPoint != null){
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(300,300,mPoint.getRadius(),paint);
}
super.onDraw(canvas);
}
public int getPointRadius(){
return 50;
}
public void setPointRadius(int radius){
mPoint.setRadius(radius);
invalidate();
}
}
我們?cè)谶@里添加了 getPointRadius 函數(shù),返回值是 Int.有些同學(xué)可能會(huì)疑惑:我怎么知道這里要返回 int 值呢? 我們前面說過當(dāng)且僅當(dāng)我們?cè)趧?chuàng)建 ObjectAnimator 時(shí),只給他傳遞了一個(gè)過渡值的時(shí)候,系統(tǒng)才會(huì)調(diào)用屬性對(duì)應(yīng)的 get 函數(shù)來得到動(dòng)畫的初始值!所以做為動(dòng)畫的初始值,那么在創(chuàng)建動(dòng)畫時(shí)過渡值傳的什么類型,這里的 get 函數(shù)就要返回類型
public static ObjectAnimator ofObject(Object target, String propertyName,TypeEvaluator evaluator, Object... values)
比如上面的 ofObject,get 函數(shù)所返回的類型就是與最后一個(gè)參數(shù) Object... values,相同類型的。 在我們?cè)?MyPointView 添加上 PointRadius 所對(duì)應(yīng)的 get 函數(shù)以后重新執(zhí)行動(dòng)畫:
ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);
animator.setDuration(2000);
animator.start();
此時(shí)的效果圖如下:
http://wiki.jikexueyuan.com/project/android-animation/images/79.gif" alt="" />
從動(dòng)畫中可以看出,半徑已經(jīng)不是從 0 開始的了,而是從 50 開始的。 最后我們總結(jié)一下:當(dāng)且僅當(dāng)動(dòng)畫的只有一個(gè)過渡值時(shí),系統(tǒng)才會(huì)調(diào)用對(duì)應(yīng)屬性的 get 函數(shù)來得到動(dòng)畫的初始值。
源碼在文章底部給出
有關(guān)常用函數(shù)這一節(jié)其實(shí)沒有太多講的必要。因?yàn)?ObjectAnimator 的函數(shù)都是從 ValueAnimator 中繼承而來的,所以用法和效果與 ValueAnimator 是完全一樣的。我們這里只講解一下 Evaluator 的用法,其它的也就不再講了。
我們搜一下 TextView 所有的函數(shù)發(fā)現(xiàn),TextView 有一個(gè) set 函數(shù)能夠改變背景色:
public void setBackgroundColor(int color);
大家可以回想到,我們?cè)?ValueAnimator 中也曾改變過背景色,使用的是 ArgbEvaluator。在這里我們?cè)倩仡櫹?ArgbEvaluator,它的實(shí)現(xiàn)代碼如下:
public class ArgbEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24);
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24);
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}
}
有關(guān)它具體實(shí)現(xiàn)的原理,前面篇章中我們已經(jīng)講過了,這里主要說一點(diǎn),ArgbEvaluator 的返回值是 Integer 類型,所以我們要使用 ArgbEvaluator 的話,構(gòu)造 ObjectAnimator 時(shí)必須使用 ofInt() 下面我們來看看使用 ArgbEvaluator 的代碼:
ObjectAnimator animator = ObjectAnimator.ofInt(tv, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
animator.setDuration(8000);
animator.setEvaluator(new ArgbEvaluator());
animator.start();
然后我們來看下代碼效果:
http://wiki.jikexueyuan.com/project/android-animation/images/80.gif" alt="" />
源碼在文章底部給出
下面把其它所涉及到的函數(shù)的列表列在下面,大家可以參考 ValueAnimator 的使用方法來使用。有關(guān)自定義插值器和 Evaluator 的部分,可以參考《Animation 動(dòng)畫詳解(五)——高級(jí)進(jìn)階(一)》 (1)、常用函數(shù)
/**
* 設(shè)置動(dòng)畫時(shí)長(zhǎng),單位是毫秒
*/
ValueAnimator setDuration(long duration)
/**
* 獲取 ValueAnimator 在運(yùn)動(dòng)時(shí),當(dāng)前運(yùn)動(dòng)點(diǎn)的值
*/
Object getAnimatedValue();
/**
* 開始動(dòng)畫
*/
void start()
/**
* 設(shè)置循環(huán)次數(shù),設(shè)置為 INFINITE 表示無限循環(huán)
*/
void setRepeatCount(int value)
/**
* 設(shè)置循環(huán)模式
* value 取值有 RESTART,REVERSE,
*/
void setRepeatMode(int value)
/**
* 取消動(dòng)畫
*/
void cancel()
(2)、監(jiān)聽器相關(guān)
[java] view plain
/**
* 監(jiān)聽器一:監(jiān)聽動(dòng)畫變化時(shí)的實(shí)時(shí)值
*/
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
//添加方法為:public void addUpdateListener(AnimatorUpdateListener listener)
/**
* 監(jiān)聽器二:監(jiān)聽動(dòng)畫變化時(shí)四個(gè)狀態(tài)
*/
public static interface AnimatorListener {
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
}
//添加方法為:public void addListener(AnimatorListener listener)
(3)、插值器與 Evaluator
/**
* 設(shè)置插值器
*/
public void setInterpolator(TimeInterpolator value)
/**
* 設(shè)置 Evaluator
*/
public void setEvaluator(TypeEvaluator value)
到這里,有關(guān) ObjectAnimator 的知識(shí)就講完了,下篇再講講聯(lián)合動(dòng)畫和 xml 中實(shí)現(xiàn)動(dòng)畫的方法。
如果本文有幫到你,記得加關(guān)注哦 源碼下載地址:
csdn:http://download.csdn.net/detail/harvic880925/9445785
github:https://github.com/harvic/BlogResForGitHub
請(qǐng)大家尊重原創(chuàng)者版權(quán),轉(zhuǎn)載請(qǐng)標(biāo)明出處,謝謝