Android实现简单点赞动画
思路
找到Activity中DecorView的RootView
确定点赞控件位于屏幕中的坐标值
将点赞效果View加入到RootView中, 给效果View添加自己想要的动画效果.
重复点击时候, 需要将效果View先移除掉再重新加入到RootView中.
代码
/**
* 普通点赞效果, 点击控件后出现一个View上浮
*/
public class ViewLikeUtils {
public interface ViewLikeClickListener {
/**
* @param view 被点赞的按钮
* @param toggle 开关
* @param viewLikeUtils 工具类本身
*/
void onClick(View view, boolean toggle, ViewLikeUtils viewLikeUtils);
}
// 被点击的按钮
private View mClickView;
private View mAnimView;
private ViewLikeClickListener mListener;
private boolean toggle = false; // 点击开关标识
private int mX; // 距离屏幕左侧距离
private int mY; // 距离屏幕顶端距离, 越往下数值越大
/**
* @param mClickView 被点击的View
* @param mAnimView 点赞后, 向上浮动的View
* @param mListener 被点击的View,点击后的回调事件.
*/
public ViewLikeUtils(View mClickView, View mAnimView, @NonNull ViewLikeClickListener mListener) {
this.mClickView = mClickView;
this.mAnimView = mAnimView;
this.mListener = mListener;
initListener();
}
/**
* 设置View的监听
*/
private void initListener() {
mClickView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
getLocation(); // 获取被点击View的坐标
toggle = !toggle;
if (mListener != null) {
mListener.onClick(mClickView, toggle, ViewLikeUtils.this);
}
// mView.performClick();
}
// 正常的OnClickListener将无法调用
return true;
}
});
}
/**
* 获取View在屏幕中的坐标
*/
private void getLocation() {
int[] mLocation = new int[2];
mClickView.getLocationOnScreen(mLocation);
mX = mLocation[0];
mY = mLocation[1];
}
/**
* 开始动画
*/
private void startAnim(ValueAnimator valueAnimator) {
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimView.setAlpha(1 - (Float) valueAnimator.getAnimatedFraction());
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) mAnimView.getLayoutParams();
params.topMargin = (int) (mY - mAnimView.getMeasuredHeight() - 100 * valueAnimator.getAnimatedFraction());
mAnimView.setLayoutParams(params);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
removeChildView(mAnimView);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
valueAnimator.start();
}
/**
* 将上浮控件添加到屏幕中
*
* @param animview
*/
private void addAnimView(View animview) {
Activity activityFromView = getActivityFromView(mClickView);
if (activityFromView != null) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
FrameLayout mRootView = (FrameLayout) activityFromView.getWindow().getDecorView().getRootView();
mRootView.addView(animview, params);
// 测量浮动View的大小
animview.measure(0, 0);
params.topMargin = (int) (mY - animview.getMeasuredHeight());
params.leftMargin = mX + mClickView.getMeasuredWidth() / 2 - animview.getMeasuredHeight() / 2;
animview.setLayoutParams(params);
}
}
/**
* 开始动画
*/
public void startLikeAnim(ValueAnimator valueAnimator) {
removeChildView(mAnimView);
addAnimView(mAnimView);
startAnim(valueAnimator);
}
/**
* 获取Activity
*
* @param view
* @return
*/
public Activity getActivityFromView(View view) {
if (null != view) {
Context context = view.getContext();
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
return (Activity) context;
}
context = ((ContextWrapper) context).getBaseContext();
}
}
return null;
}
/**
* 将子View从父容器中去除
*/
private void removeChildView(View mChildView) {
ViewGroup parentViewGroup = (ViewGroup) mChildView.getParent();
if (parentViewGroup != null) {
parentViewGroup.removeView(mChildView);
}
}
}
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
使用
// 效果View
val textView = TextView(this@MainActivity2)
textView.text = "+1"
textView.setTextColor(Color.RED)
textView.textSize = mBtn.textSize
// 效果View动画
val animator = ValueAnimator.ofInt(10, 200)
animator.duration = 800
ViewLikeUtils(findViewById<Button>(R.id.btn_anim), textView) { clickView, toggle, mUtils ->
// 开始动画
mUtils.startLikeAnim(animator)
}
1
2
3
4
5
6
7
8
9
10
11
12
效果
贝塞尔动画点赞效果
思路其实差不多, 具体看代码
public class ViewLikeBesselUtils {
public interface ViewLikeClickListener {
/**
* @param view 被点赞的按钮
* @param toggle 开关
* @param viewLikeBesselUtils 工具类本身
*/
void onClick(View view, boolean toggle, ViewLikeBesselUtils viewLikeBesselUtils);
}
// 被点击的按钮
private View mClickView;
private View[] mAnimViews;
private ViewLikeClickListener mListener;
private boolean toggle = false; // 点击开关标识
private int mX; // 距离屏幕左侧距离
private int mY; // 距离屏幕顶端距离, 越往下数值越大
private Random mRandom = new Random(); // 随机数
/**
* @param mClickView 被点击的View
* @param mAnimViews 点赞后, 向上浮动的View数组
* @param mListener 被点击的View,点击后的回调事件.
*/
public ViewLikeBesselUtils(View mClickView, View[] mAnimViews, @NonNull ViewLikeClickListener mListener) {
this.mClickView = mClickView;
this.mAnimViews = mAnimViews;
this.mListener = mListener;
initListener();
}
/**
* 设置View的监听
*/
private void initListener() {
mClickView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) {
getLocation(); // 获取被点击View的坐标
toggle = !toggle;
if (mListener != null) {
mListener.onClick(mClickView, toggle, ViewLikeBesselUtils.this);
}
// mView.performClick();
}
// 正常的OnClickListener将无法调用
return true;
}
});
}
/**
* 获取View在屏幕中的坐标
*/
private void getLocation() {
int[] mLocation = new int[2];
mClickView.getLocationInWindow(mLocation);
mX = mLocation[0];
mY = mLocation[1];
}
/**
* 开始动画
*
* @param mAnimView
*/
private void startAnim(View mAnimView, int mTime) {
AnimatorSet animatorSet = new AnimatorSet();
ArrayList<BaseInterpolator> interpolators = new ArrayList<>();
interpolators.add(new AccelerateInterpolator());
interpolators.add(new DecelerateInterpolator());
interpolators.add(new AccelerateDecelerateInterpolator());
interpolators.add(new LinearInterpolator());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
animatorSet.setInterpolator(interpolators.get(mRandom.nextInt(4)));
}
// 合并动画
animatorSet.playTogether(getAnimationSet(mAnimView), getBezierAnimatorSet(mAnimView));
animatorSet.setTarget(mAnimView);
animatorSet.setDuration(mTime);
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
removeChildView(mAnimView);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
});
animatorSet.start();
}
/**
* 将上浮控件添加到屏幕中
*
* @param animview
*/
private void addAnimView(View animview) {
Activity activityFromView = getActivityFromView(mClickView);
if (activityFromView != null) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT);
FrameLayout mRootView = (FrameLayout) activityFromView.getWindow().getDecorView().getRootView();
mRootView.addView(animview, params);
}
}
/**
* 开始动画
*/
public void startLikeAnim() {
for (View mAnimView : mAnimViews) {
removeChildView(mAnimView);
addAnimView(mAnimView);
startAnim(mAnimView, mRandom.nextInt(1500));
}
}
/**
* 获取属性动画
*/
private AnimatorSet getAnimationSet(View mView) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mView, "scaleX", 0.4f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mView, "scaleY", 0.4f, 1f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(mView, "alpha", 1f, 0.2f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleX, scaleY, alpha);
return animatorSet;
}
/**
* 获取贝塞尔动画
*/
private ValueAnimator getBezierAnimatorSet(View mView) {
// 测量view
mView.measure(0, 0);
// 屏幕宽
int width = getActivityFromView(mClickView).getWindowManager().getDefaultDisplay().getWidth();
int mPointF0X = mX + mRandom.nextInt(mView.getMeasuredWidth());
int mPointF0Y = mY - mView.getMeasuredHeight()/2;
// 起点
PointF pointF0 = new PointF(mPointF0X, mPointF0Y);
// 终点
PointF pointF3 = new PointF(mRandom.nextInt(width - 100), 0f);
// 第二点
PointF pointF1 = new PointF(mRandom.nextInt(width - 100), (float) (mY * 0.7));
// 第三点
PointF pointF2 = new PointF(mRandom.nextInt(width - 100), (float) (mY * 0.3));
BezierEvaluator be = new BezierEvaluator(pointF1, pointF2);
ValueAnimator bezierAnimator = ValueAnimator.ofObject(be, pointF0, pointF3);
bezierAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
PointF pointF = (PointF) valueAnimator.getAnimatedValue();
mView.setX(pointF.x);
mView.setY(pointF.y);
}
});
return bezierAnimator;
}
/**
* 获取Activity
*
* @param view
* @return
*/
public Activity getActivityFromView(View view) {
if (null != view) {
Context context = view.getContext();
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
return (Activity) context;
}
context = ((ContextWrapper) context).getBaseContext();
}
}
return null;
}
/**
* 将子View从父容器中去除
*/
private void removeChildView(View mChildView) {
ViewGroup parentViewGroup = (ViewGroup) mChildView.getParent();
if (parentViewGroup != null) {
parentViewGroup.removeView(mChildView);
}
}
}
public class BezierEvaluator implements TypeEvaluator<PointF> {
/**
* 这2个点是控制点
*/
private PointF point1;
private PointF point2;
public BezierEvaluator(PointF point1, PointF point2) {
this.point1 = point1;
this.point2 = point2;
}
/**
* @param t
* @param point0 初始点
* @param point3 终点
* @return
*/
@Override
public PointF evaluate(float t, PointF point0, PointF point3) {
PointF point = new PointF();
point.x = point0.x * (1 - t) * (1 - t) * (1 - t)
+ 3 * point1.x * t * (1 - t) * (1 - t)
+ 3 * point2.x * t * t * (1 - t) * (1 - t)
+ point3.x * t * t * t;
point.y = point0.y * (1 - t) * (1 - t) * (1 - t)
+ 3 * point1.y * t * (1 - t) * (1 - t)
+ 3 * point2.y * t * t * (1 - t) * (1 - t)
+ point3.y * t * t * t;
return point;
}
}
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
209
210
211
212
213
214
215
216
217
使用
mBtn = findViewById(R.id.btn_anim)
val mTVS = arrayOfNulls<TextView>(200)
for (i in 0..199) {
val mTV = TextView(this@MainActivity2)
mTV.text = "赞"
mTV.setTextColor(Color.RED)
mTV.textSize = mBtn.textSize
mTVS[i] = mTV
}
ViewLikeBesselUtils(mBtn, mTVS) { view, toggle, viewLikeBesselUtils ->
viewLikeBesselUtils.startLikeAnim()
}
1
2
3
4
5
6
7
8
9
10
11
12
效果