貝塞爾曲線之購物車動畫效果

前面的話,前陣子看了貝塞爾曲線,屬於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.     }  

代碼分析完畢,一個高逼格貝塞爾曲線實現的購物車添加商品動畫效果實現分析完畢~~

效果圖如下:


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