补间动画,属性动画实现购物车添加动画

发布时间:2023-05-09 09:53:43

公司项目需要向购物车添加商品,需要添加商品的动画效果。参照当前主流APP的一些效果,最终达到以下效果:

 

补间动画,属性动画实现购物车添加动画_ObjectAnimator

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

实现过程: 首先,商品被添加到购物车的轨迹中,类似于抛物线。幸运的是,安卓为我们提供了相关的方法–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 只包装低级贝塞尔曲线,用于设置二次贝塞尔曲线。首先,上图显示:

补间动画,属性动画实现购物车添加动画_封装_02

x3、y3 代表控制点 x、y,也就是说,P1、x2在动态图中、y2 代表目标点 x、y,也就是说,动态图中的P2。绘制路径轨迹找到了相应的类别和方法,其次是在自己的项目中的具体应用。如下图所示:

补间动画,属性动画实现购物车添加动画_封装_03

(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值时,函数都会发生变化.getLength() ,当参数为measure时.getLength() 时间表示动画结束。通过参数Transformation 获取变换矩阵(matrix),各种复杂的效果可以通过改变矩阵来实现。 通过getmatrix().setttranslate函数实现移动,该函数的两个参数代表商品的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。最后,打开动画以达到最终效果。

~~~~~~~~~~~~~~~~~~~~~~~~~~

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

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();                // 当前的点坐标包装在mcurentPosion中                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和mpathmeasuree传输到这里.getLength()表示将值从0平滑过渡到mpathmeasure.getLength()。 2.通过adddupdatelistener()添加动画监听器,在动画执行过程中不断回调。我们只需要在回调方法中使用mpathmeasure.getPosTan()将当前值取出并设置给商品的方法可以达到动画效果。 3.addlistener方法监控动画完成后的操作,删除动态添加的view,增加购物车数量。

上一篇 RecyclerView控件列表项布局match_parent属性失效的根本原理
下一篇 ViewPager实现页卡的最新方法--简洁的TabLayout(谷歌支持包)

文章素材均来源于网络,如有侵权,请联系管理员删除。

标签: Java教程Java基础Java编程技巧面试题Java面试题