两种方式实现购物车动画

简介: 公司项目有添加商品到购物车的需求,需要一个添加商品的动画效果。参照了一些当下主流APP的效果,最后实现了以下效果:点击Item,显示点击第几项;点击购买,添加商品到购物车,同时购物车商品总数加一。

公司项目有添加商品到购物车的需求,需要一个添加商品的动画效果。参照了一些当下主流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,一起进步!

继续努力!

目录
相关文章
|
2天前
|
前端开发 JavaScript
订单确认按钮动画
订单确认按钮动画
11 2
|
1月前
购物车的功能——界面源码
购物车的功能——界面源码
12 1
|
5月前
|
小程序 JavaScript
微信小程序商品筛选,侧方弹出动画选择页面
微信小程序商品筛选,侧方弹出动画选择页面
140 0
|
6月前
【 uniapp - 黑马优购 | 购物车页面(3)】结算页面组件封装与渲染 (超详细代码讲解)
【 uniapp - 黑马优购 | 购物车页面(3)】结算页面组件封装与渲染 (超详细代码讲解)
106 0
|
7月前
|
容器
ivx页面(3)轮播图的动态数据绑定实现以及轮播图点击跳转页面
ivx页面(3)轮播图的动态数据绑定实现以及轮播图点击跳转页面
78 0
|
9月前
|
小程序 JavaScript
微信小程序实现左右滑动进行切换数据页面(touchmove)
微信小程序实现左右滑动进行切换数据页面(touchmove)
630 0
|
11月前
|
小程序 API
零基础学小程序007----首页轮播图,可以自动轮播,循环轮播,定时轮播
零基础学小程序007----首页轮播图,可以自动轮播,循环轮播,定时轮播
|
11月前
|
XML 存储 缓存
底部导航栏的几种实现方式
底部导航栏的几种实现方式
288 0
uniapp——添加购物车数据以及删除购物车数据
添加购物车数据以及删除购物车数据
131 0
|
前端开发 开发者 索引
轮播图-动态响应轮播图-动态渲染 |学习笔记
快速学习 轮播图-动态响应轮播图-动态渲染
139 0
轮播图-动态响应轮播图-动态渲染  |学习笔记