梦想为劳,梦累了要梦魇的! —— 杨绛 《我们仨》
今天我们来实现一个效果那就是模仿直播刷礼物的动画。具体效果请看下图。
当我们点击按钮的时候从屏幕下方向上飘动一颗小红心,而这颗小红心的运动轨迹当然就和贝塞尔曲线有关了,同时我们还加了渐变和运动加速的效果。具体怎么实现呢?请往下看。
实现原理
1. 准备工作 测量布局,初始化画笔,绘制图片,绘制贝塞尔曲线等
绘制如下图所示:
2. 让小红心动起来
十分简单吧,下面我们就需要让小红心按照我们规定好的贝塞尔曲线运动起来,这里我们利用属性动画 小红心每运动一下就根据贝塞尔曲线公式实时计算当前的位置,并赋值刷新。
三阶贝塞尔曲线表达式
如果你还没弄懂什么意思,那么我详细下面一张图片你肯定就豁然开朗。
代码实现
测量布局:
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
wight = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(wight, height);
random = new Random();
mStartPoint.x = wight / 2;
mStartPoint.y = height;
mEndPoint.x = wight / 2;
mEndPoint.y = 0;
mConOnePoint.x = wight;
mConOnePoint.y = height * 3 / 4;
mConTwoPoint.x = 0;
mConTwoPoint.y = height / 4;
//随机数控制心出现的位置
offestplusorminus = random.nextInt(2);
Log.e("offestplusorminus", offestplusorminus + "");
if (offestplusorminus == 1) {//位移为正
offest = random.nextInt(200);
Log.e("offestplusorminus", "正");
} else {//位移为负
int minus = random.nextInt(200);
Log.e("offestplusorminus", "负");
offest = -minus;
}
}
绘制小红心及贝塞尔曲线
@Override
protected void onDraw(Canvas canvas) {
drawLove(canvas);
}
public void drawLove(Canvas canvas) {
mRoutePaint.setAlpha((int) alpha);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.love);
bapwidht = bitmap.getWidth();
bapheight = bitmap.getHeight();
Matrix matrix = new Matrix();
matrix.postTranslate(wight - value.x - offest, height - value.y);//高度不需要减bitmap本身高度,初始不用漏出心,否则会出现位移BUG
canvas.drawBitmap(bitmap, matrix, mRoutePaint);
//绘制贝塞尔曲线
mpath.reset();
mpath.moveTo(mEndPoint.x - offest, mEndPoint.y);//初始点 从下往上 贝塞尔曲线终止点为起始点
mpath.cubicTo(mConOnePoint.x - offest, mConOnePoint.y, mConTwoPoint.x - offest, mConTwoPoint.y, mStartPoint.x - offest, mStartPoint.y);//控制点 和终止点
/* canvas.drawPath(mpath, mRoutePaint);*/
canvas.save();
}
开始动画
public void addStar() {
//设置属性动画
valueAnimator = ValueAnimator.ofObject(new StarTypeEvaluator(mConOnePoint, mConTwoPoint), mEndPoint,
mStartPoint);
valueAnimator.setDuration(6000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point point = (Point) animation.getAnimatedValue();
value.x = point.x;
value.y = point.y;
if (point.y >= height / 2) {
alpha -= 4;//透明度不断减少
}
if (alpha < 0) {
alpha = 0;
}
postInvalidate();
}
});
valueAnimator.start();//开始动画
//监听动画结束
/* valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
alpha = 255;//透明度重置
//0,1,2随机数
offestplusorminus = random.nextInt(2);
if (offestplusorminus == 1) {//位移为正
offest = random.nextInt(200);
} else {//位移为负
int minus = random.nextInt(200);
offest = -minus;
}
postInvalidate();
}
});*/
}
利用属性动画根据三阶贝塞尔曲线公式求出小红心运动过程中每时每刻的所在位置的x,y
//动画估值器
class StarTypeEvaluator implements TypeEvaluator<Point> {
//记录控制点
private Point conOnePoint, conSecondPoint;
public StarTypeEvaluator(Point conOnePoint, Point conSecondPoint) {
this.conOnePoint = conOnePoint;
this.conSecondPoint = conSecondPoint;
}
@Override
public Point evaluate(float t, Point startValue, Point endValue) {
//利用三阶贝塞尔曲线公式算出中间点坐标
int x = (int) (startValue.x * Math.pow((1 - t), 3) + 3 * conOnePoint.x * t * Math.pow((1 - t), 2) + 3 *
conSecondPoint.x * Math.pow(t, 2) * (1 - t) + endValue.x * Math.pow(t, 3));
int y = (int) (startValue.y * Math.pow((1 - t), 3) + 3 * conOnePoint.y * t * Math.pow((1 - t), 2) + 3 *
conSecondPoint.y * Math.pow(t, 2) * (1 - t) + endValue.y * Math.pow(t, 3));
return new Point(x, y);
}
}
接下来我们就在Activity中注册并实现点击事件就ok了
mViewGroup = (ViewGroup) getWindow().getDecorView();
mAddLoveBtn = findViewById(R.id.btn_main_add);
mAddLoveBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
floatLove = new FloatLove(MainActivity.this);
mViewGroup.addView(floatLove);
floatLove.addStar();
}
});
思路和实现起来都十分简单,也是偶然间看到这个动画效果,手痒就用了一点时间写了出来
项目源码
附上项目源码:
项目源码