两种方式实现购物车动画

公司项目有添加商品到购物车的需求,需要一个添加商品的动画效果。参照了一些当下主流APP的效果,最后实现了以下效果:

这里写图片描述

点击Item,显示点击第几项;点击购买,添加商品到购物车,同时购物车商品总数加一。

实现过程:
首先是商品添加到购物车的轨迹,类似于一条抛物线,好在Android已经为我们提供了相关的方法–Path类(封装了贝塞尔曲线)。具体关于贝塞尔曲线,大家可以自行百度。这里我们主要研究Path为我们提供的构造路径的方法。

1.moveTo(float,float)
用于设置移动路径的起始点Point(x,y),对于android系统来说,屏幕的左上角的坐标是 (0,0) , 我们在做一些操作的时候默认基准点也是 (0,0)。Path 的moveTo 方法可以与此进行一个类比,就是为了改变 Path 的起始点。
2.quadTo(float x1, float y1, float x2, float y2 )
android 只对低阶贝塞尔曲线进行了封装,这是用于设置二次贝塞尔曲线的方法,先上图说明:
这里写图片描述

x3、y3 代表控制点的 x、y,即动态图中的P1,x2、y2 代表目标点的 x、y,即动态图中的P2。绘制路径轨迹已经找到了对应的类与方法,接下来就是在自己项目里的具体应用了。如下图:
这里写图片描述

(x0,y0)代表父布局的坐标,(x1,y1)代表商品,(x2,y2)代表购物车,(x3,y3)代表控制点。需要的点已经确定好,接下来就是代码实现了:

public class SecondActivity extends AppCompatActivity implements addListener {

    private int i;
    private TextView txt;
    private ImageView cartImg;
    private RelativeLayout relativeLayout;
    private ListView list;
    private LayoutInflater inflater;
    private ListAdapter adapter;
    private int[] imgs = new int[]{R.drawable.cake, R.drawable.milk, R.drawable.coffee, R.drawable.kettle, R.drawable.mobile};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        initviews();
    }

    private void initviews() {
        relativeLayout = (RelativeLayout) findViewById(R.id.rl);
        txt = (TextView) findViewById(R.id.second_txt);
        cartImg = (ImageView) findViewById(R.id.cart_img);
        inflater = LayoutInflater.from(this);
        list = (ListView) findViewById(R.id.list);
        adapter = new ListAdapter();
        adapter.setListener(this);
        list.setAdapter(adapter);
        list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(SecondActivity.this, "你点击了第" + String.valueOf(position + 1) + "项", Toast.LENGTH_SHORT).show();
            }
        });

    }

    public class ListAdapter extends BaseAdapter {
        private addListener listener;
        public void setListener(addListener listener) {
            this.listener = listener;
        }
        @Override
        public int getCount() {
            if (imgs != null) {
                return imgs.length;
            } else {
                return 0;
            }

        }

        @Override
        public Object getItem(int position) {
            return (position);
        }

        @Override
        public long getItemId(int id) {
            // TODO Auto-generated method stub
            return id;
        }

        @Override
        public View getView(final int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            if (convertView == null) {
                convertView = inflater.inflate(R.layout.list_item, null);
                viewHolder = new ViewHolder();
                viewHolder.itemimg = (ImageView) convertView.findViewById(R.id.item_img);
                viewHolder.itemtxt = (TextView) convertView.findViewById(R.id.item_txt);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            viewHolder.itemimg.setImageResource(imgs[position]);
            viewHolder.itemtxt.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    listener.addCart(position, viewHolder.itemimg);
                }
            });
            return convertView;
        }

        public class ViewHolder {
            public ImageView itemimg;
            public TextView itemtxt;
        }

    }

数据的准备,Item的单击事件,接口回调处理“购买”点击事件

这里需要拿到具体商品的图片进行动画处理,传递了一个ImageView过去,Position则是方便我们进行其他具体的业务处理。接下来就是最重要的动画实现了:

        //得到起始点坐标
        int parentLoc[] = new int[2];
        relativeLayout.getLocationInWindow(parentLoc);
        int startLoc[] = new int[2];
        imgview.getLocationInWindow(startLoc);
        int endLoc[] = new int[2];
        cartImg.getLocationInWindow(endLoc);

getLocationInWindow :获取该视图在整个窗口内的绝对坐,parentLoc [0]代表x坐标,parentLoc [1]代表y坐标。

        float startX = startLoc[0] - parentLoc[0] + imgview.getWidth() / 2;
        float startY = startLoc[1] - parentLoc[1] + imgview.getHeight() / 2;
        float toX = endLoc[0] - parentLoc[0] + cartImg.getWidth() / 3;
        float toY = endLoc[1] - parentLoc[1];

通过起始点坐标计算出控制点与目标点的坐标。

        final ImageView goods = new ImageView(getApplicationContext());
        goods.setImageDrawable(imgview.getDrawable());
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(60, 60);
        relativeLayout.addView(goods, params);

动态在父布局中添加一个执行添加动画的视图,也就是效果图中的商品缩略图。之前自己用的是传递过来的ImageView,发现每次动画一执行,Item的图片相应也会消失。所以用这种方法来替代这个商品,记住最后在动画完成的时候将父布局中动态添加的这个view移除即可。

        Path path = new Path();
        path.moveTo(startX, startY);
        path.quadTo((startX + toX) / 2, startY, toX, toY);

调用Path类对应的方法模拟出这一条抛物线

现在路径曲线有了,还需要一个非常重要的辅助类:路径测量PathMeasure,无论Path路径多么复杂,PathMeasure也会将所有path中的路径看成一个直线,取出某一点的位置,然后计算出对应的坐标。

构造方法:
PathMeasure(Path path, boolean forceClosed)
常用方法:
float getLength() :测量path的长度
boolean getPosTan(float distance, float[] pos, float[] tan) :传入一个距离distance(0<=distance<=getLength()),然后会计算当前距离的坐标点和切线,pos会自动填充上坐标,这个方法很重要。

路径上每一点的坐标都能够获取到,接下来就是动画的实现了,其实就是商品每次根据不同的点坐标移动到不同的位置,这样就实现了想要的效果。我这里用的是自定义的一个动画:

/**
 * Created by tangyangkai on 16/4/20.
 */
public class PathAnimation extends Animation {

    private PathMeasure measure;
    private float[] pos = new float[2];

    public PathAnimation(Path path) {
        measure = new PathMeasure(path, false);
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        measure.getPosTan(measure.getLength() * interpolatedTime, pos, null);
        t.getMatrix().setTranslate(pos[0], pos[1]);

    }
}

通过重写Animation的 applyTransformation (float interpolatedTime, Transformation t)函数来实现自定义动画效果。在绘制动画的过程中会反复的调用applyTransformation 函数,每次调用参数interpolatedTime值都会变化,该参数从0渐变为measure.getLength() ,当该参数为measure.getLength() 时表明动画结束。通过参数Transformation 来获取变换的矩阵(matrix),通过改变矩阵就可以实现各种复杂的效果。
通过getMatrix().setTranslate函数来实现移动,该函数的两个参数代表商品的x坐标与y坐标,由于interpolatedTime是从0到measure.getLength() 变化,所在这里实现的效果就是商品会沿着制定的路径进行移动。

PathAnimation animation = new PathAnimation(path);
        animation.setDuration(1000);
        animation.setInterpolator(new LinearInterpolator());
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                i++;
                txt.setText(String.valueOf(i));
                relativeLayout.removeView(goods);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

        goods.startAnimation(animation);

自定义动画完成,然后就是调用。依次设置动画持续时间,匀速动画线性插值器,动画监听。记住在动画完成的时候,将商品数量加一,同时移除动态添加的view。最后开启动画,达到最后的效果。

参考资料:

http://blog.csdn.net/tianjian4592/article/details/47067161
http://blog.csdn.net/gucun4848/article/details/8459280

忙着出效果,所以没有研究用属性动画来实现,有时间会去完成的,然后更新博客。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~分割线

之前说到想使用属性动画来实现,周末回去好好看了看这方面的知识,最后一样的效果,不同的动画实现。废话不多说,看代码(记得设置成局部变量,不然会出现重叠现象):

final PathMeasure mPathMeasure= new PathMeasure(path, false);
  final float[] mCurrentPosition = new float[2];

路径测量辅助类path measure,数组存放x,y坐标

//添加购物车动画实现
    @Override
    public void addCart(int position, ImageView imgview) {
        //得到起始点坐标
        int parentLoc[] = new int[2];
        relativeLayout.getLocationInWindow(parentLoc);
        int startLoc[] = new int[2];
        imgview.getLocationInWindow(startLoc);
        int endLoc[] = new int[2];
        cartImg.getLocationInWindow(endLoc);


        final ImageView goods = new ImageView(getApplicationContext());
        goods.setImageDrawable(imgview.getDrawable());
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(60, 60);
        relativeLayout.addView(goods, params);

        float startX = startLoc[0] - parentLoc[0] + imgview.getWidth() / 2;
        float startY = startLoc[1] - parentLoc[1] + imgview.getHeight() / 2;
        float toX = endLoc[0] - parentLoc[0] + cartImg.getWidth() / 3;
        float toY = endLoc[1] - parentLoc[1];


        Path path = new Path();
        path.moveTo(startX, startY);
        path.quadTo((startX + toX) / 2, startY, toX, toY);
        mPathMeasure = new PathMeasure(path, false);

Path路径以及PathMeasure路径测量的构造

//属性动画实现
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeasure.getLength());
        valueAnimator.setDuration(1000);
        // 匀速插值器
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (Float) animation.getAnimatedValue();
                // 获取当前点坐标封装到mCurrentPosition
                mPathMeasure.getPosTan(value, mCurrentPosition, null);
                goods.setTranslationX(mCurrentPosition[0]);
                goods.setTranslationY(mCurrentPosition[1]);
            }
        });
        valueAnimator.start();


        valueAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                i++;
                txt.setText(String.valueOf(i));
                relativeLayout.removeView(goods);
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

这里是属性动画实现的核心,着重学习一下。
科普时间(引用郭神博客):
ValueAnimator是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。

其实添加购物车的动画实现就是对商品的x,y坐标不断赋值,不断更新,达到抛物线的效果。
1.调用ValueAnimator的ofFloat()方法就可以构建出一个ValueAnimator的实例,ofFloat()方法当中允许传入多个float类型的参数,这里传入0和mPathMeasure.getLength()就表示将值从0平滑过渡到mPathMeasure.getLength()。
2.通过addUpdateListener()方法来添加一个动画的监听器,在动画执行的过程中会不断地进行回调,我们只需要在回调方法当中通过mPathMeasure.getPosTan()方法将当前的值取出并设置给商品,就可以达到动画效果了。
3.addListener方法来监听动画完成以后的操作,移除动态添加的view,购物车数量加一。

参考博客:

http://blog.csdn.net/guolin_blog/article/details/43536355
http://blog.csdn.net/lmj623565791/article/details/38067475

欧了,关于属性动画细节,实现原理这几篇博客讲的很清楚,感谢这些大神。

下载地址:

https://github.com/18722527635/MyCartView

欢迎大家star,fork,提issues,一起进步!

继续努力!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
js加入购物车抛物线动画购物车效果特效,亲测可用, 当您在电商购物网站浏览中意的商品时,您可以点击页面中的“加入购物车”按钮即可将商品加入的购物车中。本文介绍借助一款基于jQuery的动画插件,点击加入购物车按钮时,实现商品将飞入到右侧的购物车中的效果。 HTML 首先载入jQuery库文件和jquery.fly.min.js插件。 复制代码 代码如下: 接着,将商品信息html结构布置好,本例中,我们用四个商品并排布置,每个商品box中包括有商品图片、价格、名称以及加入购物车按钮等信息。 复制代码 代码如下: ¥3499.00 LG 49LF5400-CA 49寸IPS硬屏富贵招财铜钱设计 加入购物车 ¥3799.00 Hisense/海信 LED50T1A 海信电视官方旗舰店 加入购物车 ¥¥3999.00 Skyworth/创维 50E8EUS 8核4Kj极清酷开系统智能液晶电视 加入购物车 ¥6969.00 乐视TV Letv X60S 4核1080P高清3D安卓智能超级电视 加入购物车 然后,我们还需要在页面的右侧加上购物车以及提示信息。 复制代码 代码如下: 购物车 已成功加入购物车! CSS 我们使用CSS先将商品排列美化,然后设置右侧购物车样式,具体请看代码: 复制代码 代码如下: .box{float:left; width:198px; height:320px; margin-left:5px; border:1px solid #e0e0e0; text-align:center} .box p{line-height:20px; padding:4px 4px 10px 4px; text-align:left} .box:hover{border:1px solid #f90} .box h4{line-height:32px; font-size:14px; color:#f30;font-weight:500} .box h4 span{font-size:20px} .u-flyer{display: block;width: 50px;height: 50px;border-radius: 50px;position: fixed;z-index: 9999;} .m-sidebar{position: fixed;top: 0;right: 0;background: #000;z-index: 2000;width: 35px;height: 100%;font-size: 12px;color: #fff;} .cart{color: #fff;t
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值