这是一个利用贝塞尔曲线实现的仿花束直播的点赞效果,实现该效果涉及到:
1、Random随机数的使用;
2、ObjectAnimator属性动画及插值器的使用;
3、贝塞尔曲线的使用;
/**
* Created by Administrator on 2018/1/30.
* 点赞的效果
*/
public class LoveLayout extends RelativeLayout {
//随机数
private Random mRandom;
//图片资源
private int[] mImageRes;
//控件的宽高
private int mWidth, mHeight;
//获取图片的宽高
private int mDrawableWidth, mDrawableHeight;
//插值器数组
private Interpolator[] mInterpolator;
public LoveLayout(Context context) {
this(context, null);
}
public LoveLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoveLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mRandom = new Random();
mImageRes = new int[]{R.drawable.pl_blue, R.drawable.pl_red, R.drawable.pl_yellow};
//获取图片的宽高
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.pl_blue);
mDrawableWidth = drawable.getIntrinsicWidth();
mDrawableHeight = drawable.getIntrinsicHeight();
mInterpolator = new Interpolator[]{new AccelerateDecelerateInterpolator(), new AccelerateInterpolator(),
new DecelerateInterpolator(), new LinearInterpolator()};
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取控件的宽高
mWidth = MeasureSpec.getSize(widthMeasureSpec);
mHeight = MeasureSpec.getSize(heightMeasureSpec);
}
}
上面这些这是是初始化数据及获取一些图片的宽度和高度,在android属性动画的基本使用(http://blog.csdn.net/wangwo1991/article/details/77424239)这篇博客中有对属性动画的差值器有做说明;接下来在触发动作的时候提供一个addLove(),将效果添加到布局容器中;
/**
* 添加一个点赞的view
*/
public void addLove() {
//添加一个imageview在底部
final ImageView loveIv = new ImageView(getContext());
//设置图片资源(随机数)
loveIv.setImageResource(mImageRes[mRandom.nextInt(mImageRes.length - 1)]);
//添加到底部中心
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
//设置底部居中
params.addRule(ALIGN_PARENT_BOTTOM);
params.addRule(CENTER_HORIZONTAL);
loveIv.setLayoutParams(params);
addView(loveIv);
//添加的效果是:有放大和透明的变化
AnimatorSet animatorSet = getAimator(loveIv);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
//动画执行完毕后,将其移除
removeView(loveIv);
}
});
animatorSet.start();
}
这里就是创建一个ImageView,随机设置ImageView的背景,并设置其在布局容器中显示的位置,同时给ImageView添加透明度和缩放动画效果;
/**
* 设置属性动画效果
* @param loveIv
* @return
*/
public AnimatorSet getAimator(ImageView loveIv) {
//添加的效果是:有放大和透明的变化
AnimatorSet allAnimator = new AnimatorSet();
AnimatorSet innerAnimator = new AnimatorSet();
//添加属性动画
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(loveIv, "alpha", 0.3f, 1.0f);
ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(loveIv, "scaleX", 0.3f, 1.0f);
ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(loveIv, "scaleY", 0.3f, 1.0f);
//一起执行动画
innerAnimator.playTogether(alphaAnimator, scaleXAnimator, scaleYAnimator);
innerAnimator.setDuration(350);
//运行的路径动画 按顺序执行动画
allAnimator.playSequentially(innerAnimator, getBezierAnimator(loveIv));
return allAnimator;
}
这里设置了透明度和缩放动画效果,通过AnimatorSet一起开启动画执行,在执行的时候根据贝塞尔曲线的路径去执行,而不是随意的去执行;
- 一阶曲线原理:
一阶曲线是没有控制点的,仅有两个数据点(A 和 B),最终效果一个线段。
一阶公式如下:
- 二阶曲线原理
二阶曲线由两个数据点(A 和 C),一个控制点(B)来描述曲线状态,大致如下:
那么ac之间的红线是怎么生成的呢,让我们了解一下:
在AB线段和BC线段分别去D、E两点,且满足条件
连接DE,取点F,使得: ,这样获取到的点F就是贝塞尔曲线上的一个点,动态图如下:
二阶公式如下:
- 三阶曲线原理
三阶曲线由两个数据点(A 和 D),两个控制点(B 和 C)来描述曲线状态
动态图如下:
三阶公式如下:
这是一阶贝塞尔、二阶贝塞尔和三阶贝塞尔,当然还有四阶、五阶等,具体网上有很多关于贝塞尔曲线的知识(https://www.cnblogs.com/wjtaigwh/p/6647114.html);这个效果里面用到的是三阶贝塞尔曲线,不过这里需要自定义路径属性动画;
/**
* Created by Administrator on 2018/1/30.
* 自定义的路径属性动画
*/
public class LoveTypeEvaluator implements TypeEvaluator<PointF> {
private PointF point1, point2;
public LoveTypeEvaluator(PointF point1, PointF point2) {
this.point1 = point1;
this.point2 = point2;
}
@Override
public PointF evaluate(float t, PointF point0, PointF point3) {
//t 的范围是0-1的范围
//可以开始套公式了
PointF pointF = new PointF();
pointF.x = point0.x * (1 - t) * (1 - t) * (1 - t)
+ 3 * point1.x * t * (1 - t) * (1 - t)
+ 3 * point2.x * t * t * (1 - t)
+ point3.x * t * t * t;
pointF.y = point0.y * (1 - t) * (1 - t) * (1 - t)
+ 3 * point1.y * t * (1 - t) * (1 - t)
+ 3 * point2.y * t * t * (1 - t)
+ point3.y * t * t * t;
return pointF;
}
}
根据构造方法传入的point1和point2使用三阶贝塞尔曲线公式进行路径的绘制;
/**
* 绘制贝塞尔曲线
* @param loveIv
* @return
*/
private Animator getBezierAnimator(final ImageView loveIv) {
//确定这四个点
PointF point0 = new PointF(mWidth / 2 - mDrawableWidth / 2, mHeight - mDrawableHeight);
//确保p2点的y值一定要大于p2点的y值
PointF point1 = getPoint(1);
PointF point2 = getPoint(2);
PointF point3 = new PointF(mRandom.nextInt(mWidth) - mDrawableWidth, 0);
LoveTypeEvaluator typeEvaluator = new LoveTypeEvaluator(point1, point2);
//第一个参数 typeEvaluator 第二个参数就是p0,第三个参数就是p3
ValueAnimator bezererAnimator = ObjectAnimator.ofObject(typeEvaluator, point0, point3);
//插值器
bezererAnimator.setInterpolator(mInterpolator[mRandom.nextInt(mInterpolator.length - 1)]);
bezererAnimator.setDuration(5000);
bezererAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
loveIv.setX(pointF.x);
loveIv.setY(pointF.y);
//设置透明度
float t = animation.getAnimatedFraction();
loveIv.setAlpha(1 - t + 0.2f);
}
});
return bezererAnimator;
}
/**
* 获取p1和p2
* @param index
* @return
*/
private PointF getPoint(int index) {
return new PointF(mRandom.nextInt(mWidth) - mDrawableWidth, mRandom.nextInt(mHeight / 2) + (index - 1) * (mHeight / 2));
}
这样效果就实现了。
源码地址:
https://pan.baidu.com/s/1rakMHcS