雷达涟漪效果图
话不多说直接上效果图
上面的效果图看着挺高大上的 其实实现起来很简单,下面就来一步步实现这个效果
第一步:分析
首先当我们看到一个效果并想要实现它的时候呢 第一步要做的工作就是分析他的原理,
可能你看到这个图的时候感觉无从下手,找不到切入点,不过没关心 认真的看下去 你会恍然大悟的感觉
仔细的观察后你会发现 这个图是由:
1.一个实心的圆慢慢的放大扩散并且放大过程中有慢慢的渐变直至消散
2.在第一个圆消散的过程中第二个圆从第一个圆的位置继续重复和第一个圆一样的动作
3.第三个 第四个 第N个圆 一直重复第一个圆一样的动作
4.周而复始的无限循环
结论:多个实心的圆圈通过渐变+扩散的动画效果的集合。既然是多个圆的话,那我们最终的视图是要继承一个ViewGroup 作为一个容器来承载这些圆,并负责把这些圆组合起来。
既然视觉效果能动的话那一定离不开我们的动画效果,渐变+缩放(放大)。弄清楚这几点以后下面我们就来一步步实现
第二步:实现
- 上面我们分析了这个自定义view 是既然是要继承自ViewGroup的,所以我们首先新建 一个类RardarView 让他继承自我们的ViewGroup 这里就先让它继承自 RelativeLayout。当然其他的ViewGroup也可以,例如(LinearLayout,FramLayout,….)这样的话我们可以在雷达的中央放置与我们业务逻辑相关的东西
public class RadarView extends RelativeLayout { public RadarView(Context context) { this(context, null); } public RadarView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public RadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } }
- 首先要搞一个如下图的圆出来,然后加入到我们的RardarView中, 在RardarView 这个类里新建一个内部类RippleView
public class RadarView extends RelativeLayout {
/**
* 圆的边框宽
*/
float rippleStrokeWidth = 0;
/**
* 涟漪的画笔
*/
private Paint ripplePaint;
float rippleRadius = 50;//圆的半径
public RadarView(Context context) {
this(context, null);
}
public RadarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ripplePaint = new Paint();
ripplePaint.setColor(Color.parseColor("#3700B3"));
ripplePaint.setStyle(Paint.Style.FILL);
//房
ripplePaint.setDither(true);
//抗锯齿
ripplePaint.setAntiAlias(true);
ripplePaint.setStrokeCap(Paint.Cap.ROUND);
//半径+线宽
LayoutParams rippleParams = new LayoutParams((int) (2 * (rippleRadius + rippleStrokeWidth)), (int) (2 * (rippleRadius + rippleStrokeWidth)));
rippleParams.addRule(CENTER_IN_PARENT, TRUE);
RippleView rippleView = new RippleView(getContext());
rippleView.setLayoutParams(rippleParams);
addView(rippleView);
}
private class RippleView extends View {
public RippleView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int radius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2;
canvas.drawCircle(radius, radius, radius - rippleStrokeWidth, ripplePaint);
}
}
}
3.到现在我们已经成功的画出来了一个圆,并且添加到了RardarView中 接下里就到了关键的一步,让这个圆动起来(缩放+渐变)如下图,当然这少不了动画效果。
public class RadarView extends RelativeLayout {
/**
* 圆的边框宽
*/
float rippleStrokeWidth = 0;
/**
* 涟漪的画笔
*/
private Paint ripplePaint;
float rippleRadius = 50;//圆的半径
public RadarView(Context context) {
this(context, null);
}
public RadarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ripplePaint = new Paint();
ripplePaint.setColor(Color.parseColor("#3700B3"));
ripplePaint.setStyle(Paint.Style.FILL);
//房
ripplePaint.setDither(true);
//抗锯齿
ripplePaint.setAntiAlias(true);
ripplePaint.setStrokeCap(Paint.Cap.ROUND);
//半径+线宽
LayoutParams rippleParams = new LayoutParams((int) (2 * (rippleRadius + rippleStrokeWidth)), (int) (2 * (rippleRadius + rippleStrokeWidth)));
rippleParams.addRule(CENTER_IN_PARENT, TRUE);
RippleView rippleView = new RippleView(getContext());
rippleView.setLayoutParams(rippleParams);
addView(rippleView);
//缩放动画(这里我们用 ofFloat 为了使渐变效果更自然 ofInt 的话可能会有不连贯的效果 因为 ofFloat 的渐变类似于 1.12 1.35 1.684 ...7.0 而 ofofInt 则是 1 2 3 4 ...7 )
ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(rippleView, "ScaleX", 1, 7);
objectAnimatorX.setRepeatMode(ObjectAnimator.RESTART);
objectAnimatorX.setRepeatCount(ObjectAnimator.INFINITE);
ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(rippleView, "ScaleY", 1, 7);
objectAnimatorY.setRepeatMode(ObjectAnimator.RESTART);
objectAnimatorY.setRepeatCount(ObjectAnimator.INFINITE);
//渐变动画
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(rippleView, "Alpha", 1.0f, 0f);
//重复的模式是 RESTART 重新播放
objectAnimator.setRepeatMode(ObjectAnimator.RESTART);
//重复的此时是 INFINITE 无限播放
objectAnimator.setRepeatCount(ObjectAnimator.INFINITE);
AnimatorSet animatorSet=new AnimatorSet();
animatorSet.setDuration(3000);
animatorSet.playTogether(objectAnimatorX,objectAnimatorY,objectAnimator);
animatorSet.start();
}
private class RippleView extends View {
public RippleView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int radius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2;
canvas.drawCircle(radius, radius, radius - rippleStrokeWidth, ripplePaint);
}
}
}
4.到了这里基本上主要逻辑都已经写完了给代码封装下,完整代码
/**
* ASCustomView
*
* @Description: 自定义雷达view
* @Date : 2020-11-27 10:58
* @Author: Lyb
*/
public class RadarView extends RelativeLayout {
private static final String TAG = "Lyb";
/**
* 涟漪从开始到结束的动画持续时间
*/
private @IntRange(from = 0)
int rippleDurationTime = 3000;
/**
* 涟漪的数量
*/
private @IntRange(from = 0)
int rippleAmount = 4;
/**
* 涟漪的线宽
*/
private @FloatRange(from = 0)
float rippleStrokeWidth = 1;
/**
* 涟漪的画笔
*/
private Paint ripplePaint;
/**
* 1. 填充 2.边框
*/
private @IntRange(from = 1, to = 2)
int rippleType = 1;
/**
* 圆的半径
*/
private @FloatRange(from = 0)
float rippleRadius = 50;//半径
private ArrayList<RippleView> rippleViewList = new ArrayList<>();
/**
* 动画的集合
*/
private ArrayList<Animator> animatorList;
/**
* 动画集
*/
private AnimatorSet animatorSet;
/**
* 动画的状态
*/
private @IntRange(from = 0, to = 2)
int rippleAnimatorState = 0;//0 未开始 1.进行中 2 暂停状态
/**
* 动画的状态
*/
private int rippleColor;//0 未开始 1.进行中 2 暂停状态
private @FloatRange(from = 0)
float scaleXStart = 1f;
private @FloatRange(from = 0)
float scaleXEnd = 7f;
private @FloatRange(from = 0)
float scaleYStart = 1f;
private @FloatRange(from = 0)
float scaleYEnd = 7f;
private @FloatRange(from = 0)
float alphaStart = 1.0f;
private @FloatRange(from = 0)
float alphaEnd = 0f;
public RadarView(Context context) {
this(context, null);
}
public RadarView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RadarView_style);
rippleDurationTime = typedArray.getInteger(R.styleable.RadarView_style_rippleDurationTime, 3000);
rippleAmount = typedArray.getInteger(R.styleable.RadarView_style_rippleAmount, 4);
rippleType = typedArray.getInteger(R.styleable.RadarView_style_rippleType, 1);
rippleRadius = typedArray.getFloat(R.styleable.RadarView_style_rippleRadius, 50);
rippleColor = typedArray.getColor(R.styleable.RadarView_style_rippleColor, Color.RED);
scaleXStart = typedArray.getFloat(R.styleable.RadarView_style_ScaleXStart, 1f);
scaleXEnd = typedArray.getFloat(R.styleable.RadarView_style_ScaleXEnd, 7f);
scaleYStart = typedArray.getFloat(R.styleable.RadarView_style_ScaleYStart, 1f);
scaleYEnd = typedArray.getFloat(R.styleable.RadarView_style_ScaleYEnd, 7f);
alphaStart = typedArray.getFloat(R.styleable.RadarView_style_rippleAlphaStart, 1.0f);
alphaEnd = typedArray.getFloat(R.styleable.RadarView_style_rippleAlphaEnd, 0f);
//半径+线宽
LayoutParams rippleParams = new LayoutParams((int) (2 * (rippleRadius + rippleStrokeWidth)), (int) (2 * (rippleRadius + rippleStrokeWidth)));
rippleParams.addRule(CENTER_IN_PARENT, TRUE);
animatorList = new ArrayList<>();
animatorSet = new AnimatorSet();
ripplePaint = new Paint();
ripplePaint.setColor(rippleColor);
if (rippleType == 1) {
ripplePaint.setStyle(Paint.Style.FILL);
} else {
ripplePaint.setStyle(Paint.Style.STROKE);
ripplePaint.setStrokeWidth(rippleStrokeWidth);
}
ripplePaint.setDither(true);
ripplePaint.setAntiAlias(true);
ripplePaint.setStrokeCap(Paint.Cap.ROUND);
long rippleDelay = rippleDurationTime / rippleAmount;
for (int i = 0; i < rippleAmount; i++) {
RippleView rippleView = new RippleView(getContext());
rippleView.setLayoutParams(rippleParams);
addView(rippleView);
rippleViewList.add(rippleView);
//缩放动画
ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(rippleView, "ScaleX", scaleXStart, scaleXEnd);
objectAnimatorX.setRepeatMode(ObjectAnimator.RESTART);
objectAnimatorX.setRepeatCount(ObjectAnimator.INFINITE);
objectAnimatorX.setStartDelay(i * rippleDelay);
ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(rippleView, "ScaleY", scaleYStart, scaleYEnd);
objectAnimatorY.setRepeatMode(ObjectAnimator.RESTART);
objectAnimatorY.setRepeatCount(ObjectAnimator.INFINITE);
objectAnimatorY.setStartDelay(i * rippleDelay);
//渐变动画
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(rippleView, "Alpha", alphaStart, alphaEnd);
objectAnimator.setRepeatMode(ObjectAnimator.RESTART);
objectAnimator.setRepeatCount(ObjectAnimator.INFINITE);
objectAnimator.setStartDelay(i * rippleDelay);
animatorList.add(objectAnimatorX);
animatorList.add(objectAnimatorY);
animatorList.add(objectAnimator);
}
animatorSet.setDuration(rippleDurationTime);
Log.i(TAG, "rippleDurationTime: " + rippleDurationTime);
animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
animatorSet.playTogether(animatorList);
}
private class RippleView extends View {
public RippleView(Context context) {
super(context);
//这里是为了
this.setVisibility(VISIBLE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int radius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 2;
canvas.drawCircle(radius, radius, radius - rippleStrokeWidth, ripplePaint);
}
}
/**
* 开始动画
*/
public void startRippleAnimation() {
if (rippleAnimatorState != 1) {
for (RippleView rippleView : rippleViewList) {
rippleView.setVisibility(VISIBLE);
}
animatorSet.start();
rippleAnimatorState = 1;
}
}
/**
* 停止动画
*/
public void stopRippleAnimation() {
if (rippleAnimatorState != 0) {
animatorSet.end();
rippleAnimatorState = 0;
}
}
/**
* 暂停动画
*/
public void pauseRippleAnimation() {
if (rippleAnimatorState != 2) {
animatorSet.pause();
rippleAnimatorState = 2;
}
}
/**
* 恢复动画
*/
public void resumeRippleAnimation() {
if (rippleAnimatorState == 2) {
animatorSet.resume();
rippleAnimatorState = 1;
}
}
}
<!-- 雷达view-->
<declare-styleable name="RadarView_style">
<!-- 涟漪从开始到结束的动画持续时间 毫秒-->
<attr name="rippleDurationTime" format="integer" />
<!-- 涟漪的数量 -->
<attr name="rippleAmount" format="integer" />
<!-- 涟漪的类型 1. 填充 2.边框-->
<attr name="rippleType" format="integer" />
<!-- 涟漪的半径 -->
<attr name="rippleRadius" format="float" />
<!-- 涟漪的颜色 -->
<attr name="rippleColor" format="color" />
<!-- 涟漪的透明度区间 默认 1->0 -->
<attr name="rippleAlphaStart" format="float" />
<attr name="rippleAlphaEnd" format="float" />
<!-- 涟漪的X轴缩放的区间 默认 1-7 -->
<attr name="ScaleXStart" format="float" />
<attr name="ScaleXEnd" format="float" />
<!-- 涟漪的Y轴缩放的区间 默认 1-7 -->
<attr name="ScaleYStart" format="float" />
<attr name="ScaleYEnd" format="float" />
</declare-styleable>
以上就能实现雷达涟漪效果,但是如果你细心点你会发现 采用缩放动画的话,RippleView放大过程中会失真,模糊的感觉,体验贼不爽
下一篇我们来讲下如何优化这个问题
本文地址:https://blog.csdn.net/APPLYB/article/details/110523356