效果图
原理分析
其实代码很简单,主要的知识点就是执行动画是如何配合插值器和估值器的运算,达到我们想要的效果。这里的向上抛体运动和自由落体运动主要是运用了高中的加速度运算的公式,计算其x轴和y轴的路程和速度即可,通过插值器计算时间和估值器计算位移,达到向上抛体效果。
- 插值器:用0-t,来表示时间的递增
- 估值器:用公式计算出动画的路程
- 当前速度:v = v0 - gt
- 向上抛体运动:s = v0t + 1/2 gt^2
- 自由落体运动:s = 1/2 gt^2
实现步骤
1、初始化变量
//1、继承RelativeLayout
public class GravityView extends RelativeLayout implements View.OnClickListener {
private Context context;
//2、准备几张图片
private int[] christmas_drawable = {R.drawable.christmas01, R.drawable.christmas02, R.drawable.christmas03
, R.drawable.christmas04, R.drawable.christmas05, R.drawable.christmas06};
//随机数种子
private Random random = new Random();
//View的宽高
private int width, height;
//图片的宽高
private int drawableWidth, drawableHeight;
//动画播放时长
private int time = 5;
public GravityView(Context context) {
this(context, null);
}
public GravityView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GravityView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
//3、设置点击事件
setOnClickListener(this);
//4、获取图片的宽高
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.christmas01);
drawableWidth = drawable.getIntrinsicWidth();
drawableHeight = drawable.getIntrinsicHeight();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取View的宽高
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
}
}
2、点击事件
@Override
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
public void onClick(View v) {
//5、点击增加图片
addChristmas(context);
}
/**
* 添加图片
*
* @param context
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private void addChristmas(Context context) {
/**
* 1、点击一次增加一张图片在底部
*/
final ImageView imageView = new ImageView(context);
imageView.setBackgroundResource(christmas_drawable[random.nextInt(christmas_drawable.length - 1)]);
LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(ALIGN_PARENT_BOTTOM);
params.addRule(CENTER_HORIZONTAL);
imageView.setLayoutParams(params);
addView(imageView);
//2、开始动画
AnimatorSet animatorSet = getAnimatorSet(imageView);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//3、动画执行后移除View
removeView(imageView);
}
});
animatorSet.start();
}
3、添加动画
/**
* 添加动画
*
* @param imageView
* @return
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private AnimatorSet getAnimatorSet(ImageView imageView) {
AnimatorSet enter = new AnimatorSet();
//1、缩放动画
AnimatorSet scaleAnimator = new AnimatorSet();
ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 0.3f, 1f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0.3f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0.3f, 1f);
scaleAnimator.setDuration(300);
scaleAnimator.playTogether(alpha, scaleX, scaleY);
//2、向上抛体动画
ValueAnimator bezierAnimator = getGravityAnimator(imageView);
//3、两个动画按顺序播放
enter.playSequentially(scaleAnimator, bezierAnimator);
return enter;
}
/**
* 向上抛体动画
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private ValueAnimator getGravityAnimator(final ImageView imageView) {
//1、构建p0和p3点,p0就是底部中点,p3是不会用到的点,随便取个(0,0)
PointF point0 = new PointF((width - drawableWidth) / 2, height - drawableHeight);
PointF point3 = new PointF(0, 0);
//2、创建估值器
BezierEvaluator evaluator = new BezierEvaluator();
final ValueAnimator valueAnimator = ObjectAnimator.ofObject(evaluator, point0, point3);
//3、自定义插值器
valueAnimator.setInterpolator(new XYInterpolator());
valueAnimator.setDuration(time * 1000);
//4、监听贝塞尔曲线估值器返回值
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//5、获取BezierEvaluator中evaluate()返回的运行轨迹坐标点,设置图片路线
PointF pointF = (PointF) animation.getAnimatedValue();
imageView.setX(pointF.x);
imageView.setY(pointF.y);
}
});
return valueAnimator;
}
/**
* 估值器:计算动画的执行轨迹
*/
public class BezierEvaluator implements TypeEvaluator<PointF> {
double g = 1500; //物理中的g
double v0 = 2000; //初速度
double t1 = 0; //记录向上抛体顶点位置的时间
double y1 = 0; //记录向上抛体顶点位置的y
@Override
public PointF evaluate(float t, PointF point0, PointF point3) {
PointF point = new PointF();
//保持x轴坐标不动
point.x = point0.x;
double v = v0 - (g * t);
if (v <= 0) {
//当物体到达顶点,速度开始小于0,开始自由落体运动,s = 1/2 gt^2
point.y = (float) (y1 + (g * (t - t1) * (t - t1)) / 2);
} else {
//向上抛体运动,s = v0t + 1/2 gt^2
point.y = (float) (point0.y - (v0 * t) + (g * t * t) / 2);
t1 = t;
y1 = point.y;
}
return point;
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
public class XYInterpolator extends BaseInterpolator {
@Override
public float getInterpolation(float input) {
// input是从0-1递增,所以会从0-time递增,也就是对应估值器的t的变化
return time * input;
}
}
其布局的实现
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.test.customView.GravityView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true" />
</RelativeLayout>