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

前面的话,前阵子看了贝塞尔曲线,属于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.     }  

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

效果图如下:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章