Android 圖片彈跳動畫
這幾天看到一個小動畫,覺得有點意思,就自己實現來看看,先看效果圖
OK,這個效果基本功能就是,一個圖片,從頂部掉下來,完後彈幾下,再停止,實現起來還是比較簡單的,不過也走了點小彎路,這裡記錄下。
有段時間做自定義控制元件比較多,有點中毒了,看到任何效果第一個先想到自定義控制元件,所以一開始我是用自定義控制元件巢狀自己用動畫計算距離來實現,後來發現沒必要,但基本思路是一致的,這裡先看看自定義控制元件巢狀動畫如何實現。
首先自定義一個ViewGroup, 看一下里面用到的幾個變數
都有註釋,主要注意下次數,其中圖片落下或彈起,都算一次動畫private int mWidth; // 螢幕寬度 private int mHeight; // 螢幕高度 private boolean mFirst = true; // 是否第一次執行 private int mTotalRound; // 總次數 private int mCurRound; // 當前次數 private long mTime; // 動畫時間
完後實現一個加入圖片的方法
private void init() {
ImageView view = new ImageView(mContext);
view.setBackgroundResource(R.mipmap.jump);
view.setScaleType(ImageView.ScaleType.FIT_XY);
addView(view);
}
這沒什麼好說,就是定義一個ImageView,設定圖片,全屏,完後將ImageView加入ViewGroup。
接下來,實現onMeasure方法,獲取螢幕寬高,這個在後面動畫中有用
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mWidth = MeasureSpec.getSize(widthMeasureSpec); //獲取ViewGroup寬度 mHeight = MeasureSpec.getSize(heightMeasureSpec); //獲取ViewGroup高度 setMeasuredDimension(mWidth, mHeight); //設定ViewGroup的寬高 if (mFirst) { mFirst = false; beginAnimation(); } }
這裡mFirst是一個布林變數,初始為true,這裡就是動畫只執行一次,不管進入onMeasure方法多少次,beginAnimation就是我們要進行的動畫。
實現動畫前,我們先來仔細觀察一下這個動畫,這個動畫看著挺簡單,但是有幾個細節還是要注意:
1. 動畫彈起的高度越來越小,我這裡是第一次彈起螢幕的高度的1/2,第二次彈起1/4,第三次彈起1/8,以此類推
2. 我們將圖片的一次落下或彈起看成一次動畫,動畫的時間越來越短,假設第一次落下動畫需要1秒,那第一次彈起就需要1/2秒,第二次落下也是1/2秒,第二次彈起則需要1/4秒,以此類推
3. 下落的時候,速度越來越快,彈起的時候,速度越來越慢
瞭解了這些細節後,我就可以用layout方法來動態改變ImageView的位置,從而實現動畫的效果,layout方法很簡單
public void layout(int l, int t, int r, int b)
傳入left, top, right, bottom的值來絕對位置,left和right我們不用關注,因為圖片是上下移動,所以left恆為0,而right恆為螢幕的寬度,而top和bottom是實時變化的,按順序看下top和bottom的值變化,假設螢幕高度為mHeight
第一次動畫,圖片從螢幕的頂部下落到螢幕的底部,top的變化為從-mHeight到0,bottom的變化從0到mHeight
第二次動畫,圖片從螢幕的底部彈起到圖片的底部正好在螢幕高度的1/2處,top的變化從0到-mHeight/2,bottom的變化從mHeight到mHeight/2
第三次動畫,圖片底部從螢幕高度的1/2處下落到螢幕的底部,top的變化從-mHeight/2到0,bottom的變化從mHeight/2到mHeight
以此類推,後面的動畫都是類似的,這些都清楚後,直接來看動畫如何實現
private void beginAnimation() {
final int height;
if (mCurRound % 2 == 0) { // 向下
height = (int) (-mHeight * Math.pow(0.5, mCurRound / 2));
} else { // 向上
height = (int) (-mHeight * Math.pow(0.5, (mCurRound + 1) / 2));
}
Animation translateAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (mCurRound % 2 == 0) {
getChildAt(0).layout(0, (int) (height * (1 - interpolatedTime)), mWidth, mHeight + height - (int) (interpolatedTime * height));
} else {
getChildAt(0).layout(0, (int) (height * interpolatedTime), mWidth, mHeight + (int) (height * interpolatedTime));
}
}
};
if (mCurRound % 2 == 0) {
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, mCurRound / 2)));
} else {
translateAnimation.setInterpolator(new DecelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, (mCurRound + 1) / 2)));
}
startAnimation(translateAnimation);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mCurRound++;
if (mCurRound < mTotalRound) {
beginAnimation();
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
mCurRound就是當前動畫的次數,從0開始計數,取模2為0,也就是偶數,也就是下落,取模2為1,也就是奇數,也就是彈起,後面的applyTransformation方法,有一個引數interpolatedTime,在動畫執行的過程中,會不斷呼叫applyTransformation這個方法,而interpolatedTime的值從0變化1,我們可以根據這個值,來動態計算當前的高度,而計算方法參考上面的每次動畫的top和bottom的值變化範圍,自己在紙上畫畫就知道了。後面的if (mCurRound % 2 == 0) {
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, mCurRound / 2)));
} else {
translateAnimation.setInterpolator(new DecelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, (mCurRound + 1) / 2)));
}
就是之前分析的,動畫下落和彈起的速率變化不一樣,動畫時間也越來越短。
最後就是在動畫執行結束的時候,判斷下當前動畫執行了多少個了,如果沒執行完就繼續執行下一個動畫,這裡是一個遞迴的呼叫。程式碼實現起來還是蠻簡單的,關鍵是前面的分析過程,和細節的注意,下面貼出完整程式碼
public class JumpActivity extends Activity {
private int totalRound = 10;
private int time = 2000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new BounceView(this, totalRound, time));
}
}
public class BounceView extends ViewGroup {
private Context mContext;
private int mWidth; // 螢幕寬度
private int mHeight; // 螢幕高度
private boolean mFirst = true; // 是否第一次執行
private int mTotalRound; // 總次數
private int mCurRound; // 當前次數
private long mTime; // 動畫時間
public BounceView(Context context) {
super(context);
init();
}
public BounceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BounceView(Context context, int roundNum, long time) {
super(context);
mContext = context;
mTime = time;
mTotalRound = roundNum;
init();
}
private void init() {
ImageView view = new ImageView(mContext);
view.setBackgroundResource(R.mipmap.jump);
view.setScaleType(ImageView.ScaleType.FIT_XY);
addView(view);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec); //獲取ViewGroup寬度
mHeight = MeasureSpec.getSize(heightMeasureSpec); //獲取ViewGroup高度
setMeasuredDimension(mWidth, mHeight); //設定ViewGroup的寬高
if (mFirst) {
mFirst = false;
beginAnimation();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
private void beginAnimation() {
final int height;
if (mCurRound % 2 == 0) { // 向下
height = (int) (-mHeight * Math.pow(0.5, mCurRound / 2));
} else { // 向上
height = (int) (-mHeight * Math.pow(0.5, (mCurRound + 1) / 2));
}
Animation translateAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (mCurRound % 2 == 0) {
getChildAt(0).layout(0, (int) (height * (1 - interpolatedTime)), mWidth, mHeight + height - (int) (interpolatedTime * height));
} else {
getChildAt(0).layout(0, (int) (height * interpolatedTime), mWidth, mHeight + (int) (height * interpolatedTime));
}
}
};
if (mCurRound % 2 == 0) {
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, mCurRound / 2)));
} else {
translateAnimation.setInterpolator(new DecelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, (mCurRound + 1) / 2)));
}
startAnimation(translateAnimation);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mCurRound++;
if (mCurRound < mTotalRound) {
beginAnimation();
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
}
這是第一種方法,我們自定義了控制元件,而且還在applyTransformation中做了計算,後來想了下,其實沒必要,不使用自定義控制元件,不自己去計算,也可以實現,思路其實差不多,唯一的區別就是我們可以使用屬性動畫,直接用ObjectAnimator的ofFloat方法來移動圖片就行了,這裡因為思路都是一致的,我就直接貼程式碼了:
先宣告一個佈局檔案jump_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/move"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/jump"
android:scaleType="fitXY"
/>
</LinearLayout>
完後宣告Java類JumpActivity.java:public class JumpActivity extends Activity {
private ImageView view;
private int mHeight;
private int totalRound = 10;
private int curRound = 0;
private int time = 2000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(new BounceView(this, totalRound, time));
setContentView(R.layout.jump_layout);
view = (ImageView) findViewById(R.id.move);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
Rect outRect = new Rect();
getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(outRect);
mHeight = outRect.height();
beginTransAnimation();
}
}
private void beginTransAnimation() {
final int height;
if (curRound % 2 == 0) { // 向下
height = (int) (-mHeight * Math.pow(0.5, curRound / 2));
} else { // 向上
height = (int) (-mHeight * Math.pow(0.5, (curRound + 1) / 2));
}
float from = 0;
float to = 0;
if (curRound % 2 == 0) {
from = height;
to = 0;
} else {
from = 0;
to = height;
}
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", from, to);
animator.setInterpolator(new AccelerateInterpolator());
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
curRound++;
if (curRound <= totalRound) {
beginTransAnimation();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
if (curRound % 2 == 0) {
animator.setInterpolator(new AccelerateInterpolator());
animator.setDuration((long) (time * Math.pow(0.5, curRound / 2))).start();
} else {
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration((long) (time * Math.pow(0.5, (curRound + 1) / 2))).start();
}
}
}