[置顶] Android补间动画,属性动画实现购物车添加动画
公司项目有添加商品到购物车的需求,需要一个添加商品的动画效果。参照了一些当下主流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 只对低阶贝塞尔曲线进行了封装,这是用于设置二次贝塞尔曲线的方法,先上图说明:
x1、y1 代表控制点的 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的单击事件,接口回调处理“购买”点击事件(可参考)
http://blog.csdn.net/tyk0910/article/details/50564917
这里需要拿到具体商品的图片进行动画处理,传递了一个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
忙着出效果,所以没有研究用属性动画来实现,有时间会去完成的,然后更新博客。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~分割线
之前说到想使用属性动画来实现,周末回去好好看了看这方面的知识,最后一样的效果,不同的动画实现。废话不多说,看代码:
private PathMeasure mPathMeasure; private 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
欧了,关于属性动画细节,实现原理这几篇博客讲的很清楚,感谢这些大神。
继续努力!
更多相关文章
- Android(安卓)hardware按键触感功能实现
- 在Android上用规则过渡图片实现百叶窗过渡动画(已过时,请改用gles2
- android 动画入门(一)
- 自定义View之滑动事件
- 自定义view实现超萌动感天气小太阳
- Android属性动画解析(中),ValueAnimator和ObjectAnimator的高级用法
- Android(安卓)本地文件缓存各个方法获取的路径小结
- [Android] 图像处理整合之处理ColorMatrix和Intend传递路径显示
- Android实现的三种翻页功能原理