先来看下效果:
五角星gif图显示速度较实际慢,代码中可自定义闪烁的时长。
自定义 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"
/>