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

欠伸展肢体,吟咏心自愉。这篇文章主要讲述Android 利用二次贝塞尔曲线模仿购物车添加物品抛物线动画相关的知识,希望能为你提供帮助。
android 利用二次贝塞尔曲线模仿购物车添加物品抛物线动画



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

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

文章图片


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=https://www.songbingjia.com/android/" @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=https://www.songbingjia.com/android/" @mipmap/add" /> < /RelativeLayout>


8.给出ListView Adapter代码 仅仅是点击时增加了回调接口:
public class ItemAdapter extends BaseAdapter implements View.OnClickListener { List< String> data = https://www.songbingjia.com/android/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)); } } }

9.其中自定义MoveImageView仅仅是增加了一个set方法方便属性动画 update时调用。
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 tfloat 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.供参考~完~~
【Android 利用二次贝塞尔曲线模仿购物车添加物品抛物线动画】












    推荐阅读