android 点赞飘心,点赞飘心动画组件FlyHeartView

点赞飘心动画组件

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;

}

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值