Android custom View to achieve breathing light effect

Effect

First look at the effect:
insert image description here
the above is the effect of the gif image, just for reference, the actual speed is much faster, which can better reflect the effect of the breathing light.

Custom View

BreathViewThe custom Kotlin code is as follows:

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 //动画执行时间/呼吸速率
    }
}

BreathViewThe custom Java code is as follows:

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();
	}
}

The attribute definition attrs.xml of the custom View is as follows:

<resources>
    <declare-styleable name="BreathView">
        <attr name="centerCircleRadius" format="dimension"/>
        <attr name="circleColor" format="color"/>
        <attr name="maxCircleRadius" format="dimension"/>
    </declare-styleable>
</resources>

Among them, the size of the middle circle is defined by centerCircleRadiusthe property , and the middle circle is not displayed when it is 0; maxCircleRadiusthe maximum display radius of the circle is defined by the property, circleColorand the property is the color of the circle.
Defined in the interface as follows:

    <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" />

Application Scenario

It can be passed, setting red can realize the fault alarm effect, and setting green can represent the normal state. Different colors can be defined to achieve colorful breathing light effects, such as power reminder, device failure reminder, device switch status reminder and other effects.

Guess you like

Origin blog.csdn.net/CJohn1994/article/details/127968724