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();
        }
    }
}


源碼下載


PS:第三種實現方式可見Android 圖片彈跳動畫2




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章