一、引入

  1. 其实之前一直以为像饿了么或者是美团外卖那种把商品添加到购物车的动画会很难做,但是实际做起来好像并没有想象中的那么难哈哈。

  2. 布局主要使用CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+TabLayout+ViewPager

  3. 动画主要使用二阶贝塞尔曲线与属性动画

  4. 消息传递使用EventBus普通事件

使用二阶贝塞尔曲线实现添加购物车动画 - 图1

二、大致思路

使用二阶贝塞尔曲线实现添加购物车动画 - 图2

  1. 如图所示主要有三个点,起点、终点、以及贝塞尔曲线的控制点

  2. 起点即点击的View的位置,一般来说用如下方式即可取得。startPosition[0]为x轴开始坐标,startPosition[1]为Y轴终点坐标,两点可以看作对角线上面的两个端点(左上角x坐标,右下角y坐标)

  1. //贝塞尔起始数据点
  2. int[] startPosition = new int[2];
  3. view.getLocationOnScreen(startPosition);
  1. 终点即购物车篮子的位置,与起点类似
  1. mShoppingCart.getLocationInWindow(endPosition);
  1. 控制点,我选的控制点为上图的C点,即A点的y坐标,B点的X坐标
  1. controlPosition[0] = endPosition[0];
  2. controlPosition[1] = startPosition[1];
  1. 需要注意的地方,我不清楚是不是因为我的布局的问题,获取到的点击的A点总是会有一个偏移,后来经同事提醒,减去了TabLayout的坐标的y轴坐标即位置才可以。
  1. // 起点
  2. int[] startPosition;
  3. // 终点
  4. int[] endPosition = new int[2];
  5. // 贝塞尔控制点
  6. int[] controlPosition = new int[2];
  7. // tablayout位置
  8. int[] tablayoutPosition = new int[2];
  9. startPosition = data.getStartPosition();
  10. mShoppingCart.getLocationInWindow(endPosition);
  11. mTabLayout.getLocationInWindow(tablayoutPosition);
  12. // 处理起点y坐标偏移的问题
  13. startPosition[1] = startPosition[1] - tablayoutPosition[1] - mTabLayout.getHeight();
  14. // 终点进行一下居中处理
  15. endPosition[0] = endPosition[0] + (mShoppingCart.getWidth() / 2);
  16. controlPosition[0] = endPosition[0];
  17. controlPosition[1] = startPosition[1];
  1. 通过PathquadTo方法绘制贝塞尔曲线,使用PathMeasure获取点的坐标(借助ValueAnimator.ofFloat()配合getPosTan()来获取坐标)
  1. Path path = new Path();
  2. path.moveTo(startPosition[0], startPosition[1]);
  3. path.quadTo(controlPosition[0], controlPosition[1], endPosition[0], endPosition[1]);
  4. PathMeasure pathMeasure = new PathMeasure();
  5. // false表示path路径不闭合
  6. pathMeasure.setPath(path, false);
  7. // ofFloat是一个生成器
  8. ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, pathMeasure.getLength());
  9. // 匀速线性插值器
  10. valueAnimator.setInterpolator(new LinearInterpolator());
  11. valueAnimator.setDuration(800);
  12. valueAnimator.addUpdateListener(animation -> {
  13. float value = (Float) animation.getAnimatedValue();
  14. pathMeasure.getPosTan(value, currentPosition, null);
  15. imageView.setX(currentPosition[0]);
  16. imageView.setY(currentPosition[1]);
  17. });
  18. valueAnimator.start();
  1. 下面是用属性动画给购物车篮子做了一个放大缩小的动画效果
  1. // mShoppingCart是View
  2. ObjectAnimator shoppingCartX = ObjectAnimator.ofFloat(mShoppingCart, "scaleX", 1.0f, 1.3f, 1.0f);
  3. ObjectAnimator shoppingCartY = ObjectAnimator.ofFloat(mShoppingCart, "scaleY", 1.0f, 1.3f, 1.0f);
  4. shoppingCartX.setInterpolator(new AccelerateInterpolator());
  5. shoppingCartY.setInterpolator(new AccelerateInterpolator());
  6. AnimatorSet shoppingCart = new AnimatorSet();
  7. shoppingCart
  8. .play(shoppingCartX)
  9. .with(shoppingCartY);
  10. shoppingCart.setDuration(800);
  11. shoppingCart.start();

三、稍完整的大部分代码

  1. private void AddAnimation(AddEventBean data) {
  2. // 起点
  3. int[] startPosition;
  4. // 终点
  5. int[] endPosition = new int[2];
  6. // 贝塞尔控制点
  7. int[] controlPosition = new int[2];
  8. // 当前位置
  9. float[] currentPosition = new float[2];
  10. // tablayout位置
  11. int[] tablayoutPosition = new int[2];
  12. startPosition = data.getStartPosition();
  13. mShoppingCart.getLocationInWindow(endPosition);
  14. mTabLayout.getLocationInWindow(tablayoutPosition);
  15. // 处理起点y坐标偏移的问题
  16. startPosition[1] = startPosition[1] - tablayoutPosition[1] - mTabLayout.getHeight();
  17. // 终点进行一下居中处理
  18. endPosition[0] = endPosition[0] + (mShoppingCart.getWidth() / 2);
  19. controlPosition[0] = endPosition[0];
  20. controlPosition[1] = startPosition[1];
  21. final ImageView imageView = new ImageView(this);
  22. mConView.addView(imageView);
  23. imageView.setImageResource(R.drawable.specialadd);
  24. imageView.getLayoutParams().width = getResources().getDimensionPixelSize(R.dimen.dp_px_30);
  25. imageView.getLayoutParams().height = getResources().getDimensionPixelSize(R.dimen.dp_px_30);
  26. imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
  27. imageView.setVisibility(View.VISIBLE);
  28. imageView.setX(startPosition[0]);
  29. imageView.setY(startPosition[1]);
  30. Path path = new Path();
  31. path.moveTo(startPosition[0], startPosition[1]);
  32. path.quadTo(controlPosition[0], controlPosition[1], endPosition[0], endPosition[1]);
  33. PathMeasure pathMeasure = new PathMeasure();
  34. // false表示path路径不闭合
  35. pathMeasure.setPath(path, false);
  36. // ofFloat是一个生成器
  37. ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, pathMeasure.getLength());
  38. // 匀速线性插值器
  39. valueAnimator.setInterpolator(new LinearInterpolator());
  40. valueAnimator.setDuration(800);
  41. valueAnimator.addUpdateListener(animation -> {
  42. float value = (Float) animation.getAnimatedValue();
  43. pathMeasure.getPosTan(value, currentPosition, null);
  44. imageView.setX(currentPosition[0]);
  45. imageView.setY(currentPosition[1]);
  46. });
  47. valueAnimator.start();
  48. ObjectAnimator shoppingCartX = ObjectAnimator.ofFloat(mShoppingCart, "scaleX", 1.0f, 1.3f, 1.0f);
  49. ObjectAnimator shoppingCartY = ObjectAnimator.ofFloat(mShoppingCart, "scaleY", 1.0f, 1.3f, 1.0f);
  50. shoppingCartX.setInterpolator(new AccelerateInterpolator());
  51. shoppingCartY.setInterpolator(new AccelerateInterpolator());
  52. AnimatorSet shoppingCart = new AnimatorSet();
  53. shoppingCart
  54. .play(shoppingCartX)
  55. .with(shoppingCartY);
  56. shoppingCart.setDuration(800);
  57. shoppingCart.start();
  58. valueAnimator.addListener(new Animator.AnimatorListener() {
  59. @Override
  60. public void onAnimationStart(Animator animation) {
  61. }
  62. //当动画结束后:
  63. @Override
  64. public void onAnimationEnd(Animator animation) {
  65. goodsChange(data);
  66. }
  67. @Override
  68. public void onAnimationCancel(Animator animation) {
  69. }
  70. @Override
  71. public void onAnimationRepeat(Animator animation) {
  72. }
  73. });
  74. }

四、大致写下布局(同时也算留做备份)

使用二阶贝塞尔曲线实现添加购物车动画 - 图3

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. ... ...>
  5. <RelativeLayout
  6. ... ...>
  7. 顶部常驻的toolbar
  8. </RelativeLayout>
  9. <android.support.design.widget.CoordinatorLayout
  10. android:layout_width="match_parent"
  11. android:layout_height="0dp"
  12. android:layout_weight="1">
  13. <android.support.design.widget.AppBarLayout
  14. ... ...>
  15. <android.support.design.widget.CollapsingToolbarLayout
  16. ... ...
  17. app:layout_scrollFlags="scroll|exitUntilCollapsed">
  18. <LinearLayout
  19. ... ...>
  20. TabLayout上面的View
  21. </LinearLayout>
  22. </android.support.design.widget.CollapsingToolbarLayout>
  23. <android.support.design.widget.TabLayout
  24. ... ... />
  25. </android.support.design.widget.AppBarLayout>
  26. <RelativeLayout
  27. ... ...
  28. android:fillViewport="true"
  29. app:layout_behavior="@string/appbar_scrolling_view_behavior">
  30. <android.support.v4.view.ViewPager
  31. android:id="@+id/view_pager"
  32. android:layout_width="match_parent"
  33. android:layout_height="match_parent" />
  34. </RelativeLayout>
  35. </android.support.design.widget.CoordinatorLayout>
  36. <LinearLayout
  37. ... ...>
  38. 最下面的购物车一栏
  39. </LinearLayout>
  40. </LinearLayout>

五、推荐资源

  1. View的位置参数

  2. Path之贝塞尔曲线