package MyJar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.TypeEvaluator; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.support.annotation.IntDef; import android.support.v4.view.animation.FastOutLinearInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.util.DisplayMetrics; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Random; public class ElectricFanLoadingRenderer extends LoadingRenderer { private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); private static final Interpolator FASTOUTLINEARIN_INTERPOLATOR = new FastOutLinearInInterpolator(); private static final Interpolator[] INTERPOLATORS = new Interpolator[]{LINEAR_INTERPOLATOR, DECELERATE_INTERPOLATOR, ACCELERATE_INTERPOLATOR, FASTOUTLINEARIN_INTERPOLATOR, MATERIAL_INTERPOLATOR}; private static final List mLeafHolders = new ArrayList<>(); private static final Random mRandom = new Random(); private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { @Override public void onAnimationRepeat(Animator animator) { super.onAnimationRepeat(animator); reset(); } }; public static final int MODE_NORMAL = 0; public static final int MODE_LEAF_COUNT = 1; @IntDef({MODE_NORMAL, MODE_LEAF_COUNT}) @Retention(RetentionPolicy.SOURCE) public @interface MODE { } private static final String PERCENTAGE_100 = "100%"; private static final long ANIMATION_DURATION = 7333; private static final int LEAF_COUNT = 28; private static final int DEGREE_180 = 180; private static final int DEGREE_360 = 360; private static final int FULL_GROUP_ROTATION = (int) (5.25f * DEGREE_360); private static final int DEFAULT_PROGRESS_COLOR = 0xfffca72e; private static final int DEFAULT_PROGRESS_BGCOLOR = 0xfffcd49f; private static final int DEFAULT_ELECTRIC_FAN_BGCOLOR = 0xfffccc59; private static final int DEFAULT_ELECTRIC_FAN_OUTLINE_COLOR = Color.WHITE; private static final float DEFAULT_WIDTH = 182.0f; private static final float DEFAULT_HEIGHT = 65.0f; private static final float DEFAULT_TEXT_SIZE = 11.0f; private static final float DEFAULT_STROKE_WIDTH = 2.0f; private static final float DEFAULT_STROKE_INTERVAL = .2f; private static final float DEFAULT_CENTER_RADIUS = 16.0f; private static final float DEFAULT_PROGRESS_CENTER_RADIUS = 11.0f; private static final float DEFAULT_LEAF_FLY_DURATION_FACTOR = 0.1f; private static final float LEAF_CREATE_DURATION_INTERVAL = 1.0f / LEAF_COUNT; private static final float DECELERATE_DURATION_PERCENTAGE = 0.4f; private static final float ACCELERATE_DURATION_PERCENTAGE = 0.6f; private final Paint mPaint = new Paint(); private final RectF mTempBounds = new RectF(); private final RectF mCurrentProgressBounds = new RectF(); private float mTextSize; private float mStrokeXInset; private float mStrokeYInset; private float mProgressCenterRadius; private float mScale; private float mRotation; private float mProgress; private float mNextLeafCreateThreshold; private int mProgressColor; private int mProgressBgColor; private int mElectricFanBgColor; private int mElectricFanOutlineColor; private float mStrokeWidth; private float mCenterRadius; @MODE private int mMode; private int mCurrentLeafCount; private Drawable mLeafDrawable; private Drawable mLoadingDrawable; private Drawable mElectricFanDrawable; public ElectricFanLoadingRenderer(Context context,int ic_leaf,int ic_loading,int ic_eletric_fan) { super(context); init(context, ic_leaf, ic_loading, ic_eletric_fan); setupPaint(); addRenderListener(mAnimatorListener); } private void init(Context context,int ic_leaf,int ic_loading,int ic_eletric_fan) { mMode = MODE_NORMAL; mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH); mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT); mTextSize = DensityUtil.dip2px(context, DEFAULT_TEXT_SIZE); mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS); mProgressCenterRadius = DensityUtil.dip2px(context, DEFAULT_PROGRESS_CENTER_RADIUS); mProgressColor = DEFAULT_PROGRESS_COLOR; mProgressBgColor = DEFAULT_PROGRESS_BGCOLOR; mElectricFanBgColor = DEFAULT_ELECTRIC_FAN_BGCOLOR; mElectricFanOutlineColor = DEFAULT_ELECTRIC_FAN_OUTLINE_COLOR; mLeafDrawable = context.getResources().getDrawable(ic_leaf); mLoadingDrawable = context.getResources().getDrawable(ic_loading); mElectricFanDrawable = context.getResources().getDrawable(ic_eletric_fan); mDuration = ANIMATION_DURATION; setInsets((int) mWidth, (int) mHeight); } private void setupPaint() { mPaint.setAntiAlias(true); mPaint.setStrokeWidth(mStrokeWidth); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeCap(Paint.Cap.ROUND); } @Override protected void draw(Canvas canvas, Rect bounds) { int saveCount = canvas.save(); RectF arcBounds = mTempBounds; arcBounds.set(bounds); arcBounds.inset(mStrokeXInset, mStrokeYInset); mCurrentProgressBounds.set(arcBounds.left, arcBounds.bottom - 2 * mCenterRadius, arcBounds.right, arcBounds.bottom); //draw loading drawable mLoadingDrawable.setBounds((int) arcBounds.centerX() - mLoadingDrawable.getIntrinsicWidth() / 2, 0, (int) arcBounds.centerX() + mLoadingDrawable.getIntrinsicWidth() / 2, mLoadingDrawable.getIntrinsicHeight()); mLoadingDrawable.draw(canvas); //draw progress background float progressInset = mCenterRadius - mProgressCenterRadius; RectF progressRect = new RectF(mCurrentProgressBounds); //sub DEFAULT_STROKE_INTERVAL, otherwise will have a interval between progress background and progress outline progressRect.inset(progressInset - DEFAULT_STROKE_INTERVAL, progressInset - DEFAULT_STROKE_INTERVAL); mPaint.setColor(mProgressBgColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawRoundRect(progressRect, mProgressCenterRadius, mProgressCenterRadius, mPaint); //draw progress mPaint.setColor(mProgressColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(createProgressPath(mProgress, mProgressCenterRadius, progressRect), mPaint); //draw leaves for (int i = 0; i < mLeafHolders.size(); i++) { int leafSaveCount = canvas.save(); LeafHolder leafHolder = mLeafHolders.get(i); Rect leafBounds = leafHolder.mLeafRect; canvas.rotate(leafHolder.mLeafRotation, leafBounds.centerX(), leafBounds.centerY()); mLeafDrawable.setBounds(leafBounds); mLeafDrawable.draw(canvas); canvas.restoreToCount(leafSaveCount); } //draw progress background outline, //after drawing the leaves and then draw the outline of the progress background can //prevent the leaves from flying to the outside RectF progressOutlineRect = new RectF(mCurrentProgressBounds); float progressOutlineStrokeInset = (mCenterRadius - mProgressCenterRadius) / 2.0f; progressOutlineRect.inset(progressOutlineStrokeInset, progressOutlineStrokeInset); mPaint.setStyle(Paint.Style.STROKE); mPaint.setColor(mProgressBgColor); mPaint.setStrokeWidth(mCenterRadius - mProgressCenterRadius); canvas.drawRoundRect(progressOutlineRect, mCenterRadius, mCenterRadius, mPaint); //draw electric fan outline float electricFanCenterX = arcBounds.right - mCenterRadius; float electricFanCenterY = arcBounds.bottom - mCenterRadius; mPaint.setColor(mElectricFanOutlineColor); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mStrokeWidth); canvas.drawCircle(arcBounds.right - mCenterRadius, arcBounds.bottom - mCenterRadius, mCenterRadius - mStrokeWidth / 2.0f, mPaint); //draw electric background mPaint.setColor(mElectricFanBgColor); mPaint.setStyle(Paint.Style.FILL); canvas.drawCircle(arcBounds.right - mCenterRadius, arcBounds.bottom - mCenterRadius, mCenterRadius - mStrokeWidth + DEFAULT_STROKE_INTERVAL, mPaint); //draw electric fan int rotateSaveCount = canvas.save(); canvas.rotate(mRotation, electricFanCenterX, electricFanCenterY); mElectricFanDrawable.setBounds((int) (electricFanCenterX - mElectricFanDrawable.getIntrinsicWidth() / 2 * mScale), (int) (electricFanCenterY - mElectricFanDrawable.getIntrinsicHeight() / 2 * mScale), (int) (electricFanCenterX + mElectricFanDrawable.getIntrinsicWidth() / 2 * mScale), (int) (electricFanCenterY + mElectricFanDrawable.getIntrinsicHeight() / 2 * mScale)); mElectricFanDrawable.draw(canvas); canvas.restoreToCount(rotateSaveCount); //draw 100% text if (mScale < 1.0f) { mPaint.setTextSize(mTextSize * (1 - mScale)); mPaint.setColor(mElectricFanOutlineColor); Rect textRect = new Rect(); mPaint.getTextBounds(PERCENTAGE_100, 0, PERCENTAGE_100.length(), textRect); canvas.drawText(PERCENTAGE_100, electricFanCenterX - textRect.width() / 2.0f, electricFanCenterY + textRect.height() / 2.0f, mPaint); } canvas.restoreToCount(saveCount); } private Path createProgressPath(float progress, float circleRadius, RectF progressRect) { RectF arcProgressRect = new RectF(progressRect.left, progressRect.top, progressRect.left + circleRadius * 2, progressRect.bottom); RectF rectProgressRect = null; float progressWidth = progress * progressRect.width(); float progressModeWidth = mMode == MODE_LEAF_COUNT ? (float) mCurrentLeafCount / (float) LEAF_COUNT * progressRect.width() : progress * progressRect.width(); float swipeAngle = DEGREE_180; //the left half circle of the progressbar if (progressModeWidth < circleRadius) { swipeAngle = progressModeWidth / circleRadius * DEGREE_180; } //the center rect of the progressbar if (progressModeWidth < progressRect.width() - circleRadius && progressModeWidth >= circleRadius) { rectProgressRect = new RectF(progressRect.left + circleRadius, progressRect.top, progressRect.left + progressModeWidth, progressRect.bottom); } //the right half circle of the progressbar if (progressWidth >= progressRect.width() - circleRadius) { rectProgressRect = new RectF(progressRect.left + circleRadius, progressRect.top, progressRect.right - circleRadius, progressRect.bottom); mScale = (progressRect.width() - progressWidth) / circleRadius; } //the left of the right half circle if (progressWidth < progressRect.width() - circleRadius) { mRotation = (progressWidth / (progressRect.width() - circleRadius)) * FULL_GROUP_ROTATION % DEGREE_360; RectF leafRect = new RectF(progressRect.left + progressWidth, progressRect.top, progressRect.right - circleRadius, progressRect.bottom); addLeaf(progress, leafRect); } Path path = new Path(); path.addArc(arcProgressRect, DEGREE_180 - swipeAngle / 2, swipeAngle); if (rectProgressRect != null) { path.addRect(rectProgressRect, Path.Direction.CW); } return path; } @Override protected void computeRender(float renderProgress) { if (renderProgress < DECELERATE_DURATION_PERCENTAGE) { mProgress = DECELERATE_INTERPOLATOR.getInterpolation(renderProgress / DECELERATE_DURATION_PERCENTAGE) * DECELERATE_DURATION_PERCENTAGE; } else { mProgress = ACCELERATE_INTERPOLATOR.getInterpolation((renderProgress - DECELERATE_DURATION_PERCENTAGE) / ACCELERATE_DURATION_PERCENTAGE) * ACCELERATE_DURATION_PERCENTAGE + DECELERATE_DURATION_PERCENTAGE; } } @Override protected void setAlpha(int alpha) { mPaint.setAlpha(alpha); } @Override protected void setColorFilter(ColorFilter cf) { mPaint.setColorFilter(cf); } @Override protected void reset() { mScale = 1.0f; mCurrentLeafCount = 0; mNextLeafCreateThreshold = 0.0f; mLeafHolders.clear(); } protected void setInsets(int width, int height) { final float minEdge = (float) Math.min(width, height); float insetXs; if (mCenterRadius <= 0 || minEdge < 0) { insetXs = (float) Math.ceil(mCenterRadius / 2.0f); } else { insetXs = mCenterRadius; } mStrokeYInset = (float) Math.ceil(mCenterRadius / 2.0f); mStrokeXInset = insetXs; } private void addLeaf(float progress, RectF leafFlyRect) { if (progress < mNextLeafCreateThreshold) { return; } mNextLeafCreateThreshold += LEAF_CREATE_DURATION_INTERVAL; LeafHolder leafHolder = new LeafHolder(); mLeafHolders.add(leafHolder); Animator leafAnimator = getAnimator(leafHolder, leafFlyRect, progress); leafAnimator.addListener(new AnimEndListener(leafHolder)); leafAnimator.start(); } private Animator getAnimator(LeafHolder target, RectF leafFlyRect, float progress) { ValueAnimator bezierValueAnimator = getBezierValueAnimator(target, leafFlyRect, progress); AnimatorSet finalSet = new AnimatorSet(); finalSet.playSequentially(bezierValueAnimator); finalSet.setInterpolator(INTERPOLATORS[mRandom.nextInt(INTERPOLATORS.length)]); finalSet.setTarget(target); return finalSet; } private ValueAnimator getBezierValueAnimator(LeafHolder target, RectF leafFlyRect, float progress) { BezierEvaluator evaluator = new BezierEvaluator(getPoint1(leafFlyRect), getPoint2(leafFlyRect)); int leafFlyStartY = (int) (mCurrentProgressBounds.bottom - mLeafDrawable.getIntrinsicHeight()); int leafFlyRange = (int) (mCurrentProgressBounds.height() - mLeafDrawable.getIntrinsicHeight()); int startPointY = leafFlyStartY - mRandom.nextInt(leafFlyRange); int endPointY = leafFlyStartY - mRandom.nextInt(leafFlyRange); ValueAnimator animator = ValueAnimator.ofObject(evaluator, new PointF((int) (leafFlyRect.right - mLeafDrawable.getIntrinsicWidth()), startPointY), new PointF(leafFlyRect.left, endPointY)); animator.addUpdateListener(new BezierListener(target)); animator.setTarget(target); animator.setDuration((long) ((mRandom.nextInt(300) + mDuration * DEFAULT_LEAF_FLY_DURATION_FACTOR) * (1.0f - progress))); return animator; } //get the pointF which belong to the right half side private PointF getPoint1(RectF leafFlyRect) { PointF point = new PointF(); point.x = leafFlyRect.right - mRandom.nextInt((int) (leafFlyRect.width() / 2)); point.y = (int) (leafFlyRect.bottom - mRandom.nextInt((int) leafFlyRect.height())); return point; } //get the pointF which belong to the left half side private PointF getPoint2(RectF leafFlyRect) { PointF point = new PointF(); point.x = leafFlyRect.left + mRandom.nextInt((int) (leafFlyRect.width() / 2)); point.y = (int) (leafFlyRect.bottom - mRandom.nextInt((int) leafFlyRect.height())); return point; } private class BezierEvaluator implements TypeEvaluator { private PointF point1; private PointF point2; public BezierEvaluator(PointF point1, PointF point2) { this.point1 = point1; this.point2 = point2; } //Third-order Bezier curve formula: B(t) = point0 * (1-t)^3 + 3 * point1 * t * (1-t)^2 + 3 * point2 * t^2 * (1-t) + point3 * t^3 @Override public PointF evaluate(float fraction, PointF point0, PointF point3) { float t = fraction; float tLeft = 1.0f - t; float x = (float) (point0.x * Math.pow(tLeft, 3) + 3 * point1.x * t * Math.pow(tLeft, 2) + 3 * point2.x * Math.pow(t, 2) * tLeft + point3.x * Math.pow(t, 3)); float y = (float) (point0.y * Math.pow(tLeft, 3) + 3 * point1.y * t * Math.pow(tLeft, 2) + 3 * point2.y * Math.pow(t, 2) * tLeft + point3.y * Math.pow(t, 3)); return new PointF(x, y); } } private class BezierListener implements ValueAnimator.AnimatorUpdateListener { private LeafHolder target; public BezierListener(LeafHolder target) { this.target = target; } @Override public void onAnimationUpdate(ValueAnimator animation) { PointF point = (PointF) animation.getAnimatedValue(); target.mLeafRect.set((int) point.x, (int) point.y, (int) (point.x + mLeafDrawable.getIntrinsicWidth()), (int) (point.y + mLeafDrawable.getIntrinsicHeight())); target.mLeafRotation = target.mMaxRotation * animation.getAnimatedFraction(); } } private class AnimEndListener extends AnimatorListenerAdapter { private LeafHolder target; public AnimEndListener(LeafHolder target) { this.target = target; } @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mLeafHolders.remove(target); mCurrentLeafCount++; } } private class LeafHolder { public Rect mLeafRect = new Rect(); public float mLeafRotation = 0.0f; public float mMaxRotation = mRandom.nextInt(120); } }