贝塞尔曲线之购物车动画效果

前面的话,前阵子看了贝塞尔曲线,属于canvas的一个方法,这篇博客只是个实践,觉得看起来好上手们其实做一遍可以很好地熟悉贝塞尔曲线的运用。


本文属于转载,出处是http://blog.csdn.net/shineflowers/article/details/53931527。

Question

  • 贝塞尔曲线是什么?
  • 贝塞尔曲线可以做什么?
  • 怎么做?

What is it ?

贝塞尔曲线在百度定义是贝塞尔曲线(Bézier curve),又称 贝兹 曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。

Usage

贝塞尔曲线根据不同点实现不同动态效果:

  • 一阶贝塞尔曲线(两点),绘制成一条直线

  • 二阶贝塞尔曲线(三点)

  • 三阶贝塞尔曲线(四点)

  • 四阶贝塞尔曲线(五点)

  • 五阶贝塞尔曲线(六点)

看了上面贝塞尔曲线不同点不同效果后,相信大家都清楚贝塞尔曲线能干什么?没错,贝塞尔曲线能造高逼格动画

就笔者目前了解的采用贝塞尔曲线实现的知名开源项目有:

  • QQ拖拽清除效果

  • 纸飞机刷新动画

  • 滴油刷新动画

  • 波浪动画

到此大家是不是很兴奋,想更多了解如何造一个高逼格贝塞尔曲线动画。接下来我就给大家讲述如何造一个基于贝塞尔曲线实现的购物车动画,大家擦亮眼睛啦~~

How to do it ?

思路

  • 确定动画起终点

  • 在起终点之间使用二次贝塞尔曲线填充起终点之间点的轨迹

  • 设置属性动画,ValueAnimator插值器,获取中间点的坐标

  • 将执行动画控件的x、y坐标设为上面得到的中间点坐标

  • 开启属性动画

  • 当动画结束时的操作

知识点

  • Android中提供了绘制一阶、二阶、三阶的接口:
    • 一阶接口:
      public void lineTo(float x,float y)
    • 二阶接口:
      public void quadTo(float x1, float y1, float x2, float y2)
    • 三阶接口:
      public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
  • PathMeasure使用

    • getLength()

    • 理解 boolean getPosTan(float distance, float[] pos, float[] tan)

  • 如何获取控件在屏幕中的绝对坐标

    • int[] location = new int[2]; view.getLocationInWindow(location); 得到view在屏幕中的绝对坐标。

  • 理解属性动画插值器ValueAnimator

Code

首先写购物车布局xml,代码如下:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/rly_bezier_curve_shopping_cart"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:paddingBottom="@dimen/activity_vertical_margin"  
  7.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  8.     android:paddingRight="@dimen/activity_horizontal_margin"  
  9.     android:paddingTop="@dimen/activity_vertical_margin">  
  10.   
  11.     <FrameLayout  
  12.         android:id="@+id/fly_bezier_curve_shopping_cart"  
  13.         android:layout_width="match_parent"  
  14.         android:layout_height="wrap_content"  
  15.         android:layout_alignParentBottom="true"  
  16.         android:layout_alignParentLeft="true"  
  17.         android:paddingRight="30dp"  
  18.         android:layout_alignParentStart="true">  
  19.         <ImageView  
  20.             android:id="@+id/iv_bezier_curve_shopping_cart"  
  21.             android:layout_width="40dp"  
  22.             android:layout_height="40dp"  
  23.             android:layout_gravity="right"  
  24.             android:src="@drawable/menu_shop_car_selected" />  
  25.         <TextView  
  26.             android:id="@+id/tv_bezier_curve_shopping_cart_count"  
  27.             android:layout_width="wrap_content"  
  28.             android:layout_height="wrap_content"  
  29.             android:textColor="@color/white"  
  30.             android:background="@drawable/corner_view"  
  31.             android:text="0"  
  32.             android:layout_gravity="right"/>  
  33.     </FrameLayout>  
  34.   
  35.     <ListView  
  36.         android:id="@+id/lv_bezier_curve_shopping_cart"  
  37.         android:layout_width="match_parent"  
  38.         android:layout_height="match_parent"  
  39.         android:layout_above="@+id/fly_bezier_curve_shopping_cart"/>  
  40. </RelativeLayout>  

然后写购物车适配器、实体类,代码如下:

[java]  view plain  copy
  1. /** 
  2.  * @className: GoodsAdapter 
  3.  * @classDescription: 购物车商品适配器 
  4.  * @author: leibing 
  5.  * @createTime: 2016/09/28 
  6.  */  
  7. public class GoodsAdapter extends BaseAdapter {  
  8.     // 数据源(购物车商品图片)  
  9.     private ArrayList<GoodsModel> mData;  
  10.     // 布局  
  11.     private LayoutInflater mLayoutInflater;  
  12.     // 回调监听  
  13.     private CallBackListener mCallBackListener;  
  14.   
  15.     /** 
  16.      * 构造函数 
  17.      * @author leibing 
  18.      * @createTime 2016/09/28 
  19.      * @lastModify 2016/09/28 
  20.      * @param context 上下文 
  21.      * @param mData 数据源(购物车商品图片) 
  22.      * @return 
  23.      */  
  24.     public GoodsAdapter(Context context, ArrayList<GoodsModel> mData){  
  25.         mLayoutInflater = LayoutInflater.from(context);  
  26.         this.mData = mData;  
  27.     }  
  28.   
  29.     @Override  
  30.     public int getCount() {  
  31.         return mData != null ? mData.size(): 0;  
  32.     }  
  33.   
  34.     @Override  
  35.     public Object getItem(int i) {  
  36.         return mData != null ? mData.get(i): null;  
  37.     }  
  38.   
  39.     @Override  
  40.     public long getItemId(int i) {  
  41.         return i;  
  42.     }  
  43.   
  44.     @Override  
  45.     public View getView(int i, View view, ViewGroup viewGroup) {  
  46.         ViewHolder viewHolder;  
  47.         if (view == null){  
  48.             view = mLayoutInflater.inflate(R.layout.adapter_shopping_cart_item, null);  
  49.             viewHolder = new ViewHolder(view);  
  50.             view.setTag(viewHolder);  
  51.         }else {  
  52.             // 复用ViewHolder  
  53.             viewHolder = (ViewHolder) view.getTag();  
  54.         }  
  55.   
  56.         // 更新UI  
  57.         if (i < mData.size())  
  58.             viewHolder.updateUI(mData.get(i));  
  59.   
  60.         return view;  
  61.     }  
  62.   
  63.     /** 
  64.      * @className: ViewHolder 
  65.      * @classDescription: 商品ViewHolder 
  66.      * @author: leibing 
  67.      * @createTime: 2016/09/28 
  68.      */  
  69.     class  ViewHolder {  
  70.         // 显示商品图片  
  71.         private ImageView mShoppingCartItemIv;  
  72.   
  73.         /** 
  74.          * 构造函数 
  75.          * @author leibing 
  76.          * @createTime 2016/09/28 
  77.          * @lastModify 2016/09/28 
  78.          * @param view 视图 
  79.          * @return 
  80.          */  
  81.         public ViewHolder(View view){  
  82.             // findView  
  83.             mShoppingCartItemIv = (ImageView) view.findViewById(R.id.iv_shopping_cart_item);  
  84.             // onClick  
  85.             view.findViewById(R.id.tv_shopping_cart_item).setOnClickListener(  
  86.                     new View.OnClickListener() {  
  87.                 @Override  
  88.                 public void onClick(View view) {  
  89.                     if (mShoppingCartItemIv != null && mCallBackListener != null)  
  90.                         mCallBackListener.callBackImg(mShoppingCartItemIv);  
  91.                 }  
  92.             });  
  93.         }  
  94.   
  95.         /** 
  96.          * 更新UI 
  97.          * @author leibing 
  98.          * @createTime 2016/09/28 
  99.          * @lastModify 2016/09/28 
  100.          * @param goods 商品实体对象 
  101.          * @return 
  102.          */  
  103.         public void updateUI(GoodsModel goods){  
  104.             if (goods != null  
  105.                     && goods.getmGoodsBitmap() != null  
  106.                     && mShoppingCartItemIv != null)  
  107.                 mShoppingCartItemIv.setImageBitmap(goods.getmGoodsBitmap());  
  108.         }  
  109.     }  
  110.   
  111.     /** 
  112.      * 设置回调监听 
  113.      * @author leibing 
  114.      * @createTime 2016/09/28 
  115.      * @lastModify 2016/09/28 
  116.      * @param mCallBackListener 回调监听 
  117.      * @return 
  118.      */  
  119.     public void setCallBackListener(CallBackListener mCallBackListener){  
  120.         this.mCallBackListener = mCallBackListener;  
  121.     }  
  122.   
  123.     /** 
  124.      * @interfaceName: CallBackListener 
  125.      * @interfaceDescription: 回调监听 
  126.      * @author: leibing 
  127.      * @createTime: 2016/09/28 
  128.      */  
  129.     public interface CallBackListener{  
  130.         void callBackImg(ImageView goodsImg);  
  131.     }  
  132. }  
然后写添加数据源以及设置适配器,代码如下:
[java]  view plain  copy
  1. // 购物车父布局  
  2. private RelativeLayout mShoppingCartRly;  
  3. // 购物车列表显示  
  4. private ListView mShoppingCartLv;  
  5. // 购物数目显示  
  6. private TextView mShoppingCartCountTv;  
  7. // 购物车图片显示  
  8. private ImageView mShoppingCartIv;  
  9. // 购物车适配器  
  10. private GoodsAdapter mGoodsAdapter;  
  11. // 数据源(购物车商品图片)  
  12. private ArrayList<GoodsModel> mData;  
  13. // 贝塞尔曲线中间过程点坐标  
  14. private float[] mCurrentPosition = new float[2];  
  15. // 路径测量  
  16. private PathMeasure mPathMeasure;  
  17. // 购物车商品数目  
  18. private int goodsCount = 0;  
  19.   
  20. @Override  
  21. protected void onCreate(Bundle savedInstanceState) {  
  22.     super.onCreate(savedInstanceState);  
  23.     setContentView(R.layout.activity_main);  
  24.     // findView  
  25.     mShoppingCartLv = (ListView) findViewById(R.id.lv_bezier_curve_shopping_cart);  
  26.     mShoppingCartCountTv = (TextView) findViewById(R.id.tv_bezier_curve_shopping_cart_count);  
  27.     mShoppingCartRly = (RelativeLayout) findViewById(R.id.rly_bezier_curve_shopping_cart);  
  28.     mShoppingCartIv = (ImageView) findViewById(R.id.iv_bezier_curve_shopping_cart);  
  29.     // 是否显示购物车商品数目  
  30.     isShowCartGoodsCount();  
  31.     // 添加数据源  
  32.     addData();  
  33.     // 设置适配器  
  34.     setAdapter();  
  35. }  
  36.   
  37. /** 
  38.  * 设置适配器 
  39.  * @author leibing 
  40.  * @createTime 2016/09/28 
  41.  * @lastModify 2016/09/28 
  42.  * @param 
  43.  * @return 
  44.  */  
  45. private void setAdapter() {  
  46.     // 初始化适配器  
  47.     mGoodsAdapter = new GoodsAdapter(this, mData);  
  48.     // 设置适配器监听  
  49.     mGoodsAdapter.setCallBackListener(new GoodsAdapter.CallBackListener() {  
  50.         @Override  
  51.         public void callBackImg(ImageView goodsImg) {  
  52.             // 添加商品到购物车  
  53.             addGoodsToCart(goodsImg);  
  54.         }  
  55.     });  
  56.     // 设置适配器  
  57.     mShoppingCartLv.setAdapter(mGoodsAdapter);  
  58. }  
接下来写最重要的一块,添加商品到购物车,代码如下:
[java]  view plain  copy
  1. /** 
  2.      * 添加商品到购物车 
  3.      * @author leibing 
  4.      * @createTime 2016/09/28 
  5.      * @lastModify 2016/09/28 
  6.      * @param goodsImg 商品图标 
  7.      * @return 
  8.      */  
  9.     private void addGoodsToCart(ImageView goodsImg) {  
  10.         // 创造出执行动画的主题goodsImg(这个图片就是执行动画的图片,从开始位置出发,经过一个抛物线(贝塞尔曲线),移动到购物车里)  
  11.         final ImageView goods = new ImageView(this);  
  12.         goods.setImageDrawable(goodsImg.getDrawable());  
  13.         RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(100100);  
  14.         mShoppingCartRly.addView(goods, params);  
  15.   
  16.         // 得到父布局的起始点坐标(用于辅助计算动画开始/结束时的点的坐标)  
  17.         int[] parentLocation = new int[2];  
  18.         mShoppingCartRly.getLocationInWindow(parentLocation);  
  19.   
  20.         // 得到商品图片的坐标(用于计算动画开始的坐标)  
  21.         int startLoc[] = new int[2];  
  22.         goodsImg.getLocationInWindow(startLoc);  
  23.   
  24.         // 得到购物车图片的坐标(用于计算动画结束后的坐标)  
  25.         int endLoc[] = new int[2];  
  26.         mShoppingCartIv.getLocationInWindow(endLoc);  
  27.   
  28.         // 开始掉落的商品的起始点:商品起始点-父布局起始点+该商品图片的一半  
  29.         float startX = startLoc[0] - parentLocation[0] + goodsImg.getWidth() / 2;  
  30.         float startY = startLoc[1] - parentLocation[1] + goodsImg.getHeight() / 2;  
  31.   
  32.         // 商品掉落后的终点坐标:购物车起始点-父布局起始点+购物车图片的1/5  
  33.         float toX = endLoc[0] - parentLocation[0] + mShoppingCartIv.getWidth() / 5;  
  34.         float toY = endLoc[1] - parentLocation[1];  
  35.   
  36.         // 开始绘制贝塞尔曲线  
  37.         Path path = new Path();  
  38.         // 移动到起始点(贝塞尔曲线的起点)  
  39.         path.moveTo(startX, startY);  
  40.         // 使用二阶贝塞尔曲线:注意第一个起始坐标越大,贝塞尔曲线的横向距离就会越大,一般按照下面的式子取即可  
  41.         path.quadTo((startX + toX) / 2, startY, toX, toY);  
  42.         // mPathMeasure用来计算贝塞尔曲线的曲线长度和贝塞尔曲线中间插值的坐标,如果是true,path会形成一个闭环  
  43.         mPathMeasure = new PathMeasure(path, false);  
  44.   
  45.         // 属性动画实现(从0到贝塞尔曲线的长度之间进行插值计算,获取中间过程的距离值)  
  46.         ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());  
  47.         valueAnimator.setDuration(500);  
  48.   
  49.         // 匀速线性插值器  
  50.         valueAnimator.setInterpolator(new LinearInterpolator());  
  51.         valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
  52.             @Override  
  53.             public void onAnimationUpdate(ValueAnimator animation) {  
  54.                 // 当插值计算进行时,获取中间的每个值,  
  55.                 // 这里这个值是中间过程中的曲线长度(下面根据这个值来得出中间点的坐标值)  
  56.                 float value = (Float) animation.getAnimatedValue();  
  57.                 // 获取当前点坐标封装到mCurrentPosition  
  58.                 // boolean getPosTan(float distance, float[] pos, float[] tan) :  
  59.                 // 传入一个距离distance(0<=distance<=getLength()),然后会计算当前距离的坐标点和切线,pos会自动填充上坐标,这个方法很重要。  
  60.                 // mCurrentPosition此时就是中间距离点的坐标值  
  61.                 mPathMeasure.getPosTan(value, mCurrentPosition, null);  
  62.                 // 移动的商品图片(动画图片)的坐标设置为该中间点的坐标  
  63.                 goods.setTranslationX(mCurrentPosition[0]);  
  64.                 goods.setTranslationY(mCurrentPosition[1]);  
  65.             }  
  66.         });  
  67.   
  68.         // 开始执行动画  
  69.         valueAnimator.start();  
  70.   
  71.         // 动画结束后的处理  
  72.         valueAnimator.addListener(new Animator.AnimatorListener() {  
  73.             @Override  
  74.             public void onAnimationStart(Animator animation) {  
  75.             }  
  76.   
  77.             @Override  
  78.             public void onAnimationEnd(Animator animation) {  
  79.                 // 购物车商品数量加1  
  80.                 goodsCount ++;  
  81.                 isShowCartGoodsCount();  
  82.                 mShoppingCartCountTv.setText(String.valueOf(goodsCount));  
  83.                 // 把执行动画的商品图片从父布局中移除  
  84.                 mShoppingCartRly.removeView(goods);  
  85.             }  
  86.   
  87.             @Override  
  88.             public void onAnimationCancel(Animator animation) {  
  89.             }  
  90.   
  91.             @Override  
  92.             public void onAnimationRepeat(Animator animation) {  
  93.             }  
  94.         });  
  95.     }  

代码分析完毕,一个高逼格贝塞尔曲线实现的购物车添加商品动画效果实现分析完毕~~

效果图如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值