一、概述,最后面有完整代码下载地址
老规矩先上图
好了,基本就是这个样子,录完的视频用格式工厂转换完就这个样子了,将就看吧
二、定义我们自己的Layout
1. /**
2. * @author 刘洋巴金
3. * @date 2017-4-27
4. *
5. * 定义我们自己的布局
6. * */
7. public class LoveLayout extends RelativeLayout{
8.
9. private Context context;
10. private LayoutParams params;
11. private Drawable[] icons = new Drawable[4];
12. private Interpolator[] interpolators = new Interpolator[4];
13. private int mWidth;
14. private int mHeight;
15.
16. public LoveLayout(Context context, AttributeSet attrs) {
17. super(context, attrs);
18.
19. this.context = context;
20. initView();
21. }
22.
23. private void initView() {
24.
25. // 图片资源
26. 0] = getResources().getDrawable(R.drawable.green);
27. 1] = getResources().getDrawable(R.drawable.purple);
28. 2] = getResources().getDrawable(R.drawable.red);
29. 3] = getResources().getDrawable(R.drawable.yellow);
30.
31. // 插值器
32. 0] = new AccelerateDecelerateInterpolator(); // 在动画开始与结束的地方速率改变比较慢,在中间的时候加速
33. 1] = new AccelerateInterpolator(); // 在动画开始的地方速率改变比较慢,然后开始加速
34. 2] = new DecelerateInterpolator(); // 在动画开始的地方快然后慢
35. 3] = new LinearInterpolator(); // 以常量速率改变
36.
37. int width = icons[0].getIntrinsicWidth();
38. int height = icons[0].getIntrinsicWidth();
39. new LayoutParams(width, height);
40. params.addRule(CENTER_HORIZONTAL, TRUE);
41. params.addRule(ALIGN_PARENT_BOTTOM, TRUE);
42. }
基本就是做了初始化,声明了4个drawable,也就是4个颜色的心,4个插值器,用于控制动画速率的改变,设置初始位置为屏幕的下边中点处。
1. @Override
2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3. // TODO Auto-generated method stub
4. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
5. mWidth = getMeasuredWidth();
6. mHeight = getMeasuredHeight();
7. }
8.
9. public void addLoveView() {
10. // TODO Auto-generated method stub
11. final ImageView iv = new ImageView(context);
12. iv.setLayoutParams(params);
13. new Random().nextInt(4)]);
14. addView(iv);
15.
16. // 开启动画,并且用完销毁
17. AnimatorSet set = getAnimatorSet(iv);
18. set.start();
19. new AnimatorListenerAdapter() {
20. @Override
21. public void onAnimationEnd(Animator animation) {
22. // TODO Auto-generated method stub
23. super.onAnimationEnd(animation);
24. removeView(iv);
25. }
26. });
27. }
用于添加心型效果。动画结束后,再移除
1. /**
2. * 获取动画集合
3. * @param iv
4. * */
5. private AnimatorSet getAnimatorSet(ImageView iv) {
6.
7. // 1.alpha动画
8. "alpha", 0.3f, 1f);
9.
10. // 2.缩放动画
11. "scaleX", 0.2f, 1f);
12. "scaleY", 0.2f, 1f);
13.
14. // 动画集合
15. new AnimatorSet();
16. set.playTogether(alpha, scaleX, scaleY);
17. 500);
18.
19. // 贝塞尔曲线动画
20. ValueAnimator bzier = getBzierAnimator(iv);
21.
22. new AnimatorSet();
23. set2.playSequentially(set, bzier);
24. set2.setTarget(iv);
25. return set2;
26. }
playTogether:几个动画同时执行
ObjectAnimator为属性动画,不熟悉可以百度了解下
然后是设置贝塞尔曲线动画
playSequentially:动画依次执行
1. /**
2. * 贝塞尔动画
3. * */
4. private ValueAnimator getBzierAnimator(final ImageView iv) {
5. // TODO Auto-generated method stub
6. // 4个点的坐标
7. new BasEvaluator(PointFs[1], PointFs[2]);
8. 0], PointFs[3]);
9. new AnimatorUpdateListener() {
10.
11. @Override
12. public void onAnimationUpdate(ValueAnimator animation) {
13. // TODO Auto-generated method stub
14. PointF p = (PointF) animation.getAnimatedValue();
15. iv.setX(p.x);
16. iv.setY(p.y);
17. 1- animation.getAnimatedFraction()); // 透明度
18. }
19. });
20. valueAnim.setTarget(iv);
21. 3000);
22. new Random().nextInt(4)]);
23. return valueAnim;
24. }
25.
26. private PointF[] getPointFs(ImageView iv) {
27. // TODO Auto-generated method stub
28. new PointF[4];
29. 0] = new PointF(); // p0
30. 0].x = (mWidth- params.width)/ 2;
31. 0].y = mHeight - params.height;
32.
33. 1] = new PointF(); // p1
34. 1].x = new Random().nextInt(mWidth);
35. 1].y = new Random().nextInt(mHeight /2) + mHeight / 2 + params.height;
36.
37. 2] = new PointF(); // p2
38. 2].x = new Random().nextInt(mWidth);
39. 2].y = new Random().nextInt(mHeight /2);
40.
41. 3] = new PointF(); // p3
42. 3].x = new Random().nextInt(mWidth);
43. 3].y = 0;
44. return PointFs;
45. }
先获得4个点的坐标
p0坐标:x坐标((布局的宽-心形图片宽)除以2),y坐标(布局的高 -心形图片高),这样获得的是顶部部水平中心点的坐标。
p1坐标:x坐标(横坐标中的随机位置),y坐标(布局一半的高度 加上 0到一半高度范围内的随机坐标+心形的高度的一半)。这样取到的横坐标是在布局宽度之内的随机坐标,纵坐标为整个路径高度中部以上的随机坐标。
p2坐标:与p1类似,横坐标是在布局宽度之内的随机坐标,纵坐标为整个路径高度中部以下的随机坐标。
p3坐标:控件底部中心点
好了知道4个坐标了,那么开始计算路径
首先为了计算贝塞尔曲线,我们先写一个估值器
1. /**
2. *
3. * 估值器,计算路径
4. * */
5. public class BasEvaluator implements TypeEvaluator<PointF> {
6.
7. private PointF p1;
8. private PointF p2;
9.
10. public BasEvaluator(PointF p1, PointF p2) {
11. super();
12. this.p1 = p1;
13. this.p2 = p2;
14. }
15.
16. @Override
17. public PointF evaluate(float fraction, PointF p0, PointF p3) {
18. // TODO Auto-generated method stub
19. new PointF();
20.
21. // 贝塞尔曲线公式 p0*(1-t)^3 + 3p1*t*(1-t)^2 + 3p2*t^2*(1-t) + p3^3
22. 1-fraction) *(1-fraction ) * (1-fraction)
23. 3*p1.x * fraction *(1-fraction )*(1-fraction )
24. 3*p2.x *fraction *fraction *(1-fraction )
25. +p3.x*fraction *fraction *fraction ;
26. 1-fraction ) *(1-fraction ) * (1-fraction )
27. 3*p1.y * fraction *(1-fraction )*(1-fraction )
28. 3*p2.y *fraction *fraction *(1-fraction )
29. +p3.y*fraction *fraction *fraction ;
30. return pointf;
31. }
32. }
TypeEvaluator:估值器回调evaluate方法,用于动态的改变动画的属性值。
evaluate三个参数:
1.fraction,默认传入的就是(currentTime - startTime) / duration,动画执行的时间除以总的时间比值,可以理解为变化率。当duration到了的时候,正好,起始点变到终点。
2.起始点
3.终点
根据三个参数,计算点的根据每毫秒的变化率,计算点的路径轨迹。
好了贝塞尔曲线动画就讲完了,然后再把动画绑定到控件上。
最后在MainActivity中根据点击事件,进行增加心型就好了。
1. btn_press = (Button)findViewById(R.id.btn_press);
2. ll_love = (LoveLayout)findViewById(R.id.ll_love);
3. btn_press.setOnClickListener(new OnClickListener() {
4.
5. @Override
6. public void onClick(View v) {
7. // TODO Auto-generated method stub
8. ll_love.addLoveView();
9. }
10. });