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 、需要用到的父容器。
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#00ffe1"
- android:orientation="vertical"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context=".MainActivity">
- <RelativeLayout
- android:id="@+id/main_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <ListView
- android:id="@+id/main_lv"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:divider="#0011ff"
- android:dividerHeight="2dp"/>
- <!-- shop img-->
- <ImageView
- android:id="@+id/main_img"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_marginBottom="20dp"
- android:layout_marginLeft="20dp"
- android:src="@mipmap/shop"/>
- </RelativeLayout>
- </LinearLayout>
7. 给出ListView Item 布局:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="#FFF"
- android:padding="30dp">
- <TextView
- android:id="@+id/item_text"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:textColor="#F00"
- android:textSize="20sp"/>
- <ImageView
- android:id="@+id/item_img"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true"
- android:src="@mipmap/add"/>
- </RelativeLayout>
8.给出ListView Adapter代码 仅仅是点击时增加了回调接口:
- public class ItemAdapter extends BaseAdapter implements View.OnClickListener {
- List<String> data = new ArrayList<>();
- Context mContext;
- public ItemAdapter(Context context) {
- mContext = context;
- for (int i = 0; i < 30; i++) {
- data.add("item+" + i);
- }
- }
- @Override
- public int getCount() {
- return data.size();
- }
- @Override
- public Object getItem(int position) {
- return data.get(position);
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false);
- convertView.setTag(new ViewH(convertView));
- }
- ViewH holder = (ViewH) convertView.getTag();
- holder.tv.setText(data.get(position));
- holder.img.setOnClickListener(this);
- return convertView;
- }
- @Override
- public void onClick(View v) {
- if (mListener != null) {
- mListener.add(v);
- }
- }
- private AddClickListener mListener;
- public void setListener(AddClickListener listener) {
- mListener = listener;
- }
- public interface AddClickListener {
- void add(View v);
- }
- public static class ViewH {
- private ImageView img;
- private TextView tv;
- public ViewH(View view) {
- img = ((ImageView) view.findViewById(R.id.item_img));
- tv = ((TextView) view.findViewById(R.id.item_text));
- }
- }
- }
- public class MoveImageView extends ImageView {
- public MoveImageView(Context context) {
- super(context);
- }
- public void setMPointF(PointF pointF) {
- setX(pointF.x);
- setY(pointF.y);
- }
- }
10.重要的实现在Activity部分:
- public class MainActivity extends AppCompatActivity implements ItemAdapter.AddClickListener, Animator.AnimatorListener {
- private ImageView shopImg;//购物车 IMG
- private RelativeLayout container;//ListView 购物车View的父布局
- private ListView itemLv;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- findViews();
- initViews();
- }
- private void initViews() {
- ItemAdapter adapter = new ItemAdapter(this);
- //当前Activity实现 adapter内部 点击的回调
- adapter.setListener(this);
- itemLv.setAdapter(adapter);
- }
- /**
- * ListView + 点击回调方法
- */
- @Override
- public void add(View addV) {
- int[] childCoordinate = new int[2];
- int[] parentCoordinate = new int[2];
- int[] shopCoordinate = new int[2];
- //1.分别获取被点击View、父布局、购物车在屏幕上的坐标xy。
- addV.getLocationInWindow(childCoordinate);
- container.getLocationInWindow(parentCoordinate);
- shopImg.getLocationInWindow(shopCoordinate);
- //2.自定义ImageView 继承ImageView
- MoveImageView img = new MoveImageView(this);
- img.setImageResource(R.mipmap.heart1);
- //3.设置img在父布局中的坐标位置
- img.setX(childCoordinate[0] - parentCoordinate[0]);
- img.setY(childCoordinate[1] - parentCoordinate[1]);
- //4.父布局添加该Img
- container.addView(img);
- //5.利用 二次贝塞尔曲线 需首先计算出 MoveImageView的2个数据点和一个控制点
- PointF startP = new PointF();
- PointF endP = new PointF();
- PointF controlP = new PointF();
- //开始的数据点坐标就是 addV的坐标
- startP.x = childCoordinate[0] - parentCoordinate[0];
- startP.y = childCoordinate[1] - parentCoordinate[1];
- //结束的数据点坐标就是 shopImg的坐标
- endP.x = shopCoordinate[0] - parentCoordinate[0];
- endP.y = shopCoordinate[1] - parentCoordinate[1];
- //控制点坐标 x等于 购物车x;y等于 addV的y
- controlP.x = endP.x;
- controlP.y = startP.y;
- //启动属性动画
- ObjectAnimator animator = ObjectAnimator.ofObject(img, "mPointF",
- new PointFTypeEvaluator(controlP), startP, endP);
- animator.setDuration(1000);
- animator.addListener(this);
- animator.start();
- }
- @Override
- public void onAnimationStart(Animator animation) {
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- //动画结束后 父布局移除 img
- Object target = ((ObjectAnimator) animation).getTarget();
- container.removeView((View) target);
- //shopImg 开始一个放大动画
- Animation scaleAnim = AnimationUtils.loadAnimation(this, R.anim.shop_scale);
- shopImg.startAnimation(scaleAnim);
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- }
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
- /**
- * 自定义估值器
- */
- public class PointFTypeEvaluator implements TypeEvaluator<PointF> {
- /**
- * 每个估值器对应一个属性动画,每个属性动画仅对应唯一一个控制点
- */
- PointF control;
- /**
- * 估值器返回值
- */
- PointF mPointF = new PointF();
- public PointFTypeEvaluator(PointF control) {
- this.control = control;
- }
- @Override
- public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
- return getBezierPoint(startValue, endValue, control, fraction);
- }
- /**
- * 二次贝塞尔曲线公式
- *
- * @param start 开始的数据点
- * @param end 结束的数据点
- * @param control 控制点
- * @param t float 0-1
- * @return 不同t对应的PointF
- */
- private PointF getBezierPoint(PointF start, PointF end, PointF control, float t) {
- mPointF.x = (1 - t) * (1 - t) * start.x + 2 * t * (1 - t) * control.x + t * t * end.x;
- mPointF.y = (1 - t) * (1 - t) * start.y + 2 * t * (1 - t) * control.y + t * t * end.y;
- return mPointF;
- }
- }
- private void findViews() {
- shopImg = (ImageView) findViewById(R.id.main_img);
- container = (RelativeLayout) findViewById(R.id.main_container);
- itemLv = (ListView) findViewById(R.id.main_lv);
- }
- }
11.购物车有一个Scale的补间动画:
- <?xml version="1.0" encoding="utf-8"?>
- <set xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="200"
- android:repeatCount="1"
- android:repeatMode="reverse">
- <scale
- android:fromXScale="1.0"
- android:fromYScale="1.0"
- android:pivotX="50%"
- android:pivotY="50%"
- android:toXScale="1.2"
- android:toYScale="1.2"/>
- </set>
12.供参考~完~~