先看下效果:
上面为gif图的效果,只做参考,实际速率要快好多,能更好体现呼吸灯效果。
自定义 BreathView
的Kotlin代码如下:
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import android.view.animation.Animation
import android.view.animation.LinearInterpolator
class BreathView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr), AnimatorUpdateListener {
private val mCenterCircleRadius: Float
private var mMaxCircleRadius: Float
private val mCirclePaint: Paint
private var mAlphaValue = 0
init {
val a = context.obtainStyledAttributes(attrs, R.styleable.BreathView)
mCenterCircleRadius = a.getDimension(R.styleable.BreathView_centerCircleRadius, 5f)
mMaxCircleRadius = a.getDimension(R.styleable.BreathView_maxCircleRadius, 10f)
if (mCenterCircleRadius >= mMaxCircleRadius) {
mMaxCircleRadius = mCenterCircleRadius * 2
}
val circleColor = a.getColor(R.styleable.BreathView_circleColor, Color.GREEN)
a.recycle()
mCirclePaint = Paint()
mCirclePaint.isAntiAlias = true
mCirclePaint.style = Paint.Style.FILL
mCirclePaint.color = circleColor
val circleAlphaValueAnimator = ValueAnimator.ofInt(0, 255)
circleAlphaValueAnimator.duration = BREATH_TIME
circleAlphaValueAnimator.repeatCount = Animation.INFINITE
circleAlphaValueAnimator.repeatMode = ValueAnimator.REVERSE
circleAlphaValueAnimator.interpolator = LinearInterpolator()
circleAlphaValueAnimator.addUpdateListener(this)
circleAlphaValueAnimator.start()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var width = MeasureSpec.getSize(widthMeasureSpec)
var height = MeasureSpec.getSize(heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(widthMeasureSpec)
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
width = Math.max(width.toFloat(), mMaxCircleRadius * 2).toInt()
}
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
height = Math.max(width.toFloat(), mMaxCircleRadius * 2).toInt()
}
setMeasuredDimension(width, height)
}
override fun draw(canvas: Canvas) {
super.draw(canvas)
val centerX = width / 2.0f
val centerY = height / 2.0f
mCirclePaint.alpha = 255
canvas.drawCircle(centerX, centerY, mCenterCircleRadius, mCirclePaint)
mCirclePaint.alpha = mAlphaValue
canvas.drawCircle(centerX, centerY, mMaxCircleRadius, mCirclePaint)
}
override fun onAnimationUpdate(valueAnimator: ValueAnimator) {
mAlphaValue = valueAnimator.animatedValue as Int
invalidate()
}
companion object {
private const val BREATH_TIME: Long = 1000 //动画执行时间/呼吸速率
}
}
自定义 BreathView
的Java代码如下:
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
public class BreathView extends View implements ValueAnimator.AnimatorUpdateListener {
private static final long BREATH_TIME = 1000; //动画执行时间/呼吸速率
private final float mCenterCircleRadius;
private float mMaxCircleRadius;
private final Paint mCirclePaint;
private int mAlphaValue;
public BreathView(Context context) {
this(context, null);
}
public BreathView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BreathView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BreathView);
mCenterCircleRadius = a.getDimension(R.styleable.BreathView_centerCircleRadius, 5f);
mMaxCircleRadius = a.getDimension(R.styleable.BreathView_maxCircleRadius, 10f);
if (mCenterCircleRadius >= mMaxCircleRadius) {
mMaxCircleRadius = mCenterCircleRadius * 2;
}
int circleColor = a.getColor(R.styleable.BreathView_circleColor, Color.GREEN);
a.recycle();
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);
mCirclePaint.setStyle(Paint.Style.FILL);
mCirclePaint.setColor(circleColor);
ValueAnimator circleAlphaValueAnimator = ValueAnimator.ofInt(0, 255);
circleAlphaValueAnimator.setDuration(BREATH_TIME);
circleAlphaValueAnimator.setRepeatCount(Animation.INFINITE);
circleAlphaValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
circleAlphaValueAnimator.setInterpolator(new LinearInterpolator());
circleAlphaValueAnimator.addUpdateListener(this);
circleAlphaValueAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(widthMeasureSpec);
if(widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
width = (int) Math.max(width, mMaxCircleRadius * 2);
}
if(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
height = (int) Math.max(width, mMaxCircleRadius * 2);
}
setMeasuredDimension(width, height);
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
float centerX = getWidth() / 2.0f;
float centerY = getHeight() / 2.0f;
mCirclePaint.setAlpha(255);
canvas.drawCircle(centerX, centerY, mCenterCircleRadius, mCirclePaint);
mCirclePaint.setAlpha(mAlphaValue);
canvas.drawCircle(centerX, centerY, mMaxCircleRadius, mCirclePaint);
}
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAlphaValue = (int) valueAnimator.getAnimatedValue();
invalidate();
}
}
自定义View的属性定义 attrs.xml 如下:
<resources>
<declare-styleable name="BreathView">
<attr name="centerCircleRadius" format="dimension"/>
<attr name="circleColor" format="color"/>
<attr name="maxCircleRadius" format="dimension"/>
declare-styleable>
resources>
其中通过 centerCircleRadius
属性定义中间圆形的大小,为0时则不显示中间圆形;通过 maxCircleRadius
属性定义圆形最大显示半径,circleColor
属性为圆形颜色。
在界面中定义如下:
<com.example.customui.BreathView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:centerCircleRadius="3dp"
app:maxCircleRadius="8dp"
app:circleColor="@android:color/holo_red_light" />
可通过,设置红色可实现故障报警效果,设置绿色代表正常的状态。可定义不同颜色,实现七彩的呼吸灯效果,如实现电量提醒、设备故障提醒、设备开关状态提醒等等效果。