標籤:
這幾天看到一個小動畫,覺得有點意思,就自己實現來看看,先看
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(); } }}
源碼下載
Android 圖片彈跳動畫