T006 自定义控件 扫描雷达动画

一、说明

  • 表层是一张指针图,增加旋转动画,每旋转一周,增加一个波纹扩散动画效果。
  • 波纹扩散动画效果是在底层的波纹图上实现缩放和透明度变化的组合动画。
  • 使用容器来保存波纹扩散动画,便于复用。

指针原图

波纹原图

二、完整代码

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
package com.xxt.xtest;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class ScanningView extends FrameLayout {
/**
* 指针
*/
private ImageView ivNeedle;
/**
* 波纹
*/
private ImageView ivRipple;
/**
* 中间文字
*/
private TextView tvTitle;
/**
* 装波纹的容器
*/
private FrameLayout fl_move_circle;
/**
* 匀速插值器
*/
private LinearInterpolator interpolator = new LinearInterpolator();
/**
* 准备动画
*/
private AnimatorSet prepareAnim;
/**
* 指针旋转动画
*/
private ObjectAnimator needleRotateAnim;
private List<AnimatorSet> animList = new ArrayList<>();
private int animCount = 3;
private int index = 0;
public ScanningView(Context context) {
super(context);
initView();
}
public ScanningView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView(){
View v = LayoutInflater.from(getContext()).inflate(R.layout.rotate_view,null);
ivNeedle = v.findViewById(R.id.iv_btn);
ivRipple = v.findViewById(R.id.iv_out_circle);
tvTitle = v.findViewById(R.id.tv_title);
fl_move_circle = v.findViewById(R.id.fl_move_circle);
addView(v, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
initAnim();
prepareAnim.start();
}
/**
* 初始化动画
* 准备动画 prepareAnim
* 扫描动画 scanningAnim
*/
private void initAnim() {
initPrepareAnim();
initScanningAnim();
}
private void initPrepareAnim() {
// 开始循环的放大缩小波纹
ObjectAnimator outCircleAlpha = ObjectAnimator.ofFloat(ivRipple, "alpha", 0.2f, 0.6f);
outCircleAlpha.setDuration(1000);
ObjectAnimator outCircleAnimX = ObjectAnimator.ofFloat(ivRipple, "scaleX", 1f, 1.18f, 1f);
outCircleAnimX.setDuration(2000);
outCircleAnimX.setRepeatCount(ValueAnimator.INFINITE);
outCircleAnimX.setInterpolator(interpolator);
ObjectAnimator outCircleAnimY = ObjectAnimator.ofFloat(ivRipple, "scaleY", 1f, 1.18f, 1f);
outCircleAnimY.setDuration(2000);
outCircleAnimY.setRepeatCount(ValueAnimator.INFINITE);
outCircleAnimY.setInterpolator(interpolator);
prepareAnim = new AnimatorSet();
prepareAnim.playTogether(outCircleAnimX, outCircleAnimY, outCircleAlpha);
}
private void initScanningAnim() {
// 指针转动动画
needleRotateAnim = ObjectAnimator.ofFloat(ivNeedle, "rotation", 0f, 360f);
needleRotateAnim.setDuration(1800);
needleRotateAnim.setInterpolator(interpolator);
needleRotateAnim.setRepeatCount(ValueAnimator.INFINITE);
needleRotateAnim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
playRippleAnim();
}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {
playRippleAnim();
}
});
}
private void initRippleAnim() {
final ImageView imageView = new ImageView(getContext());
LayoutParams lp = new LayoutParams(dip2px(getContext(), 110), dip2px(getContext(), 110));
lp.gravity = Gravity.CENTER;
imageView.setLayoutParams(lp);
imageView.setImageResource(R.drawable.ripple);
fl_move_circle.addView(imageView);
ObjectAnimator outCircleAnimX = ObjectAnimator.ofFloat(imageView, "scaleX", 1f, 5f);
ObjectAnimator outCircleAnimY = ObjectAnimator.ofFloat(imageView, "scaleY", 1f, 5f);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.6f, 0);
outCircleAnimX.setDuration(5000);
outCircleAnimY.setDuration(5000);
alphaAnim.setDuration(5000);
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(outCircleAnimX, outCircleAnimY, alphaAnim);
animList.add(animatorSet);
}
private void playRippleAnim() {
if (animList.size() < animCount) {
initRippleAnim();
}
animList.get(index).start();
index++;
if (index == animCount) index = 0;
}
/**
* 模拟开始
*/
public void onceClick(){
// 取消掉循环的波纹
prepareAnim.cancel();
ivRipple.setVisibility(GONE);
needleRotateAnim.start();
tvTitle.setText("扫描中");
}
/**
* 根据手机的分辨率从 dip 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}

其布局文件 res/layout/scanning_view.xml 如下:

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
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#69C8FA"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fl_move_circle"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/iv_out_circle"
android:layout_width="110dp"
android:layout_height="110dp"
android:layout_gravity="center"
android:alpha="0.6"
android:src="@drawable/ripple" />
<ImageView
android:id="@+id/iv_btn"
android:layout_width="110dp"
android:layout_height="110dp"
android:layout_gravity="center"
android:src="@drawable/needle" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:textColor="#ffffff"
android:layout_gravity="center"
android:text="点击扫描"
android:textSize="10sp"
android:layout_marginTop="13dp"
android:layout_height="wrap_content" />
</FrameLayout>

三、使用

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
public class DemoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_demo);
final ScanningView view = findViewById(R.id.scanning);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
view.onceClick();
}
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.xxt.xtest.ScanningView
android:id="@+id/scanning"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>