Android动画需求,从上方掉落下来一堆金币,然后弹弹弹,最后收起金币。大概的样子就是下面这张图了 o(* ̄︶ ̄*)o
整体项目结构如下图所示:
金币雨是一个自定义的控件,需要使用时,直接在XML中引用即可。如下:
在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