Android自定义View实现呼吸灯效果

效果

先看下效果:
在这里插入图片描述
上面为gif图的效果,只做参考,实际速率要快好多,能更好体现呼吸灯效果。

自定义View

自定义 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" />

应用场景

可通过,设置红色可实现故障报警效果,设置绿色代表正常的状态。可定义不同颜色,实现七彩的呼吸灯效果,如实现电量提醒、设备故障提醒、设备开关状态提醒等等效果。

猜你喜欢

转载自blog.csdn.net/CJohn1994/article/details/127968724