点赞飘心动画组件
package com.reone.likepoint;
import android.animation.Animator;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.animation.AnimatorSet;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Created by wangxingsheng on 2018/7/24.
* 点赞飘心动画组件
*/
public class FlyHeartView extends FrameLayout {
private List mLikeDrawables; // 图片的集合
private LayoutParams mLayoutParams; // 用于设置动画对象的位置参数
private Random mRandom; // 用于产生随机数,如生成随机图片
private int mViewWidth; // 控件的宽度
private int mViewHeight; // 控件的高度
private int mPicWidth; // 图片的宽度
private int mPicHeight; // 图片的高度
public FlyHeartView(Context context) {
this(context, null);
}
public FlyHeartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlyHeartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initParams();
}
private void initParams() {
mLikeDrawables = new ArrayList<>();
mLikeDrawables.add(generateDrawable(R.drawable.heart0));
mLikeDrawables.add(generateDrawable(R.drawable.heart1));
mLikeDrawables.add(generateDrawable(R.drawable.heart2));
mLikeDrawables.add(generateDrawable(R.drawable.heart3));
mLikeDrawables.add(generateDrawable(R.drawable.heart4));
mLikeDrawables.add(generateDrawable(R.drawable.heart5));
mLikeDrawables.add(generateDrawable(R.drawable.heart6));
mLikeDrawables.add(generateDrawable(R.drawable.heart7));
mLikeDrawables.add(generateDrawable(R.drawable.heart8));
// 获取图片的宽高, 由于图片大小一致,故直接获取第一张图片的宽高
mPicWidth = dp2px(40);
mPicHeight = dp2px(38);
// 初始化布局参数
mLayoutParams = new LayoutParams(mPicWidth, mPicHeight);
mLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
mRandom = new Random();
}
public int dp2px(float dpValue) {
final float scale = getContext().getResources().getDisplayMetrics().scaledDensity;
return (int) (dpValue * scale + 0.5f);
}
private Drawable generateDrawable(int resID) {
return ContextCompat.getDrawable(getContext(), resID);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
for (int i = 0, size = getChildCount(); i < size; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = getWidth();
mViewHeight = getHeight();
}
/**
* 动态添加 FlowView
*/
public void addLikeView() {
ImageView likeView = new ImageView(getContext());
likeView.setImageDrawable(mLikeDrawables.get(mRandom.nextInt(mLikeDrawables.size())));
likeView.setLayoutParams(mLayoutParams);
addView(likeView);
startAnimation(likeView);
}
private void startAnimation(View target) {
// 设置进入动画
AnimatorSet enterAnimator = generateEnterAnimation(target);
// 设置路径动画
ValueAnimator curveAnimator = generateCurveAnimation(target);
// 设置动画集合, 先执行进入动画,最后再执行运动曲线动画
AnimatorSet finalAnimatorSet = new AnimatorSet();
finalAnimatorSet.setTarget(target);
finalAnimatorSet.playSequentially(enterAnimator, curveAnimator);
finalAnimatorSet.addListener(new AnimationEndListener(target));
finalAnimatorSet.start();
}
/**
* 生成进入动画
*
* @return 动画集合
*/
private AnimatorSet generateEnterAnimation(View target) {
ObjectAnimator alpha = ObjectAnimator.ofFloat(target, "alpha", 0.2f, 1f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, "scaleX", 0.5f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, "scaleY", 0.5f, 1f);
AnimatorSet enterAnimation = new AnimatorSet();
enterAnimation.playTogether(alpha, scaleX, scaleY);
enterAnimation.play(alpha);
enterAnimation.setDuration(150);
enterAnimation.setTarget(target);
return enterAnimation;
}
/**
* 生成曲线运动动画
*
* @return 动画集合
*/
private ValueAnimator generateCurveAnimation(View target) {
BezierCurveEvaluator evaluator = new BezierCurveEvaluator(generateCTRLPoint(1), generateCTRLPoint(2));
ValueAnimator valueAnimator = ValueAnimator.ofObject(evaluator,
new Point((mViewWidth - mPicWidth) / 2, mViewHeight - mPicHeight),
new Point(mRandom.nextInt(mViewWidth - mPicWidth), 0));
valueAnimator.setDuration(2500);
valueAnimator.addUpdateListener(new CurveUpdateLister(target));
valueAnimator.setTarget(target);
return valueAnimator;
}
/**
* 动画曲线路径更新监听器, 用于动态更新动画作用对象的位置
*/
private class CurveUpdateLister implements ValueAnimator.AnimatorUpdateListener {
private View target;
public CurveUpdateLister(View target) {
this.target = target;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获取当前动画运行的状态值, 使得动画作用对象沿着曲线(涉及贝塞儿曲线)运动
Point point = (Point) animation.getAnimatedValue();
target.setX(point.x);
if(point.y < target.getY()){
target.setY(point.y);
}
// 改变对象的透明度
target.setAlpha(1 - animation.getAnimatedFraction());
float scale = (1 - animation.getAnimatedFraction() / 2);
target.setScaleY(scale);
target.setScaleX(scale);
target.setRotation(point.rotation);
}
}
/**
* 生成贝塞儿曲线的控制点
*
* @param value 设置控制点 y 轴上取值区域
* @return 控制点的 x y 坐标
*/
private Point generateCTRLPoint(int value) {
Point point = new Point();
point.x = mRandom.nextInt(mViewWidth - mPicWidth);
point.y = mRandom.nextInt(mViewHeight / value);
return point;
}
/**
* 自定义估值算法, 计算对象当前运动的具体位置 Point
*/
private class BezierCurveEvaluator implements TypeEvaluator {
// 由于这里使用的是三阶的贝塞儿曲线, 所以我们要定义两个控制点
private Point ctrlPoint1;
private Point ctrlPoint2;
public BezierCurveEvaluator(Point ctrlPoint1, Point ctrlPoint2) {
this.ctrlPoint1 = ctrlPoint1;
this.ctrlPoint2 = ctrlPoint2;
}
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
// 这里运用了三阶贝塞儿曲线的公式, 请自行上网查阅
float leftTime = 1.0f - fraction;
Point resultPoint = new Point();
// 三阶贝塞儿曲线
resultPoint.x = (float) Math.pow(leftTime, 3) * startValue.x
+ 3 * (float) Math.pow(leftTime, 2) * fraction * ctrlPoint1.x
+ 3 * leftTime * (float) Math.pow(fraction, 2) * ctrlPoint2.x
+ (float) Math.pow(fraction, 3) * endValue.x;
resultPoint.y = (float) Math.pow(leftTime, 3) * startValue.y
+ 3 * (float) Math.pow(leftTime, 2) * fraction * ctrlPoint1.y
+ 3 * leftTime * fraction * fraction * ctrlPoint2.y
+ (float) Math.pow(fraction, 3) * endValue.y;
// 根据生成点的位置设置此点应该旋转的角度
resultPoint.rotation = (float) (- Math.atan((endValue.x - resultPoint.x)/(endValue.y - resultPoint.y)) * 180 / Math.PI % 360);
return resultPoint;
}
}
/**
* 动画结束监听器,用于释放无用的资源
*/
private class AnimationEndListener extends AnimatorListenerAdapter {
private View target;
public AnimationEndListener(View target) {
this.target = target;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
removeView(target);
}
}
class Point{
public float x;
public float y;
public float rotation;
Point() {
}
Point(float x, float y) {
this.x = x;
this.y = y;
}
}
}