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));
        }
    }
}

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 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.供参考~完~~

时间: 2024-07-30 08:18:40

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

css贝塞尔曲线模仿饿了么购物车小球动画

在线观看贝塞尔曲线值:传送门 在线观看动画效果:传送门 代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> <style> html{ widt

canvas绘制二次贝塞尔曲线----演示二次贝塞尔四个参数的作用

canvas中绘制二次贝塞尔曲线的方法为ctx.quadraticCurveTo(x1,y1,x2,y2); 四个参数分别为两个控制点的坐标.开始点即当前canvas中目前的点,如果想从指定的点开始,需要使用ctx.moveTo(x,y)方法 演示效果如下图 上代码: html <!doctype html> <html> <head> <meta charset="utf-8"> <title>无标题文档</title

二次贝塞尔曲线公式及导函数

二次贝塞尔曲线公式: B(t)=(1-t)2P0+2t(1-t)P1+t2P2,t∈[0,1] 二次贝塞尔曲线导函数: y,=2((1-t)(P1-P0)+t(P2-P1)) 要让二次贝塞尔曲线过Pt点,则控制点应为 Pc=2Pt-(P0+P2)/2 两直线的斜率分别为K1.K2,则夹角θ有 tanθ=|(k2- k1)/(1+ k1k2)|

Bezier贝塞尔曲线的原理、二次贝塞尔曲线的实现

Bezier曲线的原理 Bezier曲线是应用于二维图形的曲线.曲线由顶点和控制点组成,通过改变控制点坐标可以改变曲线的形状. 一次Bezier曲线公式: 一次Bezier曲线是由P0至P1的连续点,描述的一条线段 二次Bezier曲线公式: 二次Bezier曲线是 P0至P1 的连续点Q0和P1至P2 的连续点Q1 组成的线段上的连续点B(t),描述一条抛物线. 三次Bezier曲线公式: 二次Bezier曲线的实现 #include <vector> class CBezierCurve

贝塞尔曲线实现购物车飞入效果

代码地址如下:http://www.demodashi.com/demo/12618.html 前言 做了一个模仿添加物品飞入购物车效果的例子,下面来讲讲它的简单使用 将涉及到以下内容: 工具类的使用 项目结构图与效果图 程序设计与实现 一. 工具类设计 工具类比较多代码,这里就不每个都贴出来了,如下截图为工具类 二. 工具类的使用 飞入购物车效果我封装成了一个工具类FlyAnimtor,下面讲讲它的使用. 如果你想使用飞入效果,你可以这样写: FlyAnimtor.getInstance().

Android自定义View进阶 - 贝塞尔曲线

Path之贝塞尔曲线 作者微博: @GcsSloop [本系列相关文章] 在上一篇文章Path之基本图形中我们了解了Path的基本使用方法,本次了解Path中非常非常非常重要的内容-贝塞尔曲线. 一.Path常用方法表 为了兼容性(偷懒) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法.忍不住吐槽一下,为啥看起来有些顺手就能写的重载方法要等到API21才添加上啊.宝宝此刻内心也是崩溃的. 作用 相关方法 备注 移动起点 moveTo 移动下一次操作的起点位置 设置终点 setLa

IOS贝塞尔曲线圆形进度条和加载动画

做项目让做一个加载动画,一个圈圈在转中间加一个图片,网上有好多demo,这里我也自己写了一个,中间的图片可加可不加.其中主要用到贝塞尔曲线.UIBezierPath是对CGContextRef的进一步封装,不多说直接上代码: #import <UIKit/UIKit.h> @interface CircleLoader : UIView //进度颜色 @property(nonatomic, retain) UIColor* progressTintColor ; //轨道颜色 @proper

Android自定义视图三:给自定义视图添加“流畅”的动画

在第二部分我们实现了一个简单的折线图.这里假设你已经读了前篇.下面我们将继续为这个折线图添砖加瓦. 我在想给这个图的上方添加三个按钮,这样用户可以点选不同的按钮来查看不同类别的数据.比如,用户可以查看走路的.跑步的和骑车的.用户点不同的按钮,我们就跟还不同的运动数据显示在图形里. 我们实现了按钮点击后,设置不同的坐标点数据,然后运行APP.你会发现,虽然方法setChartData()已经被调用了,但是图形一点变化都没有.为什么呢?因为我们没有通知折线图"重绘".这可以通过调用inva

canvas 绘制二次贝塞尔曲线

代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>canvas</title> <script> function init() { var canvas=document.getElementById('canvas'); var ctx=canvas.getContext('2d')