編輯:關於android開發
下面就開始學習屬性動畫的基本用法,我們來看屬性動畫的繼承關系,如下如所示:
顯然關注的焦點應該是ValueAnimator,ObjectAnimator這兩個類啦,ObjectAnimator繼承自ValueAnimator,是屬性動畫中非常重要的一個實現類,通過ObjectAnimator類的靜態歐工廠方法來創建ObjectAnimator對象,這些靜態工廠方法包括:ObjectAnimator.ofFloat(),ObjectAnimator.ofInt()等等,當然最為重要的一個靜態工廠方法是ObjectAnimator.ofObject(),可以接收一個Object對象並為其設置屬性動畫,瞬間高大上了有木有?這些靜態工廠方法接收的參數分別是:
要設置動畫的目標對象;
動畫的屬性類型;
一個或多個屬性值;當只指定一個屬性值,系統默認此值為結束值;當指定兩個屬性值,系統默認分別為起始值和結束值;當指定三個或三個以上時,系統默認線性插值;
ValueAnimator是整個屬性動畫機制當中最核心的一個類,前面我們已經提到了,屬性動畫的運行機制是通過不斷地對值進行操作來實現的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的,ValueAnimator對過渡動畫值的計算依靠一個時間因子fraction,而這個時間因子fraction是系統由setDuration()方法設置的動畫執行時間通過計算得來的,所以ValueAnimator還負責管理動畫的持續時間、播放次數、播放模式、以及對動畫設置監聽器等,確實是一個非常重要的類。
相關API
Property Animation故名思議就是通過動畫的方式改變對象的屬性了,我們首先需要了解幾個屬性:
Duration動畫的持續時間,默認300ms。
Time interpolation:時間差值,乍一看不知道是什麼,但是我說LinearInterpolator、AccelerateDecelerateInterpolator,大家一定知道是干嘛的了,定義動畫的變化率。
Repeat count and behavior:重復次數、以及重復模式;可以定義重復多少次;重復時從頭開始,還是反向。
Animator sets: 動畫集合,你可以定義一組動畫,一起執行或者順序執行。
Frame refresh delay:幀刷新延遲,對於你的動畫,多久刷新一次幀;默認為10ms,但最終依賴系統的當前狀態;基本不用管。
相關的類
ObjectAnimator 動畫的執行類,後面詳細介紹
ValueAnimator 動畫的執行類,後面詳細介紹
AnimatorSet 用於控制一組動畫的執行:線性,一起,每個動畫的先後執行等。
AnimatorInflater 用戶加載屬性動畫的xml文件
TypeEvaluator 類型估值,主要用於設置動畫操作屬性的值。
TimeInterpolator 時間插值,上面已經介紹。
總的來說,屬性動畫就是,動畫的執行類來設置動畫操作的對象的屬性、持續時間,開始和結束的屬性值,時間差值等,然後系統會根據設置的參數動態的變化對象的屬性。
ValueAnimator
ValueAnimator是整個屬性動畫機制當中最核心的一個類,屬性動畫的運行機制是通過不斷地對值進行操作來實現的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的。它的內部使用一種時間循環的機制來計算值與值之間的動畫過渡,我們只需要將初始值和結束值提供給ValueAnimator,並且告訴它動畫所需運行的時長,那麼ValueAnimator就會自動幫我們完成從初始值平滑地過渡到結束值這樣的效果。除此之外,ValueAnimator還負責管理動畫的播放次數、播放模式、以及對動畫設置監聽器等,確實是一個非常重要的類。
但是ValueAnimator的用法卻一點都不復雜,我們先從最簡單的功能看起吧,比如說想要將一個值從0平滑過渡到1,時長300毫秒,就可以這樣寫
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.start();
很簡單吧,調用ValueAnimator的ofFloat()方法就可以構建出一個ValueAnimator的實例,ofFloat()方法當中允許傳入多個float類型的參數,這裡傳入0和1就表示將值從0平滑過渡到1,然後調用ValueAnimator的setDuration()方法來設置動畫運行的時長,最後調用start()方法啟動動畫。
用法就是這麼簡單,現在如果你運行一下上面的代碼,動畫就會執行了。可是這只是一個將值從0過渡到1的動畫,又看不到任何界面效果,我們怎樣才能知道這個動畫是不是已經真正運行了呢?這就需要借助監聽器來實現了,如下所示:
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.d("TAG", "cuurent value is " + currentValue);
}
});
anim.start()
ObjectAnimator
相比於ValueAnimator,ObjectAnimator可能才是我們最常接觸到的類,因為ValueAnimator只不過是對值進行了一個平滑的動畫過渡,但我們實際使用到這種功能的場景好像並不多。而ObjectAnimator則就不同了,它是可以直接對任意對象的任意屬性進行動畫操作的;
下面舉幾個例子(csdn上面的例子)
一個動畫能夠讓View既可以縮小、又能夠淡出(3個屬性scaleX,scaleY,alpha),只使用ObjectAnimator咋弄?
public void rotateyAnimRun(final View view)
{
ObjectAnimator anim = ObjectAnimator//
.ofFloat(view, "zhy", 1.0F, 0.0F)//
.setDuration(500);//
anim.start();
anim.addUpdateListener(new AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
float cVal = (Float) animation.getAnimatedValue();
view.setAlpha(cVal);
view.setScaleX(cVal);
view.setScaleY(cVal);
}
});
}
其實還有更簡單的方式,實現一個動畫更改多個效果:使用propertyValuesHolder
public void propertyValuesHolder(View view)
{
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f,
0f, 1f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f,
0, 1f);
PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f,
0, 1f);
ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY,pvhZ).setDuration(1000).start();
}
//AnimatorSet
ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
vcS/x7DOqta5o6xPYmplY3RBbmltYXRvcrXE08O3qLu5y+PKx8/gtbG88rWlsMmjrLWrysfO0s/g0MW/z7aou+HT0LK7ydnF89PRz9bU2tDEwO+2vNPQzazR+dK7uPbSyc7Ko6y+zcrHb2ZGbG9hdCgpt723qLXEtdq2/rj2ss7K/bW9tde/ydLUtKvExNCp1rXE2KO/xL/HsM7Sw8fKudPDuf3By2FscGhhoaJyb3RhdGlvbqGidHJhbnNsYXRpb25Yus1zY2FsZVnV4ry4uPbWtaOst9ax8L/J0tTN6rPJta3I67Wts/ahotD916qhosuuxr3SxravoaK0udaxy/W3xdXivLjW1ravu62jrMTHw7S7udPQxMTQqda1yse/ydLUyrnTw7XExNijv8bkyrXV4rj2zsrM4rXEtPCwuLfHs6PQ/rr1o6y+zcrHztLDx7/J0tS0q8jryM7S4rXE1rW1vW9mRmxvYXQoKbe9t6i1xLXatv649rLOyv21sdbQoaPIztLitcTWtaO/z+DQxdXiutyz9rr1tPO80rXE0uLBz7DJo6y1q8rCyrW+zcrHyOe0y6Gj0vLOqk9iamVjdEFuaW1hdG9y1NrJ6LzGtcTKsbryvs3Du9PQ1eu21NPaVmlld8C0vfjQ0MnovMajrLb4ysfV67bU09rIztLittTP87XEo6zL/Mv5uLrU8LXEuaTX977Nyseyu7bPtdjP8sSzuPa21M/z1tC1xMSzuPbK9NDUvfjQ0Liz1rWjrMi7uvO21M/zuPm+3cr00NTWtbXEuMSx5NTZwLS+9raoyOe6ztW5z9az9sC0oaM8YnIgLz4NCsTHw7SxyMjny7XO0sPHtffTw8/Cw+bV4tH50ru2zrT6wuujujwvcD4NCjxwcmUgY2xhc3M9"brush:java;">
ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);
其實這段代碼的意思就是ObjectAnimator會幫我們不斷地改變textview對象中alpha屬性的值,從1f變化到0f。然後textview對象需要根據alpha屬性值的改變來不斷刷新界面的顯示,從而讓用戶可以看出淡入淡出的動畫效果。
那麼textview對象中是不是有alpha屬性這個值呢?沒有,不僅textview沒有這個屬性,連它所有的父類也是沒有這個屬性的!這就奇怪了,textview當中並沒有alpha這個屬性,ObjectAnimator是如何進行操作的呢?其實ObjectAnimator內部的工作機制並不是直接對我們傳入的屬性名進行操作的,而是會去尋找這個屬性名對應的get和set方法,因此alpha屬性所對應的get和set方法應該就是:
public void setAlpha(float value);
public float getAlpha();
那麼textview對象中是否有這兩個方法呢?確實有,並且這兩個方法是由View對象提供的,也就是說不僅TextView可以使用這個屬性來進行淡入淡出動畫操作,任何繼承自View的對象都可以的。
既然alpha是這個樣子,相信大家一定已經明白了,前面我們所用的所有屬性都是這個工作原理,那麼View當中一定也存在著setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()這些方法,不信的話你可以到View當中去找一下。
桌面彈球和Win10開機小圓點旋轉動畫的實例探究
布局文件先定義了4個小圓點ImageView,把每個小圓點ImageView都放在了一個LinearLayout中,這很簡單!說到繪制小圓點,我比較推薦的一種做法是在res/drawable目錄下直接通過xml定義shape資源文件,這樣定義的好處是可以避免使用圖片資源造成不必要的內存占用。這裡我把我的小圓點定義代碼貼一下:
際上我們定義的只是一個橢圓,要顯示出小圓點我們需要指定它的寬高相等,即android:layout_width和android:layout_height的值要相等,否則就會顯示橢圓。然後只需要像引用圖片資源一樣,在drawable目錄下引用它就好,比如:
下面是完整的布局文件,僅供參考:
接著把CircleProgress屬性動畫類的代碼貼出來,並在CircleProgress屬性動畫類中拿到上面4個小圓點的對象
package com.wondertwo.propertyanime;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Button;
import android.widget.LinearLayout;
/**
* ObjectAnimator高級實例探究
* Created by wondertwo on 2016/3/22.
*/
public class CircleProgress extends Activity {
private LinearLayout mPoint_1;
private LinearLayout mPoint_2;
private LinearLayout mPoint_3;
private LinearLayout mPoint_4;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_circle_progress);
mPoint_1 = (LinearLayout) findViewById(R.id.ll_point_circle_1);
mPoint_2 = (LinearLayout) findViewById(R.id.ll_point_circle_2);
mPoint_3 = (LinearLayout) findViewById(R.id.ll_point_circle_3);
mPoint_4 = (LinearLayout) findViewById(R.id.ll_point_circle_4);
Button startAni = (Button) findViewById(R.id.start_ani_2);
startAni.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
beginPropertyAni();
}
});
}
/**
* 開啟動畫
*/
private void beginPropertyAni() {
ObjectAnimator animator_1 = ObjectAnimator.ofFloat(
mPoint_1,
"rotation",
0,
360);
animator_1.setDuration(2000);
animator_1.setInterpolator(new AccelerateDecelerateInterpolator());
ObjectAnimator animator_2 = ObjectAnimator.ofFloat(
mPoint_2,
"rotation",
0,
360);
animator_2.setStartDelay(150);
animator_2.setDuration(2000 + 150);
animator_2.setInterpolator(new AccelerateDecelerateInterpolator());
ObjectAnimator animator_3 = ObjectAnimator.ofFloat(
mPoint_3,
"rotation",
0,
360);
animator_3.setStartDelay(2 * 150);
animator_3.setDuration(2000 + 2 * 150);
animator_3.setInterpolator(new AccelerateDecelerateInterpolator());
ObjectAnimator animator_4 = ObjectAnimator.ofFloat(
mPoint_4,
"rotation",
0,
360);
animator_4.setStartDelay(3 * 150);
animator_4.setDuration(2000 + 3 * 150);
animator_4.setInterpolator(new AccelerateDecelerateInterpolator());
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(animator_1).with(animator_2).with(animator_3).with(animator_4);
animatorSet.start();
}
}
代碼確實不長只有80多行,但是麻雀雖小五髒俱全,很顯然beginPropertyAni()方法就是啟動動畫的方法,調用ObjectAnimator.ofFloat()靜態工廠方法創建ObjectAnimator對象我就不解釋了,很容易看懂!重點來了,Win10開機小圓點旋轉動畫的難點不在旋轉,如果我們把旋轉的最高點看作是旋轉的起始點,小圓點的旋轉是一個先加速後減速的過程,這恰好符合高中物理的規律,小球內切圓環軌道做圓周運動,不知道我這樣解釋是不是很形象呢?那麼控制旋轉的加速度很好辦,只要設置一個AccelerateDecelerateInterpolator()插值器就OK,但是我們發現,這不是一個小球在旋轉,而是有4個同時在旋轉,而且旋轉還不同步,這又該如何解決呢?你只要從第二個小球開始,每個小球設置固定時間間隔的延時啟動,就能完美解決上面的問題。代碼是這樣的:
animator_2.setStartDelay(150);
animator_3.setStartDelay(2 * 150);
animator_4.setStartDelay(3 * 150);
在動畫中可以清晰的看到小球下落過程中的加速運動,碰到桌面(手機屏幕的底部)後的變形壓扁,以及小球彈起的動畫,非常形象生動!先貼代碼後面再做分析:
package com.wondertwo.propertyanime;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import java.util.ArrayList;
/**
* 小球下落動畫加強版XBallsFallActivity,增加了小球桌底時的壓扁、回彈動畫
* Created by wondertwo on 2016/3/20.
*/
public class XBallsFallActivity extends Activity {
static final float BALL_SIZE = 50f;// 小球直徑
static final float FULL_TIME = 1000;// 下落時間
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_x_ball_fall);
LinearLayout xContainer = (LinearLayout) findViewById(R.id.xcontainer);
// 設置要顯示的view組件
xContainer.addView(new XBallView(this));
}
/**
* 自定義動畫組件XBallView
*/
public class XBallView extends View implements ValueAnimator.AnimatorUpdateListener {
public final ArrayList balls = new ArrayList<>();// 創建balls集合來存儲XShapeHolder對象
public XBallView(Context context) {
super(context);
setBackgroundColor(Color.WHITE);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 屏蔽ACTION_UP事件
if (event.getAction() != MotionEvent.ACTION_DOWN && event.getAction() != MotionEvent.ACTION_MOVE) {
return false;
}
// 在ACTION_DOWN事件發生點生成小球
XShapeHolder newBall = addBall(event.getX(), event.getY());
// 計算小球下落動畫開始時Y坐標
float startY = newBall.getY();
// 計算小球下落動畫結束時的Y坐標,即屏幕高度減去startY
float endY = getHeight() - BALL_SIZE;
// 獲取屏幕高度
float h = (float) getHeight();
float eventY = event.getY();
// 計算動畫持續時間
int duration = (int) (FULL_TIME * ((h - eventY) / h));
/**
* 下面開始定義小球的下落,著地壓扁,反彈等屬性動畫
*/
// 定義小球下落動畫
ValueAnimator fallAni = ObjectAnimator.ofFloat(
newBall,
"y",
startY,
endY);
// 設置動畫持續時間
fallAni.setDuration(duration);
// 設置加速插值器
fallAni.setInterpolator(new AccelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
fallAni.addUpdateListener(this);
// 定義小球壓扁動畫,控制小球x坐標左移半個球寬度
ValueAnimator squashshAni1 = ObjectAnimator.ofFloat(
newBall,
"x",
newBall.getX(),
newBall.getX() - BALL_SIZE / 2);
squashshAni1.setDuration(duration / 4);
squashshAni1.setRepeatCount(1);
squashshAni1.setRepeatMode(ValueAnimator.REVERSE);
squashshAni1.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
squashshAni1.addUpdateListener(this);
// 定義小球壓扁動畫,控制小球寬度加倍
ValueAnimator squashshAni2 = ObjectAnimator.ofFloat(
newBall,
"width",
newBall.getWidth(),
newBall.getWidth() + BALL_SIZE);
squashshAni2.setDuration(duration / 4);
squashshAni2.setRepeatCount(1);
squashshAni2.setRepeatMode(ValueAnimator.REVERSE);
squashshAni2.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
squashshAni2.addUpdateListener(this);
// 定義小球拉伸動畫, 控制小球的y坐標下移半個球高度
ValueAnimator stretchAni1 = ObjectAnimator.ofFloat(
newBall,
"y",
endY,
endY + BALL_SIZE / 2);
stretchAni1.setDuration(duration / 4);
stretchAni1.setRepeatCount(1);
stretchAni1.setRepeatMode(ValueAnimator.REVERSE);
stretchAni1.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
stretchAni1.addUpdateListener(this);
// 定義小球拉伸動畫, 控制小球的高度減半
ValueAnimator stretchAni2 = ObjectAnimator.ofFloat(
newBall,
"height",
newBall.getHeight(),
newBall.getHeight() - BALL_SIZE / 2);
stretchAni2.setDuration(duration / 4);
stretchAni2.setRepeatCount(1);
stretchAni2.setRepeatMode(ValueAnimator.REVERSE);
stretchAni2.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
stretchAni2.addUpdateListener(this);
// 定義小球彈起動畫
ValueAnimator bounceAni = ObjectAnimator.ofFloat(
newBall,
"y",
endY,
startY);
bounceAni.setDuration(duration);
bounceAni.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
bounceAni.addUpdateListener(this);
// 定義AnimatorSet,按順序播放[下落、壓扁&拉伸、彈起]動畫
AnimatorSet set = new AnimatorSet();
//在squashshAni1之前播放fallAni
set.play(fallAni).before(squashshAni1);
/**
* 由於小球彈起時壓扁,即寬度加倍,x坐標左移,高度減半,y坐標下移
* 因此播放squashshAni1的同時還要播放squashshAni2,stretchAni1,stretchAni2
*/
set.play(squashshAni1).with(squashshAni2);
set.play(squashshAni1).with(stretchAni1);
set.play(squashshAni1).with(stretchAni2);
// 在stretchAni2之後播放bounceAni
set.play(bounceAni).after(stretchAni2);
// newBall對象的漸隱動畫,設置alpha屬性值1--->0
ObjectAnimator fadeAni = ObjectAnimator.ofFloat(
newBall,
"alpha",
1f,
0f);
// 設置動畫持續時間
fadeAni.setDuration(250);
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
fadeAni.addUpdateListener(this);
// 為fadeAni設置監聽
fadeAni.addListener(new AnimatorListenerAdapter() {
// 動畫結束
@Override
public void onAnimationEnd(Animator animation) {
// 動畫結束時將該動畫關聯的ShapeHolder刪除
balls.remove(((ObjectAnimator) (animation)).getTarget());
}
});
// 再次定義一個AnimatorSet動畫集合,來組合動畫
AnimatorSet aniSet = new AnimatorSet();
// 指定在fadeAni之前播放set動畫集合
aniSet.play(set).before(fadeAni);
// 開始播放動畫
aniSet.start();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
for (XShapeHolder xShapeHolder : balls) {
canvas.save();
canvas.translate(xShapeHolder.getX(), xShapeHolder.getY());
xShapeHolder.getShape().draw(canvas);
canvas.restore();
}
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 指定重繪界面
this.invalidate();
}
/**
* addBall()方法返回XShapeHolder對象,ShapeHolder對象持有小球
*/
private XShapeHolder addBall(float x, float y) {
// 創建一個橢圓
OvalShape circle = new OvalShape();
// 設置橢圓寬高
circle.resize(BALL_SIZE, BALL_SIZE);
// 把橢圓包裝成Drawable對象
ShapeDrawable drawble = new ShapeDrawable(circle);
// 創建XShapeHolder對象
XShapeHolder holder = new XShapeHolder(drawble);
// 設置holder坐標
holder.setX(x - BALL_SIZE / 2);
holder.setY(y - BALL_SIZE / 2);
// 生成隨機組合的ARGB顏色
int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue = (int) (Math.random() * 255);
// 把red,green,blue三個顏色隨機數組合成ARGB顏色
int color = 0xff000000 + red << 16 | green << 8 | blue;
// 把red,green,blue三個顏色隨機數除以4得到商值組合成ARGB顏色
int darkColor = 0xff000000 | red / 4 << 16 | green / 4 << 8 | blue / 4;
// 創建圓形漸變效果
RadialGradient gradient = new RadialGradient(
37.5f,
12.5f,
BALL_SIZE,
color,
darkColor,
Shader.TileMode.CLAMP);
// 獲取drawble關聯的畫筆
Paint paint = drawble.getPaint();
paint.setShader(gradient);
// 為XShapeHolder對象設置畫筆
holder.setPaint(paint);
balls.add(holder);
return holder;
}
}
}
這次的代碼挺長有260多行,如果把它拆分開來,你會覺得代碼還是原來的套路,還是很熟悉的有木有?我們首先來看,彈球動畫類XBallsFallActivity中的代碼分為兩塊,一是onCreate()方法,這是每個Activity都要重寫的方法,那我們在onCreate()方法中干了什麼呢?只干了一件事就是拿到LinearLayout布局的對象,並調用addBall()方法給它添加XBallView這個view對象,代碼是這樣的:
xContainer.addView(new XBallView(this));
那XBallView對象又是什麼鬼呢?一個自定義view組件,也就是實現我們小球的view組件,這也是我們這個動畫的難點所在,我們慢慢來分析,代碼定位到XBallView類,第一眼你會發現這個類不僅繼承了View類,而且還實現了ValueAnimator.AnimatorUpdateListener這樣一個接口,再仔細一看你又會發現,這個接口怎麼聽起來這麼耳熟呢?沒錯,這就是上面我們在上面第二部分[ValueAnimator和屬性動畫的監聽]中講過的AnimatorUpdateListener類!實現了這個接口就意味著可以在XBallView中直接調用addUpdateListener(this)方法對屬性動畫進行監聽,只需要傳入this即可!
那我們再繼續往下看看有沒有我們要找的定義屬性動畫的邏輯呢?果然有!XBallView類中一共定義了7個動畫和兩個AnimatorSet動畫集合,我把這段代碼摘錄出來
/**
* 下面開始定義小球的下落,著地壓扁,反彈等屬性動畫
*/
// 定義小球下落動畫
ValueAnimator fallAni = ObjectAnimator.ofFloat(
newBall,
"y",
startY,
endY);
// 設置動畫持續時間
fallAni.setDuration(duration);
// 設置加速插值器
fallAni.setInterpolator(new AccelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
fallAni.addUpdateListener(this);
// 定義小球壓扁動畫,控制小球x坐標左移半個球寬度
ValueAnimator squashshAni1 = ObjectAnimator.ofFloat(
newBall,
"x",
newBall.getX(),
newBall.getX() - BALL_SIZE / 2);
squashshAni1.setDuration(duration / 4);
squashshAni1.setRepeatCount(1);
squashshAni1.setRepeatMode(ValueAnimator.REVERSE);
squashshAni1.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
squashshAni1.addUpdateListener(this);
// 定義小球壓扁動畫,控制小球寬度加倍
ValueAnimator squashshAni2 = ObjectAnimator.ofFloat(
newBall,
"width",
newBall.getWidth(),
newBall.getWidth() + BALL_SIZE);
squashshAni2.setDuration(duration / 4);
squashshAni2.setRepeatCount(1);
squashshAni2.setRepeatMode(ValueAnimator.REVERSE);
squashshAni2.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
squashshAni2.addUpdateListener(this);
// 定義小球拉伸動畫, 控制小球的y坐標下移半個球高度
ValueAnimator stretchAni1 = ObjectAnimator.ofFloat(
newBall,
"y",
endY,
endY + BALL_SIZE / 2);
stretchAni1.setDuration(duration / 4);
stretchAni1.setRepeatCount(1);
stretchAni1.setRepeatMode(ValueAnimator.REVERSE);
stretchAni1.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
stretchAni1.addUpdateListener(this);
// 定義小球拉伸動畫, 控制小球的高度減半
ValueAnimator stretchAni2 = ObjectAnimator.ofFloat(
newBall,
"height",
newBall.getHeight(),
newBall.getHeight() - BALL_SIZE / 2);
stretchAni2.setDuration(duration / 4);
stretchAni2.setRepeatCount(1);
stretchAni2.setRepeatMode(ValueAnimator.REVERSE);
stretchAni2.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
stretchAni2.addUpdateListener(this);
// 定義小球彈起動畫
ValueAnimator bounceAni = ObjectAnimator.ofFloat(
newBall,
"y",
endY,
startY);
bounceAni.setDuration(duration);
bounceAni.setInterpolator(new DecelerateInterpolator());
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
bounceAni.addUpdateListener(this);
// 定義AnimatorSet,按順序播放[下落、壓扁&拉伸、彈起]動畫
AnimatorSet set = new AnimatorSet();
//在squashshAni1之前播放fallAni
set.play(fallAni).before(squashshAni1);
/**
* 由於小球彈起時壓扁,即寬度加倍,x坐標左移,高度減半,y坐標下移
* 因此播放squashshAni1的同時還要播放squashshAni2,stretchAni1,stretchAni2
*/
set.play(squashshAni1).with(squashshAni2);
set.play(squashshAni1).with(stretchAni1);
set.play(squashshAni1).with(stretchAni2);
// 在stretchAni2之後播放bounceAni
set.play(bounceAni).after(stretchAni2);
// newBall對象的漸隱動畫,設置alpha屬性值1--->0
ObjectAnimator fadeAni = ObjectAnimator.ofFloat(
newBall,
"alpha",
1f,
0f);
// 設置動畫持續時間
fadeAni.setDuration(250);
// 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
fadeAni.addUpdateListener(this);
// 為fadeAni設置監聽
fadeAni.addListener(new AnimatorListenerAdapter() {
// 動畫結束
@Override
public void onAnimationEnd(Animator animation) {
// 動畫結束時將該動畫關聯的ShapeHolder刪除
balls.remove(((ObjectAnimator) (animation)).getTarget());
}
});
// 再次定義一個AnimatorSet動畫集合,來組合動畫
AnimatorSet aniSet = new AnimatorSet();
// 指定在fadeAni之前播放set動畫集合
aniSet.play(set).before(fadeAni);
// 開始播放動畫
aniSet.start();
邏輯很簡單,動畫fallAni控制小球下落,動畫squashshAni1控制小球壓扁時小球x坐標左移半個球寬度,動畫squashshAni2控制小球壓扁時小球寬度加倍,動畫stretchAni1,控制小球拉伸動畫時小球的y坐標下移半個球高度,動畫stretchAni2控制小球水平拉伸時控制小球的高度減半,動畫bounceAni定義小球彈起動畫,接著用一個AnimatorSet動畫集合把這六個動畫先組裝起來,下落動畫fallAni之後是squashshAni1、squashshAni2、stretchAni1、stretchAni2這四個動畫同時播放,這也是小球落地瞬間的完美诠釋,再之後是小球彈起bounceAni。最後還有一個fadeAni漸隱動畫控制小球彈回起始高度後消失,接著再用一個AnimatorSet動畫集合把前面的那個動畫集合和第七個fadeAni漸隱動畫組裝起來,整個桌面彈球動畫就大功告成了!
需要注意的是,在addBall()方法中,返回的是一個XShapeHolder類型的對象,那麼XShapeHolder是什麼呢?XShapeHolder包裝了ShapeDrawable對象,並且為x,y,width,height,alpha等屬性提供了setter、getter方法,代碼如下:
package com.wondertwo.propertyanime;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;
/**
*
* Created by wondertwo on 2016/3/20.
*/
public class XShapeHolder {
private float x = 0, y = 0;
private ShapeDrawable shape;
private int color;
private RadialGradient gradient;
private float alpha = 1f;
private Paint paint;
public XShapeHolder(ShapeDrawable shape) {
this.shape = shape;
}
public float getWidth() {
return shape.getShape().getWidth();
}
public void setWidth(float width) {
Shape s = shape.getShape();
s.resize(width, s.getHeight());
}
public float getHeight() {
return shape.getShape().getHeight();
}
public void setHeight(float height) {
Shape s = shape.getShape();
s.resize(s.getWidth(), height);
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
public ShapeDrawable getShape() {
return shape;
}
public void setShape(ShapeDrawable shape) {
this.shape = shape;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public RadialGradient getGradient() {
return gradient;
}
public void setGradient(RadialGradient gradient) {
this.gradient = gradient;
}
public float getAlpha() {
return alpha;
}
public void setAlpha(float alpha) {
this.alpha = alpha;
}
public Paint getPaint() {
return paint;
}
public void setPaint(Paint paint) {
this.paint = paint;
}
}
OLTP應用之MySQL架構選型在我們下定決心將企業核心應用從企業級數據庫遷移到開源數據庫產品、使用本地磁盤代替共享存儲之前。我覺得我們必須要面對並回答以下幾個問題之後才
安卓性能調優工具簡介,安卓調優簡介Traceview Traceview是執行日志的圖形查看器。這些日志通過使用Debug類記錄。 Traceview可以幫助
Android一步一步帶你實現RecyclerView的拖拽和側滑刪除功能 先上效果圖: 本篇文章我們來學習一個開源項目Android-ItemTouchHelper
Activity之概覽屏幕(Overview Screen),activityoverview概覽屏幕 概覽屏幕(也稱為最新動態屏幕、最近任務列表或最近使用的應用)是
基於CoordinatorLayout實現向上滾動導航條ToolBar滾