Android绘制五角星并实现星星闪耀发光效果

效果

先来看下效果:
Android绘制五角星并实现星星闪耀发光效果_第1张图片
五角星gif图显示速度较实际慢,代码中可自定义闪烁的时长。

自定义View

自定义 ShiningStarView 的Kotlin代码:

import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import android.view.animation.Animation
import android.view.animation.LinearInterpolator

class ShiningStarView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr), AnimatorUpdateListener {
    private val mStarRadius: Float
    private val mStarPaint: Paint
    private var mStarPath: Path? = null
    private var mAlphaValue = 255

    init {
        val a = context.obtainStyledAttributes(attrs, R.styleable.ShiningStarView)
        val mStarColor = a.getColor(R.styleable.ShiningStarView_starColor, Color.YELLOW)
        mStarRadius = a.getDimension(R.styleable.ShiningStarView_starRadius, 90f)
        a.recycle()
        mStarPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mStarPaint.color = mStarColor
        mStarPaint.maskFilter = BlurMaskFilter(mStarRadius / 3, BlurMaskFilter.Blur.SOLID) //外发光效果
        val circleAlphaValueAnimator = ValueAnimator.ofInt(0, 255)
        circleAlphaValueAnimator.duration = 1000
        circleAlphaValueAnimator.repeatCount = Animation.INFINITE
        circleAlphaValueAnimator.repeatMode = ValueAnimator.REVERSE
        circleAlphaValueAnimator.interpolator = LinearInterpolator()
        circleAlphaValueAnimator.addUpdateListener(this)
        circleAlphaValueAnimator.start()
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.save()
        mStarPaint.alpha = mAlphaValue
        canvas.drawPath(mStarPath!!, mStarPaint)
        canvas.restore()
    }

    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 = mStarRadius.toInt() * 2
        }
        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            height = mStarRadius.toInt() * 2
        }
        mStarPath = calculateStarPath(mStarRadius)
        setMeasuredDimension(width, height)
    }

    private fun calculateStarPath(radius: Float): Path {
        val starPath = Path()
        //360度除以5个角, 比如5个角 每个角72度,利用cos 、sin计算位置 1度=π/180 以顶角尖作为坐标轴原点
        val outRadian = Math.PI / 180.0 * (90.0 - 360.0 / 5 / 2) //右角尖与x轴的弧度
        val pRadian = (1.0 - 0.5) * outRadian //钝角点与x轴的弧度
        val pRightRadian = 0.5 * outRadian //钝角点到原点与右角尖的弧度
        val centerLength = radius * Math.sin(Math.PI / 5) //右角尖到顶角间的中心点到原点的距离
        val pLength = centerLength / Math.cos(pRightRadian) //钝角点到原点距离
        val pX = pLength * Math.sin(pRadian)
        val pY = pLength * Math.cos(pRadian)
        starPath.moveTo(0f, 0f)
        for (i in 0..4) {
            val angle = Math.PI / 180 * 360 / 5 * i //每次旋转坐标轴角度 angle== π/180 * (360 / 5)
            val sinA = Math.sin(angle)
            val cosA = Math.cos(angle)
            val rX2 = (radius * sinA).toFloat()
            val rY2 = (-radius * cosA + radius).toFloat()
            val pX2 = (pX * cosA - (pY - radius) * sinA).toFloat()
            val pY2 = ((pY - radius) * cosA + pX * sinA + radius).toFloat()
            starPath.lineTo(rX2, rY2)
            starPath.lineTo(pX2, pY2)
        }
        starPath.close()
        val matrix = Matrix()
        //坐标系平移
        matrix.setTranslate(radius, 0f)
        starPath.transform(matrix)
        return starPath
    }

    override fun onAnimationUpdate(animation: ValueAnimator) {
        mAlphaValue = animation.animatedValue as Int
        invalidate()
    }
}

自定义 ShiningStarView 的Java代码:

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;

import androidx.annotation.Nullable;

public class ShiningStarView extends View implements ValueAnimator.AnimatorUpdateListener {
    private final float mStarRadius;
    private final Paint mStarPaint;
    private Path mStarPath;
    private int mAlphaValue = 255;

    public ShiningStarView(Context context) {
        this(context, null);
    }

    public ShiningStarView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ShiningStarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context,attrs,defStyleAttr);
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ShiningStarView);
        int mStarColor = a.getColor(R.styleable.ShiningStarView_starColor, Color.YELLOW);
        mStarRadius = a.getDimension(R.styleable.ShiningStarView_starRadius, 90);
        a.recycle();

        mStarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mStarPaint.setColor(mStarColor);
        mStarPaint.setMaskFilter(new BlurMaskFilter(mStarRadius / 3, BlurMaskFilter.Blur.SOLID)); //外发光效果

        ValueAnimator circleAlphaValueAnimator = ValueAnimator.ofInt(0, 255);
        circleAlphaValueAnimator.setDuration(1000);
        circleAlphaValueAnimator.setRepeatCount(Animation.INFINITE);
        circleAlphaValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        circleAlphaValueAnimator.setInterpolator(new LinearInterpolator());
        circleAlphaValueAnimator.addUpdateListener(this);
        circleAlphaValueAnimator.start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        mStarPaint.setAlpha(mAlphaValue);
        canvas.drawPath(mStarPath, mStarPaint);
        canvas.restore();
    }

    @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) mStarRadius * 2;
        }
        if(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            height = (int) mStarRadius * 2;
        }
        mStarPath = calculateStarPath(mStarRadius);

        setMeasuredDimension(width, height);
    }

    private Path calculateStarPath(float radius){
        Path starPath = new Path();
        //360度除以5个角, 比如5个角 每个角72度,利用cos 、sin计算位置 1度=π/180 以顶角尖作为坐标轴原点
        double outRadian = Math.PI / 180.0 * (90.0 - 360.0 / 5 / 2); //右角尖与x轴的弧度
        double pRadian = (1.0 - 0.5) * outRadian; //钝角点与x轴的弧度
        double pRightRadian = 0.5 * outRadian; //钝角点到原点与右角尖的弧度

        double centerLength = radius * Math.sin(Math.PI / 5); //右角尖到顶角间的中心点到原点的距离
        double pLength = centerLength / Math.cos(pRightRadian); //钝角点到原点距离
        double pX = pLength * Math.sin(pRadian);
        double pY = pLength * Math.cos(pRadian);
        starPath.moveTo(0,0);

        for (int i = 0;i < 5;i++){
            double angle = Math.PI / 180 * 360 / 5 * i; //每次旋转坐标轴角度 angle== π/180 * (360 / 5)
            double sinA = Math.sin(angle);
            double cosA = Math.cos(angle);
            float rX2 = (float) (radius * sinA);
            float rY2 = (float) (-radius * cosA + radius);
            float pX2 = (float) (pX * cosA - (pY - radius) * sinA);
            float pY2 = (float) ((pY - radius) * cosA + pX*sinA + radius);
            starPath.lineTo(rX2,rY2);
            starPath.lineTo(pX2,pY2);
        }
        starPath.close();
        Matrix matrix = new Matrix();
        //坐标系平移
        matrix.setTranslate(radius,0);
        starPath.transform(matrix);
        return starPath;
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mAlphaValue = (int) animation.getAnimatedValue();
        invalidate();
    }
}

通过 BlurMaskFilter 实现外发光效果,坐标轴以顶角为原点进行数学计算,得到五角星的Path轨迹。根据弧度(1角度=π/180弧度)来计算边长,根据五角星的对称原理和直角三角形的原理,借助sin和cos等公式计算对应坐标。通过动画进行循环绘制,绘制不同的透明度实现闪闪发光效果。
自定义View的属性定义 attrs.xml 如下:

<resources>
    <declare-styleable name="ShiningStarView">
        <attr name="starRadius" format="dimension"/>
        <attr name="starColor" format="color"/>
    declare-styleable>
resources>

其中通过 starRadius 属性定义五角星的半径(以此作为大小);通过 starColor 属性为五角星颜色。
在界面中定义如下:

    <com.example.customui.ShiningStarView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:starRadius="30dp"
        app:starColor="@color/yellow"
        />

你可能感兴趣的:(Kotlin,自定义View,android,kotlin,五角星,自定义View,发光效果)