之前玩淘宝误入它的直播频道,发现它的直播界面的点赞效果挺好看,然后发现QQ控件点赞有类似动画,于是趁有空花了点时间玩玩。
先上个效果图:
添加了一个按钮模拟点赞,点击多少次就出现多个水果,他们的运动轨迹和速度是不一样的,而且带有淡入淡出效果。这是淘宝直播的效果,qq空间是点击一次就出现好多个的,修改一点逻辑也能实现对应的效果。
gif图看起来有点不流畅,因为录制时锁定的帧率避免超2M不能上传,实际运行是流畅的。
因为不是做成一个通用控件,所以我也就实现了效果,大家如果要用,可以自己加更多自定义内容。实现起来挺简单的,也就不啰嗦了
用到的知识点:贝塞尔公式(三阶)、属性动画、动画集合、自动义估值器
先丢代码再说实现过程:
代码:
package cn.small_qi.transitiontest.diyview;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import cn.small_qi.transitiontest.R;
public class PressLikeView extends ViewGroup {
private List<Integer> images;//图片
private List<Interpolator> inters;//插值器
private Random random;
private int defaultSize = 150;//图片默认尺寸(px)
public PressLikeView(Context context) {
super(context);
}
public PressLikeView(Context context, AttributeSet attrs) {
super(context, attrs);
initData();
}
//初始化数据
private void initData() {
random =new Random();
images = new ArrayList<>();
inters = new ArrayList<>();
images.add(R.drawable.a510209);
images.add(R.drawable.a510213);
images.add(R.drawable.a510216);
images.add(R.drawable.a510222);
images.add(R.drawable.a510225);
images.add(R.drawable.a510234);
//....
inters.add(new LinearInterpolator());
inters.add(new AccelerateInterpolator());
inters.add(new AccelerateDecelerateInterpolator());
inters.add(new DecelerateInterpolator());
//....
}
public PressLikeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) { }
//使用预置的随机图片
public void show(){//这个方法是开放出去的,就是按钮点击时调用,出现一个水果动画
ImageView view = new ImageView(getContext());
view.setImageResource(images.get(random.nextInt(images.size())));//随机设置一张图片
view.setLayoutParams(new LayoutParams(defaultSize,defaultSize));//设置大小
addView(view);//添加到容器中
view.layout(getWidth()/2-defaultSize, (int) (getHeight()-defaultSize*1.5),getWidth()/2, (int) (getHeight()-0.5*defaultSize));//计算位置
startAnim(view);//开始动画
}
//使用自定义的图片 -- 也可以修改成传入一个ImageView
public void show(Drawable drawable){
ImageView view = new ImageView(getContext());
view.setImageDrawable(drawable);
view.setLayoutParams(new LayoutParams(defaultSize,defaultSize));
addView(view);
view.layout(getWidth()/2-defaultSize, (int) (getHeight()-defaultSize*1.5),getWidth()/2, (int) (getHeight()-0.5*defaultSize));
startAnim(view);
}
private void startAnim(final ImageView view) {
AnimatorSet animatorSet = new AnimatorSet();
//淡入动画
ValueAnimator inAnim = ValueAnimator.ofFloat(0.5f,1f);
inAnim.setDuration(500);
inAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
view.setAlpha(value);
view.setScaleX(value);
view.setY(value);
}
});
//淡出动画
ValueAnimator outAnim = ValueAnimator.ofFloat(1,0);
outAnim.setDuration(1500);
outAnim.setStartDelay(1500);//延迟启动,保证水果飞到一大半再淡出
outAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setAlpha((float) animation.getAnimatedValue());
}
});
//位移动画
ValueAnimator transAnim = ValueAnimator.ofObject(new BezierValue(),new Point(getWidth()/2,getHeight()),new Point(new Random().nextInt(getWidth()),0));
transAnim.setDuration(3000);
transAnim.setInterpolator(inters.get(random.nextInt(inters.size())));//随机设置插值器
transAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point point = (Point) animation.getAnimatedValue();
view.setX(point.x);
view.setY(point.y);
}
});
//组合动画
//三个动画同时执行
animatorSet.playTogether(inAnim,transAnim,outAnim);
//前面两个动画延迟执行,最后一个同时执行
/*animatorSet.playSequentially(inAnim,transAnim);
animatorSet.play(outAnim);*/
animatorSet.start();
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) { }
@Override
public void onAnimationEnd(Animator animation) {
removeView(view);//动画结束移除ImageView
}
@Override
public void onAnimationCancel(Animator animation) { }
@Override
public void onAnimationRepeat(Animator animation) { }
});
}
//自定义插值器
class BezierValue implements TypeEvaluator<Point>{
private Random random =new Random();
private int ctrlPX1, ctrlPX2,ctrlPY1, ctrlPY2;
private boolean isInit;//只需要初始化一次
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
Point point = new Point();
point.x = (int) cubicPointX(fraction,startValue.x,endValue.x);
point.y = (int) cubicPointY(fraction,startValue.y,endValue.y);
return point;
}
//贝塞尔计算x
private double cubicPointX(float fraction, int start, int end){
if (!isInit){
//初始化控制点y左边
ctrlPY1 = random.nextInt(start+end/2);
ctrlPY2 = random.nextInt(start+end/2)+(start+end/2);
//初始化控制点x坐标
if (random.nextBoolean()){//先左后右
ctrlPX1 = (int) (random.nextInt(start)-start/4f);//减去start/4 是为了运动曲线更明显
ctrlPX2 = (int) (random.nextInt(start)+start*1.25f);//start是宽度的一半,为了保证后面往右运动,应该是随机数加上start。现在乘1.25是为了让曲线更明显
}else{//先右后左
ctrlPX1 = (int) (random.nextInt(start)+start*1.25f);
ctrlPX2 = (int) (random.nextInt(start)-start/4f);
}
isInit =true;
}
return start*Math.pow((1-fraction),3)+3* ctrlPX1 *fraction*Math.pow((1-fraction),2)
+3* ctrlPX2 *Math.pow(fraction,2)*(1-fraction)+end*Math.pow(fraction,3);
}
//贝塞尔计算y
private double cubicPointY(float fraction, int start, int end){
return start*Math.pow((1-fraction),3)+3* ctrlPY1 *fraction*Math.pow((1-fraction),2)
+3* ctrlPY2 *Math.pow(fraction,2)*(1-fraction)+end*Math.pow(fraction,3);
}
}
}
其实就是
1.自定义一个ViewGroup,然后每点击一次就往里面添加一个ImageView,然后设置好它的位置和大小
2.为每一个ImageView设置随机的图片,然后对其执行一些列动画
3.为了某些效果加入了淡入淡出动画,相信大家都熟悉,根据自己需求来决定怎么写就行了
4.主要是运动轨迹动画,为了路径不是单纯的直线或者简单的曲线,所以用了自定义估值器配合三阶的贝塞尔实现运动路径
5.为了实现运动速度的不规则,内置了4种插值器,然后每次随机取一个,用在运动轨迹的动画上
可能有点难看懂的是我位置的计算和贝塞尔控制点的计算,我这里简要说明一下。
初始位置我本来是是要底部居中,但是稍微做了点偏移,计算位置如图所示:
而贝塞尔两个控制点和终点的选择,我是这么计算的,大家结合代码看就看得懂了:
计算好动画之后,接下来就不难了!执行动画就好了,这样就实现的这种效果拉!
--本文结束,谢谢大家阅读