Android 利用二次贝塞尔曲线模仿购物车添加物品抛物线动画

0.首先,先给出一张效果gif图。


1.贝塞尔曲线原理及相关公式参考:http://www.jianshu.com/p/c0d7ad796cee 作者:许方镇 

2.原理:计算被点击 view、购物车view 以及他们所在父容器相对于屏幕的坐标。

3.在呗点击View坐标位置 父容器通过addView 增加需要完成动画的imgview。

4.自定义估值器 通过二次贝塞尔曲线公式(2个数据点,一个控制点)完成抛物线路径上的点xy坐标计算。

5.利用属性动画 +自定义估值器 完成imgview在父容器内部的抛物线动画。

6.先给布局,其中包含一个ListView、 一个ImageView 、需要用到的父容器。

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     xmlns:tools="http://schemas.android.com/tools"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     android:background="#00ffe1"  
  8.     android:orientation="vertical"  
  9.     android:paddingBottom="@dimen/activity_vertical_margin"  
  10.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  11.     android:paddingRight="@dimen/activity_horizontal_margin"  
  12.     android:paddingTop="@dimen/activity_vertical_margin"  
  13.     tools:context=".MainActivity">  
  14.   
  15.     <RelativeLayout  
  16.         android:id="@+id/main_container"  
  17.         android:layout_width="match_parent"  
  18.         android:layout_height="match_parent">  
  19.   
  20.         <ListView  
  21.             android:id="@+id/main_lv"  
  22.             android:layout_width="match_parent"  
  23.             android:layout_height="match_parent"  
  24.             android:divider="#0011ff"  
  25.             android:dividerHeight="2dp"/>  
  26.         <!-- shop img-->  
  27.         <ImageView  
  28.             android:id="@+id/main_img"  
  29.             android:layout_width="wrap_content"  
  30.             android:layout_height="wrap_content"  
  31.             android:layout_alignParentBottom="true"  
  32.             android:layout_marginBottom="20dp"  
  33.             android:layout_marginLeft="20dp"  
  34.             android:src="@mipmap/shop"/>  
  35.     </RelativeLayout>  
  36.   
  37. </LinearLayout>  

7. 给出ListView Item 布局:

[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:layout_width="match_parent"  
  4.                 android:layout_height="wrap_content"  
  5.                 android:background="#FFF"  
  6.                 android:padding="30dp">  
  7.   
  8.     <TextView  
  9.         android:id="@+id/item_text"  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content"  
  12.         android:layout_centerInParent="true"  
  13.         android:textColor="#F00"  
  14.         android:textSize="20sp"/>  
  15.   
  16.     <ImageView  
  17.         android:id="@+id/item_img"  
  18.         android:layout_width="wrap_content"  
  19.         android:layout_height="wrap_content"  
  20.         android:layout_alignParentRight="true"  
  21.         android:layout_centerVertical="true"  
  22.         android:src="@mipmap/add"/>  
  23.   
  24. </RelativeLayout>  

8.给出ListView Adapter代码 仅仅是点击时增加了回调接口:

[java]  view plain  copy
  1. public class ItemAdapter extends BaseAdapter implements View.OnClickListener {  
  2.     List<String> data = new ArrayList<>();  
  3.     Context mContext;  
  4.   
  5.     public ItemAdapter(Context context) {  
  6.         mContext = context;  
  7.         for (int i = 0; i < 30; i++) {  
  8.             data.add("item+" + i);  
  9.         }  
  10.     }  
  11.   
  12.     @Override  
  13.     public int getCount() {  
  14.         return data.size();  
  15.     }  
  16.   
  17.     @Override  
  18.     public Object getItem(int position) {  
  19.         return data.get(position);  
  20.     }  
  21.   
  22.     @Override  
  23.     public long getItemId(int position) {  
  24.         return position;  
  25.     }  
  26.   
  27.     @Override  
  28.     public View getView(int position, View convertView, ViewGroup parent) {  
  29.         if (convertView == null) {  
  30.             convertView = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false);  
  31.             convertView.setTag(new ViewH(convertView));  
  32.         }  
  33.         ViewH holder = (ViewH) convertView.getTag();  
  34.         holder.tv.setText(data.get(position));  
  35.         holder.img.setOnClickListener(this);  
  36.         return convertView;  
  37.     }  
  38.   
  39.     @Override  
  40.     public void onClick(View v) {  
  41.         if (mListener != null) {  
  42.             mListener.add(v);  
  43.         }  
  44.     }  
  45.   
  46.     private AddClickListener mListener;  
  47.   
  48.     public void setListener(AddClickListener listener) {  
  49.         mListener = listener;  
  50.     }  
  51.   
  52.     public interface AddClickListener {  
  53.         void add(View v);  
  54.     }  
  55.   
  56.     public static class ViewH {  
  57.         private ImageView img;  
  58.         private TextView tv;  
  59.   
  60.         public ViewH(View view) {  
  61.             img = ((ImageView) view.findViewById(R.id.item_img));  
  62.             tv = ((TextView) view.findViewById(R.id.item_text));  
  63.         }  
  64.     }  
  65. }  
9.其中自定义MoveImageView仅仅是增加了一个set方法方便属性动画 update时调用。

[java]  view plain  copy
  1. public class MoveImageView extends ImageView {  
  2.   
  3.     public MoveImageView(Context context) {  
  4.         super(context);  
  5.     }  
  6.   
  7.     public void setMPointF(PointF pointF) {  
  8.         setX(pointF.x);  
  9.         setY(pointF.y);  
  10.     }  
  11. }  


10.重要的实现在Activity部分:

[java]  view plain  copy
  1. public class MainActivity extends AppCompatActivity implements ItemAdapter.AddClickListener, Animator.AnimatorListener {  
  2.   
  3.     private ImageView shopImg;//购物车 IMG  
  4.     private RelativeLayout container;//ListView 购物车View的父布局  
  5.     private ListView itemLv;  
  6.   
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         super.onCreate(savedInstanceState);  
  10.         setContentView(R.layout.activity_main);  
  11.         findViews();  
  12.         initViews();  
  13.     }  
  14.   
  15.     private void initViews() {  
  16.         ItemAdapter adapter = new ItemAdapter(this);  
  17.         //当前Activity实现 adapter内部 点击的回调  
  18.         adapter.setListener(this);  
  19.         itemLv.setAdapter(adapter);  
  20.     }  
  21.   
  22.     /** 
  23.      * ListView + 点击回调方法 
  24.      */  
  25.     @Override  
  26.     public void add(View addV) {  
  27.         int[] childCoordinate = new int[2];  
  28.         int[] parentCoordinate = new int[2];  
  29.         int[] shopCoordinate = new int[2];  
  30.         //1.分别获取被点击View、父布局、购物车在屏幕上的坐标xy。  
  31.         addV.getLocationInWindow(childCoordinate);  
  32.         container.getLocationInWindow(parentCoordinate);  
  33.         shopImg.getLocationInWindow(shopCoordinate);  
  34.   
  35.         //2.自定义ImageView 继承ImageView  
  36.         MoveImageView img = new MoveImageView(this);  
  37.         img.setImageResource(R.mipmap.heart1);  
  38.         //3.设置img在父布局中的坐标位置  
  39.         img.setX(childCoordinate[0] - parentCoordinate[0]);  
  40.         img.setY(childCoordinate[1] - parentCoordinate[1]);  
  41.         //4.父布局添加该Img  
  42.         container.addView(img);  
  43.   
  44.         //5.利用 二次贝塞尔曲线 需首先计算出 MoveImageView的2个数据点和一个控制点  
  45.         PointF startP = new PointF();  
  46.         PointF endP = new PointF();  
  47.         PointF controlP = new PointF();  
  48.         //开始的数据点坐标就是 addV的坐标  
  49.         startP.x = childCoordinate[0] - parentCoordinate[0];  
  50.         startP.y = childCoordinate[1] - parentCoordinate[1];  
  51.         //结束的数据点坐标就是 shopImg的坐标  
  52.         endP.x = shopCoordinate[0] - parentCoordinate[0];  
  53.         endP.y = shopCoordinate[1] - parentCoordinate[1];  
  54.         //控制点坐标 x等于 购物车x;y等于 addV的y  
  55.         controlP.x = endP.x;  
  56.         controlP.y = startP.y;  
  57.   
  58.         //启动属性动画  
  59.         ObjectAnimator animator = ObjectAnimator.ofObject(img, "mPointF",  
  60.                 new PointFTypeEvaluator(controlP), startP, endP);  
  61.         animator.setDuration(1000);  
  62.         animator.addListener(this);  
  63.         animator.start();  
  64.     }  
  65.   
  66.     @Override  
  67.     public void onAnimationStart(Animator animation) {  
  68.     }  
  69.   
  70.     @Override  
  71.     public void onAnimationEnd(Animator animation) {  
  72.         //动画结束后 父布局移除 img  
  73.         Object target = ((ObjectAnimator) animation).getTarget();  
  74.         container.removeView((View) target);  
  75.         //shopImg 开始一个放大动画  
  76.         Animation scaleAnim = AnimationUtils.loadAnimation(this, R.anim.shop_scale);  
  77.         shopImg.startAnimation(scaleAnim);  
  78.     }  
  79.   
  80.     @Override  
  81.     public void onAnimationCancel(Animator animation) {  
  82.     }  
  83.   
  84.     @Override  
  85.     public void onAnimationRepeat(Animator animation) {  
  86.     }  
  87.   
  88.     /** 
  89.      * 自定义估值器 
  90.      */  
  91.     public class PointFTypeEvaluator implements TypeEvaluator<PointF> {  
  92.         /** 
  93.          * 每个估值器对应一个属性动画,每个属性动画仅对应唯一一个控制点 
  94.          */  
  95.         PointF control;  
  96.         /** 
  97.          * 估值器返回值 
  98.          */  
  99.         PointF mPointF = new PointF();  
  100.   
  101.         public PointFTypeEvaluator(PointF control) {  
  102.             this.control = control;  
  103.         }  
  104.   
  105.         @Override  
  106.         public PointF evaluate(float fraction, PointF startValue, PointF endValue) {  
  107.             return getBezierPoint(startValue, endValue, control, fraction);  
  108.         }  
  109.   
  110.         /** 
  111.          * 二次贝塞尔曲线公式 
  112.          * 
  113.          * @param start   开始的数据点 
  114.          * @param end     结束的数据点 
  115.          * @param control 控制点 
  116.          * @param t       float 0-1 
  117.          * @return 不同t对应的PointF 
  118.          */  
  119.         private PointF getBezierPoint(PointF start, PointF end, PointF control, float t) {  
  120.             mPointF.x = (1 - t) * (1 - t) * start.x + 2 * t * (1 - t) * control.x + t * t * end.x;  
  121.             mPointF.y = (1 - t) * (1 - t) * start.y + 2 * t * (1 - t) * control.y + t * t * end.y;  
  122.             return mPointF;  
  123.         }  
  124.     }  
  125.   
  126.     private void findViews() {  
  127.         shopImg = (ImageView) findViewById(R.id.main_img);  
  128.         container = (RelativeLayout) findViewById(R.id.main_container);  
  129.         itemLv = (ListView) findViewById(R.id.main_lv);  
  130.     }  
  131. }  

11.购物车有一个Scale的补间动画:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <set xmlns:android="http://schemas.android.com/apk/res/android"  
  3.      android:duration="200"  
  4.      android:repeatCount="1"  
  5.      android:repeatMode="reverse">  
  6.     <scale  
  7.         android:fromXScale="1.0"  
  8.         android:fromYScale="1.0"  
  9.         android:pivotX="50%"  
  10.         android:pivotY="50%"  
  11.         android:toXScale="1.2"  
  12.         android:toYScale="1.2"/>  
  13. </set>  

12.供参考~完~~
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值