|
@@ -0,0 +1,254 @@
|
|
|
+package com.xunao.effectdemo.view;
|
|
|
+
|
|
|
+import android.animation.ValueAnimator;
|
|
|
+import android.content.Context;
|
|
|
+import android.content.res.TypedArray;
|
|
|
+import android.graphics.Canvas;
|
|
|
+import android.graphics.Paint;
|
|
|
+import android.util.AttributeSet;
|
|
|
+import android.util.TypedValue;
|
|
|
+import android.view.View;
|
|
|
+import android.view.animation.DecelerateInterpolator;
|
|
|
+import android.view.animation.LinearInterpolator;
|
|
|
+
|
|
|
+import androidx.annotation.Nullable;
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Created
|
|
|
+ * by jaren on 2017/5/26.
|
|
|
+ */
|
|
|
+
|
|
|
+public class FireworkView extends View{
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 圆最大半径(心形)
|
|
|
+ */
|
|
|
+ private float mRadius;
|
|
|
+ /**
|
|
|
+ * View变化用时
|
|
|
+ */
|
|
|
+ private int mCycleTime;
|
|
|
+ /**
|
|
|
+ * Bézier曲线画圆的近似常数
|
|
|
+ */
|
|
|
+ private static final float c = 0.551915024494f;
|
|
|
+ /**
|
|
|
+ * 环绕圆点的颜色
|
|
|
+ */
|
|
|
+ private static final int[] dotColors = {0xffdaa9fa, 0xfff2bf4b, 0xffe3bca6, 0xff329aed,
|
|
|
+ 0xffb1eb99, 0xff67c9ad, 0xffde6bac};
|
|
|
+ /**
|
|
|
+ * 4.圆环减消失、心形放大、周围环绕十四圆点
|
|
|
+ */
|
|
|
+ private static final int RING_DOT__HEART_VIEW = 3;
|
|
|
+ /**
|
|
|
+ * 5.环绕的十四圆点向外移动并缩小、透明度渐变、渐隐
|
|
|
+ */
|
|
|
+ private static final int DOT__HEART_VIEW = 4;
|
|
|
+
|
|
|
+ private float mCenterX;
|
|
|
+ private float mCenterY;
|
|
|
+ private Paint mPaint;
|
|
|
+ private ValueAnimator animatorTime;
|
|
|
+ private int mCurrentRadius;
|
|
|
+ private int mCurrentState;
|
|
|
+ private float mCurrentPercent;
|
|
|
+
|
|
|
+ private float rDotL;
|
|
|
+ private float offL;
|
|
|
+ private boolean isMax;
|
|
|
+ private float dotR;
|
|
|
+
|
|
|
+
|
|
|
+ public FireworkView(Context context) {
|
|
|
+ this(context, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ public FireworkView(Context context, @Nullable AttributeSet attrs) {
|
|
|
+ this(context, attrs, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ public FireworkView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
|
|
+ super(context, attrs, defStyleAttr);
|
|
|
+ mRadius = dp2px(20);
|
|
|
+ mCycleTime = 600;
|
|
|
+ mCenterX = mRadius;
|
|
|
+ mCenterY = mRadius;
|
|
|
+ mPaint = new Paint();
|
|
|
+ mCurrentRadius = (int) mRadius;
|
|
|
+ dotR = mRadius / 6;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onDraw(Canvas canvas) {
|
|
|
+ super.onDraw(canvas);
|
|
|
+ canvas.translate(mCenterX, mCenterY);//使坐标原点在canvas中心位置
|
|
|
+ switch (mCurrentState) {
|
|
|
+ case RING_DOT__HEART_VIEW:
|
|
|
+ drawDotWithRing(canvas, mCurrentRadius);
|
|
|
+ break;
|
|
|
+ case DOT__HEART_VIEW:
|
|
|
+ drawDot(canvas);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //绘制圆点、圆环、心形
|
|
|
+ private void drawDotWithRing(Canvas canvas, int radius) {
|
|
|
+ mPaint.setAntiAlias(true);
|
|
|
+
|
|
|
+ mPaint.setStyle(Paint.Style.STROKE);
|
|
|
+ mCurrentPercent = (1f - mCurrentPercent > 1f ? 1f : 1f - mCurrentPercent) * 1f;
|
|
|
+ //用于计算圆环宽度,最小0,与动画进度负相关
|
|
|
+// mPaint.setStrokeWidth(2 * mRadius * mCurrentPercent);
|
|
|
+
|
|
|
+ float innerR = radius - mRadius * mCurrentPercent + dotR;
|
|
|
+ double angleB = -Math.PI / 20;
|
|
|
+
|
|
|
+ offL += dotR / 14;
|
|
|
+ rDotL = innerR + offL;
|
|
|
+
|
|
|
+ mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
|
+ for (int i = 0; i < 14; i++) {
|
|
|
+ mPaint.setColor(dotColors[i % 7]);
|
|
|
+ float cx = (float) (rDotL * Math.sin(angleB));
|
|
|
+ float cy = (float) (rDotL * Math.cos(angleB));
|
|
|
+ if(i % 3 == 0){
|
|
|
+ mPaint.setStrokeWidth(dotR);
|
|
|
+ canvas.drawCircle(cx, cy, dotR, mPaint);
|
|
|
+ }else if(i % 3 == 1){
|
|
|
+ mPaint.setStrokeWidth(dotR);
|
|
|
+ canvas.drawText("/", cx, cy, mPaint);
|
|
|
+ }else{
|
|
|
+ mPaint.setStrokeWidth(dotR);
|
|
|
+ canvas.drawRect(cx - 1, cy - 1, cx + 1, cy + 1, mPaint);
|
|
|
+ }
|
|
|
+
|
|
|
+ angleB += 2 * Math.PI / 14;
|
|
|
+ }
|
|
|
+ mCurrentRadius = (int) (mRadius / 3 + offL * 4);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ //绘制圆点、心形
|
|
|
+ private void drawDot(Canvas canvas) {
|
|
|
+ mPaint.setAntiAlias(true);
|
|
|
+ mPaint.setStyle(Paint.Style.FILL);
|
|
|
+
|
|
|
+ double angleB = -Math.PI / 20;
|
|
|
+ float dotRL;
|
|
|
+ if (rDotL < 2.6 * mRadius) {//限制圆点的扩散范围
|
|
|
+ rDotL += dotR / 14;
|
|
|
+ }
|
|
|
+ if (!isMax && mCurrentRadius <= 1.1 * mRadius) {
|
|
|
+ offL += dotR / 14;
|
|
|
+ mCurrentRadius = (int) (mRadius / 3 + offL * 4);
|
|
|
+
|
|
|
+ } else {
|
|
|
+ isMax = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ dotRL = (dotR * (1 - mCurrentPercent) * 4) > dotR ? dotR :
|
|
|
+ (dotR * (1 - mCurrentPercent) * 3);
|
|
|
+ mPaint.setAlpha((int) (255 * (1 - mCurrentPercent)));//圆点逐渐透明
|
|
|
+ mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
|
+ for (int i = 0; i < 14; i++) {
|
|
|
+ mPaint.setColor(dotColors[i % 7]);
|
|
|
+ float cx = (float) (rDotL * Math.sin(angleB));
|
|
|
+ float cy = (float) (rDotL * Math.cos(angleB));
|
|
|
+ if(i % 3 == 0){
|
|
|
+ mPaint.setStrokeWidth(dotRL);
|
|
|
+ canvas.drawCircle(cx, cy, dotRL, mPaint);
|
|
|
+ }else if(i % 3 == 1){
|
|
|
+ mPaint.setStrokeWidth(dotRL);
|
|
|
+ canvas.drawText("/", cx, cy, mPaint);
|
|
|
+ }else{
|
|
|
+ mPaint.setStrokeWidth(dotRL);
|
|
|
+ canvas.drawRect(cx - 1, cy - 1, cx + 1, cy + 1, mPaint);
|
|
|
+ }
|
|
|
+
|
|
|
+ angleB += 2 * Math.PI / 14;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
|
+ super.onSizeChanged(w, h, oldw, oldh);
|
|
|
+ mCenterX = w / 2;
|
|
|
+ mCenterY = h / 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
|
+ int mWidth, mHeight;
|
|
|
+ mWidth = (int) (5.2 * mRadius + 2 * dotR);
|
|
|
+ mHeight = (int) (5.2 * mRadius + 2 * dotR);
|
|
|
+ setMeasuredDimension(mWidth, mHeight);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 点赞的变化效果
|
|
|
+ */
|
|
|
+ public void like() {
|
|
|
+ if (animatorTime != null && animatorTime.isRunning()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ resetState();
|
|
|
+ animatorTime = ValueAnimator.ofInt(0, 1200);
|
|
|
+ animatorTime.setDuration(mCycleTime);
|
|
|
+ animatorTime.setInterpolator(new DecelerateInterpolator());//需要随时间匀速变化
|
|
|
+ animatorTime.start();
|
|
|
+ animatorTime.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
|
|
+ @Override
|
|
|
+ public void onAnimationUpdate(ValueAnimator animation) {
|
|
|
+ int animatedValue = (int) animation.getAnimatedValue();
|
|
|
+
|
|
|
+ if (animatedValue <= 480) {
|
|
|
+ float percent = calcPercent(1f, 480f, animatedValue);//内环半径增大直至消亡
|
|
|
+ mCurrentPercent = percent;
|
|
|
+ mCurrentRadius = (int) (2 * mRadius);//外环半径不再改变
|
|
|
+ mCurrentState = RING_DOT__HEART_VIEW;
|
|
|
+ invalidate();
|
|
|
+ }
|
|
|
+ else if (animatedValue <= 1200) {
|
|
|
+ float percent = calcPercent(480f, 1200f, animatedValue);
|
|
|
+ mCurrentPercent = percent;
|
|
|
+ mCurrentState = DOT__HEART_VIEW;
|
|
|
+ invalidate();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重置为初始状态
|
|
|
+ */
|
|
|
+ private void resetState() {
|
|
|
+ mCurrentPercent = 0;
|
|
|
+ mCurrentRadius = 0;
|
|
|
+ isMax = false;
|
|
|
+ rDotL = 0;
|
|
|
+ offL = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private float calcPercent(float start, float end, float current) {
|
|
|
+ return (current - start) / (end - start);
|
|
|
+ }
|
|
|
+
|
|
|
+ private float dp2px(int value) {
|
|
|
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
|
|
|
+ getResources().getDisplayMetrics());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onDetachedFromWindow() {
|
|
|
+ super.onDetachedFromWindow();
|
|
|
+ if (animatorTime != null) {
|
|
|
+ animatorTime.removeAllListeners();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|