Android动画下金币雨

  Android动画需求,从上方掉落下来一堆金币,然后弹弹弹,最后收起金币。大概的样子就是下面这张图了 o(* ̄︶ ̄*)o

   整体项目结构如下图所示:

金币雨是一个自定义的控件,需要使用时,直接在XML中引用即可。如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.bounce.MainActivity">

    <com.example.bounce.RPEarnCashEntranceView
        android:layout_width="match_parent"
        android:layout_height="2080dp"
        android:text="Hello World!" />
</RelativeLayout>

在MainActivity.java中直接setContentView()即可。

当然除了上面的静态引用,也可以在java文件中动态使用,在需要引入的时候创建就好了,此时不需要在XML中引用,直接在java中创建:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RelativeLayout mMainLy = (RelativeLayout) findViewById(R.id.main_ly);

        RPEarnCashEntranceView mCoinView = new RPEarnCashEntranceView(getApplicationContext());
        mMainLy.addView(mCoinView);
    }
}

    上面是使用自定义view的两种方式,动态创建可以自己控制创建时机,静态引用的话,无法控制创建时机。

    自定义view中,有几个关键方法,第一个addCoinRainView()

/**
     * 添加金币雨
     */
    private void addCoinRainView() {
        //金币之间的间距
        int unitSpace = (mScreenWidth - DimenUtils.dp2px(2 * 60) - 9 * mCoinRainWidth) / 10;
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mCoinRainWidth, mCoinRainWidth);
        params.leftMargin = unitSpace;
        for (int i = 0; i < COUNT_COINS_RAIN; i++) {
            ImageView imageView = new ImageView(getContext());
            imageView.setImageResource(R.drawable.main_permanent_noti_alert);
            imageView.setLayoutParams(params);
            imageView.setVisibility(View.INVISIBLE);
            vCoinsRain.addView(imageView);
            CoinRainModel coinRainModel = new CoinRainModel(imageView, (i + 1) * unitSpace + i * mCoinRainWidth);
            startCoinRainAnim(coinRainModel, i);
        }
    }

    private void startCoinRainAnim(CoinRainModel coinRainModel, int what) {
        Message message = Message.obtain();
        message.obj = coinRainModel;
        message.what = what;
        mIntervalTime += 20;
        animHandler.sendMessageDelayed(message, mIntervalTime);
    }

    这个方法利用handler,每间隔20毫秒,发送一个i的消息,i代表九个金币,从0--8个数字,也正是消息的message.what字段,后面的handler的handleMessage()方法根据发送过去的消息,进行金币雨动画的开始节点,利用时间差营造出一个一个下落的效果。

    在处理消息的handler中,他的执行顺序就是刚刚每间隔20毫秒发送消息的时间顺序,具体执行是按照,0-1-2-3-4-5-6-7-8的消息顺序执行,在代码注释里可以很清楚看到先执行 向右边转一圈-->左边两圈-->左边飘出-->直下-->右边飘出-->右边两圈-->左边一圈的顺序。

private AnimHandler animHandler = new AnimHandler() {

        @Override
        public void handleMessage(Message message) {
            super.handleMessage(message);
            CoinRainModel coinRainModel = (CoinRainModel) message.obj;
            if (message.what == COUNT_COINS_RAIN / 2 + 1) {  //5
                //右边飘出去
                floatOutAnim(coinRainModel, 0);
            } else if (message.what == COUNT_COINS_RAIN / 2 - 1) { //3
                //左边飘出去
                floatOutAnim(coinRainModel, 1);
            } else if (message.what == COUNT_COINS_RAIN / 2) { //4
                //直下
                startCoinsAnim(coinRainModel);
            } else if (message.what == COUNT_COINS_RAIN / 2 + 2) { //6
                //右边两圈
                startReboundTwiceAnim(coinRainModel, 0);
            } else if (message.what == COUNT_COINS_RAIN / 2 - 2) { //2
                //左边两圈
                startReboundTwiceAnim(coinRainModel, 1);
            } else if (message.what < COUNT_COINS_RAIN / 2 - 2) { //0 1
                //向右边转一圈
                startReboundOnceAnim(coinRainModel, 0);
            } else {                                              // 7 8
                //向左边转一圈
                startReboundOnceAnim(coinRainModel, 1);
            }
        }
    };

上面就是各个动画的入口点了,那0 、1动画举例子:

/**
     * 回弹一圈的View
     */
    private void startReboundOnceAnim(final CoinRainModel coinRainModel, final int direction) {
        final Random random = new Random();
        startFallDownAnim(coinRainModel.coinView, 600).addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                int radius = DimenUtils.dp2px(80);
                AnimatorSet animatorSet = startCycleCurveAnim(coinRainModel.coinView, direction == 0 ? 600 : 800, direction == 0 ? coinRainModel.positionX - radius : coinRainModel.positionX + radius, 350 + random.nextInt(100));
                animatorSet.start();

                animatorSet.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        startCollapseCoinsAnim(coinRainModel.coinView);
                    }
                });
            }
        });
    }
/**
     * 下落的动画
     */
    private ObjectAnimator startFallDownAnim(View view, int duration) {
        view.setVisibility(View.VISIBLE);
        ObjectAnimator coinsAnim = ObjectAnimator.ofFloat(view, "translationY", 0f, 500f);
        coinsAnim.setDuration(duration);
        coinsAnim.start();
        return coinsAnim;
    }

先从Y轴下落500dp高度,然后在该动画结束时,注册监听器,在OnAnimationEnd()方法中开启startCycleCurveAnim()方法,是一个半个周期的正弦弧形动画,如下:

/**
     * 获取半个周期正弦弧形的动画
     */
    private AnimatorSet startCycleCurveAnim(View view, int duration, int x, int y) {
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "x", x);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "y", y);
        animator2.setInterpolator(new CycleInterpolator(0.5f));
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(duration);
        animatorSet.playTogether(animator1, animator2);
        return animatorSet;
    }

该动画也有一个监听器,在该动画执行完毕时,开启金币回收的动画,如下:

/**
     * 收起金币的动画
     */
    private void startCollapseCoinsAnim(final View view) {
        int marginRight = 40;
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "x", mScreenWidth - DimenUtils.dp2px(marginRight));
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "y", DimenUtils.dp2px(12));
        animator1.setInterpolator(new AccelerateInterpolator(0.5f));
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(200);
        animatorSet.playTogether(animator1, animator2);
        animatorSet.start();
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                view.setAlpha(0);
                vCoinsRain.removeView(view);
                if (vCoinsRain != null && vCoinsRain.getChildCount() == 1) {
                    startEarnCoins3DAnim();
                }
            }
        });
    }

至此,一个回弹一圈的view制作完成,其他的动画效果和这个类似。整个自定义view的代码如下:

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.CycleInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import java.util.Random;

/**
 * Annotation:结果页激励体系入口
 * Created by buder on 18-7-24
 */
public class RPEarnCashEntranceView extends RelativeLayout {

    private static final int COUNT_COINS_RAIN = 9;

    private ImageView vEarnCash;
    private LinearLayout vCoinsRain;//金币雨ViewGroup
    private int mScreenWidth;
    private int mCoinRainWidth;
    private long mIntervalTime = 0;//动画间隔时间

    public RPEarnCashEntranceView(@NonNull Context context) {
        this(context, null);
    }

    public RPEarnCashEntranceView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RPEarnCashEntranceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @Override
    protected void onDetachedFromWindow() {
        vEarnCash.clearAnimation();
        super.onDetachedFromWindow();
    }

    private void init(Context context) {
        mScreenWidth = DimenUtils.getScreenWidth();
        mCoinRainWidth = DimenUtils.dp2px(60);
        LayoutInflater.from(context).inflate(R.layout.inflate_result_page_earn_cash_entrance, this, true);
        vEarnCash = (ImageView) findViewById(R.id.earn_cash_entrance);
        vCoinsRain = (LinearLayout) findViewById(R.id.earn_cash_coins_rain);
        adjustCoinsRainHeight();
        vEarnCash.setImageResource(R.drawable.main_permanent_noti_alert);
        addCoinRainView();
    }

    /**
     * 按屏幕比例适配金币雨高度
     */
    private void adjustCoinsRainHeight() {
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) vCoinsRain.getLayoutParams();
        params.height = DimenUtils.dp2px(112 + 500);
        vCoinsRain.setLayoutParams(params);
    }

    /**
     * 开始金币翻转动画
     */
    private void startEarnCoins3DAnim() {
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(vEarnCash, "rotationY", 0, 360).setDuration(1000);
        animator1.start();
    }

    /**
     * 添加金币雨
     */
    private void addCoinRainView() {
        //金币之间的间距
        int unitSpace = (mScreenWidth - DimenUtils.dp2px(2 * 60) - 9 * mCoinRainWidth) / 10;
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mCoinRainWidth, mCoinRainWidth);
        params.leftMargin = unitSpace;
        for (int i = 0; i < COUNT_COINS_RAIN; i++) {
            ImageView imageView = new ImageView(getContext());
            imageView.setImageResource(R.drawable.main_permanent_noti_alert);
            imageView.setLayoutParams(params);
            imageView.setVisibility(View.INVISIBLE);
            vCoinsRain.addView(imageView);
            CoinRainModel coinRainModel = new CoinRainModel(imageView, (i + 1) * unitSpace + i * mCoinRainWidth);
            startCoinRainAnim(coinRainModel, i);
        }
    }

    private void startCoinRainAnim(CoinRainModel coinRainModel, int what) {
        Message message = Message.obtain();
        message.obj = coinRainModel;
        message.what = what;
        mIntervalTime += 20;
        animHandler.sendMessageDelayed(message, mIntervalTime);
    }

    private void startCoinsAnim(final CoinRainModel coinRainModel) {
        startFallDownAnim(coinRainModel.coinView, 1000).addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                startCollapseCoinsAnim(coinRainModel.coinView);
            }
        });
    }

    /**
     * 回弹一圈的View
     */
    private void startReboundOnceAnim(final CoinRainModel coinRainModel, final int direction) {
        final Random random = new Random();
        startFallDownAnim(coinRainModel.coinView, 600).addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                int radius = DimenUtils.dp2px(80);
                AnimatorSet animatorSet = startCycleCurveAnim(coinRainModel.coinView, direction == 0 ? 600 : 800, direction == 0 ? coinRainModel.positionX - radius : coinRainModel.positionX + radius, 350 + random.nextInt(100));
                animatorSet.start();

                animatorSet.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        startCollapseCoinsAnim(coinRainModel.coinView);
                    }
                });
            }
        });
    }

    /**
     * 回弹两圈的动
     */
    private void startReboundTwiceAnim(final CoinRainModel coinRainModel, final int direction) {
        startFallDownAnim(coinRainModel.coinView, 400).addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                int radius = DimenUtils.dp2px(50);
                AnimatorSet animatorSet = startCycleCurveAnim(coinRainModel.coinView, 600, direction == 0 ? coinRainModel.positionX - radius : coinRainModel.positionX + radius, 200);
                animatorSet.start();

                final AnimatorSet animatorSet1 = startCycleCurveAnim(coinRainModel.coinView, 600, direction == 0 ? coinRainModel.positionX - radius * 2 : coinRainModel.positionX + radius * 2, 300);
                animatorSet.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        animatorSet1.start();
                    }
                });
                animatorSet1.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        startCollapseCoinsAnim(coinRainModel.coinView);
                    }
                });
            }
        });
    }

    /**
     * 漂浮出去的动画
     */
    private void floatOutAnim(final CoinRainModel coinRainModel, final int direction) {
        startFallDownAnim(coinRainModel.coinView, 400).addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {

                AnimatorSet animatorSet = startCycleCurveAnim(coinRainModel.coinView, 600, direction == 0 ? coinRainModel.positionX / 2 : coinRainModel.positionX + (mScreenWidth - coinRainModel.positionX) / 2, 300);
                animatorSet.start();

                final AnimatorSet animatorSet1 = startCycleCurveAnim(coinRainModel.coinView, 600, direction == 0 ? -coinRainModel.positionX / 2 : coinRainModel.positionX + (mScreenWidth - coinRainModel.positionX) / 2 * 3, 400);

                animatorSet.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        animatorSet1.start();
                    }
                });
                animatorSet1.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        coinRainModel.coinView.setAlpha(0);
                        vCoinsRain.removeView(coinRainModel.coinView);
                    }
                });
            }
        });
    }

    /**
     * 收起金币的动画
     */
    private void startCollapseCoinsAnim(final View view) {
        int marginRight = 40;
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "x", mScreenWidth - DimenUtils.dp2px(marginRight));
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "y", DimenUtils.dp2px(12));
        animator1.setInterpolator(new AccelerateInterpolator(0.5f));
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(200);
        animatorSet.playTogether(animator1, animator2);
        animatorSet.start();
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                view.setAlpha(0);
                vCoinsRain.removeView(view);
                if (vCoinsRain != null && vCoinsRain.getChildCount() == 1) {
                    startEarnCoins3DAnim();
                }
            }
        });
    }

    /**
     * 下落的动画
     */
    private ObjectAnimator startFallDownAnim(View view, int duration) {
        view.setVisibility(View.VISIBLE);
        ObjectAnimator coinsAnim = ObjectAnimator.ofFloat(view, "translationY", 0f, 500f);
        coinsAnim.setDuration(duration);
        coinsAnim.start();
        return coinsAnim;
    }

    /**
     * 获取半个周期正弦弧形的动画
     */
    private AnimatorSet startCycleCurveAnim(View view, int duration, int x, int y) {
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "x", x);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "y", y);
        animator2.setInterpolator(new CycleInterpolator(0.5f));
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(duration);
        animatorSet.playTogether(animator1, animator2);
        return animatorSet;
    }


    private static class CoinRainModel {

        View coinView;
        int positionX;

        public CoinRainModel(View coinView, int positionX) {
            this.coinView = coinView;
            this.positionX = positionX;
        }
    }

    private static class AnimHandler extends Handler {

    }

    @SuppressLint("HandlerLeak")
    private AnimHandler animHandler = new AnimHandler() {

        @Override
        public void handleMessage(Message message) {
            super.handleMessage(message);
            CoinRainModel coinRainModel = (CoinRainModel) message.obj;
            if (message.what == COUNT_COINS_RAIN / 2 + 1) {  //5
                //右边飘出去
                floatOutAnim(coinRainModel, 0);
            } else if (message.what == COUNT_COINS_RAIN / 2 - 1) { //3
                //左边飘出去
                floatOutAnim(coinRainModel, 1);
            } else if (message.what == COUNT_COINS_RAIN / 2) { //4
                //直下
                startCoinsAnim(coinRainModel);
            } else if (message.what == COUNT_COINS_RAIN / 2 + 2) { //6
                //右边两圈
                startReboundTwiceAnim(coinRainModel, 0);
            } else if (message.what == COUNT_COINS_RAIN / 2 - 2) { //2
                //左边两圈
                startReboundTwiceAnim(coinRainModel, 1);
            } else if (message.what < COUNT_COINS_RAIN / 2 - 2) { //0 1
                //向右边转一圈
                startReboundOnceAnim(coinRainModel, 0);
            } else {                                              // 7 8
                //向左边转一圈
                startReboundOnceAnim(coinRainModel, 1);
            }
        }
    };
}

整个项目完整的代码在git上可以下载:

https://github.com/buder-cp/base_component_learn/tree/master/slide_viewpager/bounce

猜你喜欢

转载自blog.csdn.net/cpcpcp123/article/details/81611268