您的位置:首页 > 其它

把商品添加到购物车的动画效果(贝塞尔曲线)

2017-01-06 17:09 706 查看


如图:




参考:

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


思路:

确定动画的起终点
在起终点之间使用二次贝塞尔曲线填充起终点之间的点的轨迹
设置属性动画,ValueAnimator插值器,获取中间点的坐标
将执行动画的控件的x、y坐标设为上面得到的中间点坐标
开启属性动画
当动画结束时的操作


难点:

PathMeasure的使用 

- getLength() 

- boolean getPosTan(float distance, float[] pos, float[] tan) 的理解


涉及到的知识点:

如何获取控件在屏幕中的绝对坐标
//得到父布局的起始点坐标(用于辅助计算动画开始/结束时的点的坐标)
int[] parentLocation = new int[2];
rl.getLocationInWindow(parentLocation);
1
2
3
1
2
3
如何使用贝塞尔曲线以及属性动画插值器ValueAnimator
//        四、计算中间动画的插值坐标(贝塞尔曲线)(其实就是用贝塞尔曲线来完成起终点的过程)
//开始绘制贝塞尔曲线
Path path = new Path();
//移动到起始点(贝塞尔曲线的起点)
path.moveTo(startX, startY);
//使用二次萨贝尔曲线:注意第一个起始坐标越大,贝塞尔曲线的横向距离就会越大,一般按照下面的式子取即可
path.quadTo((startX + toX) / 2, startY, toX, toY);
//mPathMeasure用来计算贝塞尔曲线的曲线长度和贝塞尔曲线中间插值的坐标,
// 如果是true,path会形成一个闭环
mPathMeasure = new PathMeasure(path, false);

//★★★属性动画实现(从0到贝塞尔曲线的长度之间进行插值计算,获取中间过程的距离值)
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
// boolean getPosTan(float distance, float[] pos, float[] tan) :
// 传入一个距离distance(0<=distance<=getLength()),然后会计算当前距
// 离的坐标点和切线,pos会自动填充上坐标,这个方法很重要。
mPathMeasure.getPosTan(value, mCurrentPosition, null);//mCurrentPosition此时就是中间距离点的坐标值
// 移动的商品图片(动画图片)的坐标设置为该中间点的坐标
goods.setTranslationX(mCurrentPosition[0]);
goods.setTranslationY(mCurrentPosition[1]);
}
});
//      五、 开始执行动画
valueAnimator.start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34


所有代码:

package cn.c.com.beziercurveanimater;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

private RecyclerView mRecyclerView;
private ImageView cart;
private ArrayList<Bitmap> bitmapList = new ArrayList<>();
private RelativeLayout rl;
private PathMeasure mPathMeasure;
/**
* 贝塞尔曲线中间过程的点的坐标
*/
private float[] mCurrentPosition = new float[2];
private TextView count;
private int i = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initImg();
MyAdapter myAdapter = new MyAdapter(bitmapList);
mRecyclerView.setAdapter(myAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this));
}

private void initImg() {
bitmapList.add(BitmapFactory.decodeResource(getResources(), R.drawable.coin));
bitmapList.add(BitmapFactory.decodeResource(getResources(), R.drawable.coin1));
bitmapList.add(BitmapFactory.decodeResource(getResources(), R.drawable.coin91));
}

private void initView() {
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
cart = (ImageView) findViewById(R.id.cart);
rl = (RelativeLayout) findViewById(R.id.rl);
count = (TextView) findViewById(R.id.count);
}

class MyAdapter extends RecyclerView.Adapter<MyVH> {
private ArrayList<Bitmap> bitmapList;

public MyAdapter(ArrayList<Bitmap> bitmapList) {
this.bitmapList = bitmapList;
}

@Override
public MyVH onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
View itemView = inflater.inflate(R.layout.item, parent, false);
MyVH myVH = new MyVH(itemView);
return myVH;
}

@Override
public void onBindViewHolder(final MyVH holder, final int position) {
holder.iv.setImageBitmap(bitmapList.get(position));
holder.buy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addCart(holder.iv);
}
});
}

@Override
public int getItemCount() {
return bitmapList.size();
}
}

/**
* ★★★★★把商品添加到购物车的动画效果★★★★★
* @param iv
*/
private void addCart( ImageView iv) {
// 一、创造出执行动画的主题---imageview
//代码new一个imageview,图片资源是上面的imageview的图片
// (这个图片就是执行动画的图片,从开始位置出发,经过一个抛物线(贝塞尔曲线),移动到购物车里)
final ImageView goods = new ImageView(MainActivity.this);
goods.setImageDrawable(iv.getDrawable());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(100, 100);
rl.addView(goods, params);

// 二、计算动画开始/结束点的坐标的准备工作
//得到父布局的起始点坐标(用于辅助计算动画开始/结束时的点的坐标) int[] parentLocation = new int[2]; rl.getLocationInWindow(parentLocation);

//得到商品图片的坐标(用于计算动画开始的坐标)
int startLoc[] = new int[2];
iv.getLocationInWindow(startLoc);

//得到购物车图片的坐标(用于计算动画结束后的坐标)
int endLoc[] = new int[2];
cart.getLocationInWindow(endLoc);

// 三、正式开始计算动画开始/结束的坐标
//开始掉落的商品的起始点:商品起始点-父布局起始点+该商品图片的一半
float startX = startLoc[0] - parentLocation[0] + iv.getWidth() / 2;
float startY = startLoc[1] - parentLocation[1] + iv.getHeight() / 2;

//商品掉落后的终点坐标:购物车起始点-父布局起始点+购物车图片的1/5
float toX = endLoc[0] - parentLocation[0] + cart.getWidth() / 5;
float toY = endLoc[1] - parentLocation[1];

// 四、计算中间动画的插值坐标(贝塞尔曲线)(其实就是用贝塞尔曲线来完成起终点的过程) //开始绘制贝塞尔曲线 Path path = new Path(); //移动到起始点(贝塞尔曲线的起点) path.moveTo(startX, startY); //使用二次萨贝尔曲线:注意第一个起始坐标越大,贝塞尔曲线的横向距离就会越大,一般按照下面的式子取即可 path.quadTo((startX + toX) / 2, startY, toX, toY); //mPathMeasure用来计算贝塞尔曲线的曲线长度和贝塞尔曲线中间插值的坐标, // 如果是true,path会形成一个闭环 mPathMeasure = new PathMeasure(path, false); //★★★属性动画实现(从0到贝塞尔曲线的长度之间进行插值计算,获取中间过程的距离值) 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 // boolean getPosTan(float distance, float[] pos, float[] tan) : // 传入一个距离distance(0<=distance<=getLength()),然后会计算当前距 // 离的坐标点和切线,pos会自动填充上坐标,这个方法很重要。 mPathMeasure.getPosTan(value, mCurrentPosition, null);//mCurrentPosition此时就是中间距离点的坐标值 // 移动的商品图片(动画图片)的坐标设置为该中间点的坐标 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) {
// 购物车的数量加1
i++;
count.setText(String.valueOf(i));
// 把移动的图片imageview从父布局里移除
rl.removeView(goods);
}

@Override
public void onAnimationCancel(Animator animation) {

}

@Override
public void onAnimationRepeat(Animator animation) {

}
});
}

class MyVH extends RecyclerView.ViewHolder {

private ImageView iv;
private TextView buy;

public MyVH(View itemView) {
super(itemView);
iv = (ImageView) itemView.findViewById(R.id.iv);
buy = (TextView) itemView.findViewById(R.id.buy);
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208


完整源码:

github
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: