1:修改K歌房

2:修改房间展示每日任务
3:修改页面跳转
4:遗留问题在进入首页的时候出现首页刷新
This commit is contained in:
2025-10-27 20:09:12 +08:00
parent 8631fdbdbf
commit a4032c76ad
106 changed files with 6593 additions and 396 deletions

View File

@@ -0,0 +1,25 @@
plugins {
alias(libs.plugins.android.library)
}
android {
compileSdk 35
namespace 'app.dinus.com.loadingdrawable'
defaultConfig {
minSdkVersion 24
targetSdkVersion 35
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation 'androidx.core:core:1.5.0' // or later
implementation 'androidx.annotation:annotation:1.6.0'
implementation libs.androidx.interpolator
}

17
Loadinglibrary/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/dinus/workspace/android-sdk-macosx/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.dinus.com.loadingdrawable">
<application android:allowBackup="true" android:label="@string/app_name">
</application>
</manifest>

View File

@@ -0,0 +1,11 @@
package app.dinus.com.loadingdrawable;
import android.content.Context;
public class DensityUtil {
public static float dip2px(Context context, float dpValue) {
float scale = context.getResources().getDisplayMetrics().density;
return dpValue * scale;
}
}

View File

@@ -0,0 +1,77 @@
package app.dinus.com.loadingdrawable;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import app.dinus.com.loadingdrawable.render.LoadingDrawable;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
import app.dinus.com.loadingdrawable.render.LoadingRendererFactory;
public class LoadingView extends ImageView {
private LoadingDrawable mLoadingDrawable;
public LoadingView(Context context) {
super(context);
}
public LoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
}
private void initAttrs(Context context, AttributeSet attrs) {
try {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LoadingView);
int loadingRendererId = ta.getInt(R.styleable.LoadingView_loading_renderer, 0);
LoadingRenderer loadingRenderer = LoadingRendererFactory.createLoadingRenderer(context, loadingRendererId);
setLoadingRenderer(loadingRenderer);
ta.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}
public void setLoadingRenderer(LoadingRenderer loadingRenderer) {
mLoadingDrawable = new LoadingDrawable(loadingRenderer);
setImageDrawable(mLoadingDrawable);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startAnimation();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopAnimation();
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE;
if (visible) {
startAnimation();
} else {
stopAnimation();
}
}
private void startAnimation() {
if (mLoadingDrawable != null) {
mLoadingDrawable.start();
}
}
private void stopAnimation() {
if (mLoadingDrawable != null) {
mLoadingDrawable.stop();
}
}
}

View File

@@ -0,0 +1,89 @@
package app.dinus.com.loadingdrawable.render;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class LoadingDrawable extends Drawable implements Animatable {
private final LoadingRenderer mLoadingRender;
private final Callback mCallback = new Callback() {
@Override
public void invalidateDrawable(Drawable d) {
invalidateSelf();
}
@Override
public void scheduleDrawable(Drawable d, Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(Drawable d, Runnable what) {
unscheduleSelf(what);
}
};
public LoadingDrawable(LoadingRenderer loadingRender) {
this.mLoadingRender = loadingRender;
this.mLoadingRender.setCallback(mCallback);
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
this.mLoadingRender.setBounds(bounds);
}
@Override
public void draw(Canvas canvas) {
if (!getBounds().isEmpty()) {
this.mLoadingRender.draw(canvas);
}
}
@Override
public void setAlpha(int alpha) {
this.mLoadingRender.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
this.mLoadingRender.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void start() {
this.mLoadingRender.start();
}
@Override
public void stop() {
this.mLoadingRender.stop();
}
@Override
public boolean isRunning() {
return this.mLoadingRender.isRunning();
}
@Override
public int getIntrinsicHeight() {
return (int) this.mLoadingRender.mHeight;
}
@Override
public int getIntrinsicWidth() {
return (int) this.mLoadingRender.mWidth;
}
}

View File

@@ -0,0 +1,124 @@
package app.dinus.com.loadingdrawable.render;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
public abstract class LoadingRenderer {
private static final long ANIMATION_DURATION = 1333;
private static final float DEFAULT_SIZE = 56.0f;
private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
computeRender((float) animation.getAnimatedValue());
invalidateSelf();
}
};
/**
* Whenever {@link LoadingDrawable} boundary changes mBounds will be updated.
* More details you can see {@link LoadingDrawable#onBoundsChange(Rect)}
*/
protected final Rect mBounds = new Rect();
private Drawable.Callback mCallback;
private ValueAnimator mRenderAnimator;
protected long mDuration;
protected float mWidth;
protected float mHeight;
public LoadingRenderer(Context context) {
initParams(context);
setupAnimators();
}
@Deprecated
protected void draw(Canvas canvas, Rect bounds) {
}
protected void draw(Canvas canvas) {
draw(canvas, mBounds);
}
protected abstract void computeRender(float renderProgress);
protected abstract void setAlpha(int alpha);
protected abstract void setColorFilter(ColorFilter cf);
protected abstract void reset();
protected void addRenderListener(Animator.AnimatorListener animatorListener) {
mRenderAnimator.addListener(animatorListener);
}
void start() {
reset();
mRenderAnimator.addUpdateListener(mAnimatorUpdateListener);
mRenderAnimator.setRepeatCount(ValueAnimator.INFINITE);
mRenderAnimator.setDuration(mDuration);
mRenderAnimator.start();
}
void stop() {
// if I just call mRenderAnimator.end(),
// it will always call the method onAnimationUpdate(ValueAnimator animation)
// why ? if you know why please send email to me (dinus_developer@163.com)
mRenderAnimator.removeUpdateListener(mAnimatorUpdateListener);
mRenderAnimator.setRepeatCount(0);
mRenderAnimator.setDuration(0);
mRenderAnimator.end();
}
boolean isRunning() {
return mRenderAnimator.isRunning();
}
void setCallback(Drawable.Callback callback) {
this.mCallback = callback;
}
void setBounds(Rect bounds) {
mBounds.set(bounds);
}
private void initParams(Context context) {
mWidth = DensityUtil.dip2px(context, DEFAULT_SIZE);
mHeight = DensityUtil.dip2px(context, DEFAULT_SIZE);
mDuration = ANIMATION_DURATION;
}
private void setupAnimators() {
mRenderAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mRenderAnimator.setRepeatCount(Animation.INFINITE);
mRenderAnimator.setRepeatMode(Animation.RESTART);
mRenderAnimator.setDuration(mDuration);
//fuck you! the default interpolator is AccelerateDecelerateInterpolator
mRenderAnimator.setInterpolator(new LinearInterpolator());
mRenderAnimator.addUpdateListener(mAnimatorUpdateListener);
}
private void invalidateSelf() {
mCallback.invalidateDrawable(null);
}
}

View File

@@ -0,0 +1,71 @@
package app.dinus.com.loadingdrawable.render;
import android.content.Context;
import android.util.SparseArray;
import java.lang.reflect.Constructor;
import app.dinus.com.loadingdrawable.render.animal.FishLoadingRenderer;
import app.dinus.com.loadingdrawable.render.animal.GhostsEyeLoadingRenderer;
import app.dinus.com.loadingdrawable.render.circle.jump.CollisionLoadingRenderer;
import app.dinus.com.loadingdrawable.render.circle.jump.DanceLoadingRenderer;
import app.dinus.com.loadingdrawable.render.circle.jump.GuardLoadingRenderer;
import app.dinus.com.loadingdrawable.render.circle.jump.SwapLoadingRenderer;
import app.dinus.com.loadingdrawable.render.circle.rotate.GearLoadingRenderer;
import app.dinus.com.loadingdrawable.render.circle.rotate.LevelLoadingRenderer;
import app.dinus.com.loadingdrawable.render.circle.rotate.MaterialLoadingRenderer;
import app.dinus.com.loadingdrawable.render.circle.rotate.WhorlLoadingRenderer;
import app.dinus.com.loadingdrawable.render.goods.BalloonLoadingRenderer;
import app.dinus.com.loadingdrawable.render.goods.WaterBottleLoadingRenderer;
import app.dinus.com.loadingdrawable.render.scenery.DayNightLoadingRenderer;
import app.dinus.com.loadingdrawable.render.scenery.ElectricFanLoadingRenderer;
import app.dinus.com.loadingdrawable.render.shapechange.CircleBroodLoadingRenderer;
import app.dinus.com.loadingdrawable.render.shapechange.CoolWaitLoadingRenderer;
public final class LoadingRendererFactory {
private static final SparseArray<Class<? extends LoadingRenderer>> LOADING_RENDERERS = new SparseArray<>();
static {
//circle rotate
LOADING_RENDERERS.put(0, MaterialLoadingRenderer.class);
LOADING_RENDERERS.put(1, LevelLoadingRenderer.class);
LOADING_RENDERERS.put(2, WhorlLoadingRenderer.class);
LOADING_RENDERERS.put(3, GearLoadingRenderer.class);
//circle jump
LOADING_RENDERERS.put(4, SwapLoadingRenderer.class);
LOADING_RENDERERS.put(5, GuardLoadingRenderer.class);
LOADING_RENDERERS.put(6, DanceLoadingRenderer.class);
LOADING_RENDERERS.put(7, CollisionLoadingRenderer.class);
//scenery
LOADING_RENDERERS.put(8, DayNightLoadingRenderer.class);
LOADING_RENDERERS.put(9, ElectricFanLoadingRenderer.class);
//animal
LOADING_RENDERERS.put(10, FishLoadingRenderer.class);
LOADING_RENDERERS.put(11, GhostsEyeLoadingRenderer.class);
//goods
LOADING_RENDERERS.put(12, BalloonLoadingRenderer.class);
LOADING_RENDERERS.put(13, WaterBottleLoadingRenderer.class);
//shape change
LOADING_RENDERERS.put(14, CircleBroodLoadingRenderer.class);
LOADING_RENDERERS.put(15, CoolWaitLoadingRenderer.class);
}
private LoadingRendererFactory() {
}
public static LoadingRenderer createLoadingRenderer(Context context, int loadingRendererId) throws Exception {
Class<?> loadingRendererClazz = LOADING_RENDERERS.get(loadingRendererId);
Constructor<?>[] constructors = loadingRendererClazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes != null
&& parameterTypes.length == 1
&& parameterTypes[0].equals(Context.class)) {
constructor.setAccessible(true);
return (LoadingRenderer) constructor.newInstance(context);
}
}
throw new InstantiationException();
}
}

View File

@@ -0,0 +1,262 @@
package app.dinus.com.loadingdrawable.render.animal;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.DisplayMetrics;
import android.view.animation.Interpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class FishLoadingRenderer extends LoadingRenderer {
private Interpolator FISH_INTERPOLATOR = new FishInterpolator();
private static final float DEFAULT_PATH_FULL_LINE_SIZE = 7.0f;
private static final float DEFAULT_PATH_DOTTED_LINE_SIZE = DEFAULT_PATH_FULL_LINE_SIZE / 2.0f;
private static final float DEFAULT_RIVER_HEIGHT = DEFAULT_PATH_FULL_LINE_SIZE * 8.5f;
private static final float DEFAULT_RIVER_WIDTH = DEFAULT_PATH_FULL_LINE_SIZE * 5.5f;
private static final float DEFAULT_FISH_EYE_SIZE = DEFAULT_PATH_FULL_LINE_SIZE * 0.5f;
private static final float DEFAULT_FISH_WIDTH = DEFAULT_PATH_FULL_LINE_SIZE * 3.0f;
private static final float DEFAULT_FISH_HEIGHT = DEFAULT_PATH_FULL_LINE_SIZE * 4.5f;
private static final float DEFAULT_WIDTH = 200.0f;
private static final float DEFAULT_HEIGHT = 150.0f;
private static final float DEFAULT_RIVER_BANK_WIDTH = DEFAULT_PATH_FULL_LINE_SIZE;
private static final long ANIMATION_DURATION = 800;
private static final float DOTTED_LINE_WIDTH_COUNT = (8.5f + 5.5f - 2.0f) * 2.0f * 2.0f;
private static final float DOTTED_LINE_WIDTH_RATE = 1.0f / DOTTED_LINE_WIDTH_COUNT;
private final float[] FISH_MOVE_POINTS = new float[]{
DOTTED_LINE_WIDTH_RATE * 3.0f, DOTTED_LINE_WIDTH_RATE * 6.0f,
DOTTED_LINE_WIDTH_RATE * 15f, DOTTED_LINE_WIDTH_RATE * 18f,
DOTTED_LINE_WIDTH_RATE * 27.0f, DOTTED_LINE_WIDTH_RATE * 30.0f,
DOTTED_LINE_WIDTH_RATE * 39f, DOTTED_LINE_WIDTH_RATE * 42f,
};
private final float FISH_MOVE_POINTS_RATE = 1.0f / FISH_MOVE_POINTS.length;
private static final int DEFAULT_COLOR = Color.parseColor("#fffefed6");
private final Paint mPaint = new Paint();
private final RectF mTempBounds = new RectF();
private final float[] mFishHeadPos = new float[2];
private Path mRiverPath;
private PathMeasure mRiverMeasure;
private float mFishRotateDegrees;
private float mRiverBankWidth;
private float mRiverWidth;
private float mRiverHeight;
private float mFishWidth;
private float mFishHeight;
private float mFishEyeSize;
private float mPathFullLineSize;
private float mPathDottedLineSize;
private int mColor;
private FishLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
}
private void init(Context context) {
mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH);
mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT);
mRiverBankWidth = DensityUtil.dip2px(context, DEFAULT_RIVER_BANK_WIDTH);
mPathFullLineSize = DensityUtil.dip2px(context, DEFAULT_PATH_FULL_LINE_SIZE);
mPathDottedLineSize = DensityUtil.dip2px(context, DEFAULT_PATH_DOTTED_LINE_SIZE);
mFishWidth = DensityUtil.dip2px(context, DEFAULT_FISH_WIDTH);
mFishHeight = DensityUtil.dip2px(context, DEFAULT_FISH_HEIGHT);
mFishEyeSize = DensityUtil.dip2px(context, DEFAULT_FISH_EYE_SIZE);
mRiverWidth = DensityUtil.dip2px(context, DEFAULT_RIVER_WIDTH);
mRiverHeight = DensityUtil.dip2px(context, DEFAULT_RIVER_HEIGHT);
mColor = DEFAULT_COLOR;
mDuration = ANIMATION_DURATION;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mRiverBankWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.MITER);
mPaint.setPathEffect(new DashPathEffect(new float[]{mPathFullLineSize, mPathDottedLineSize}, mPathDottedLineSize));
}
@Override
protected void draw(Canvas canvas, Rect bounds) {
int saveCount = canvas.save();
RectF arcBounds = mTempBounds;
arcBounds.set(bounds);
mPaint.setColor(mColor);
//calculate fish clip bounds
//clip the width of the fish need to increase mPathDottedLineSize * 1.2f
RectF fishRectF = new RectF(mFishHeadPos[0] - mFishWidth / 2.0f - mPathDottedLineSize * 1.2f, mFishHeadPos[1] - mFishHeight / 2.0f,
mFishHeadPos[0] + mFishWidth / 2.0f + mPathDottedLineSize * 1.2f, mFishHeadPos[1] + mFishHeight / 2.0f);
Matrix matrix = new Matrix();
matrix.postRotate(mFishRotateDegrees, fishRectF.centerX(), fishRectF.centerY());
matrix.mapRect(fishRectF);
//draw river
int riverSaveCount = canvas.save();
mPaint.setStyle(Paint.Style.STROKE);
canvas.clipRect(fishRectF, Region.Op.DIFFERENCE);
canvas.drawPath(createRiverPath(arcBounds), mPaint);
canvas.restoreToCount(riverSaveCount);
//draw fish
int fishSaveCount = canvas.save();
mPaint.setStyle(Paint.Style.FILL);
canvas.rotate(mFishRotateDegrees, mFishHeadPos[0], mFishHeadPos[1]);
canvas.clipPath(createFishEyePath(mFishHeadPos[0], mFishHeadPos[1] - mFishHeight * 0.06f), Region.Op.DIFFERENCE);
canvas.drawPath(createFishPath(mFishHeadPos[0], mFishHeadPos[1]), mPaint);
canvas.restoreToCount(fishSaveCount);
canvas.restoreToCount(saveCount);
}
private float calculateRotateDegrees(float fishProgress) {
if (fishProgress < FISH_MOVE_POINTS_RATE * 2) {
return 90;
}
if (fishProgress < FISH_MOVE_POINTS_RATE * 4) {
return 180;
}
if (fishProgress < FISH_MOVE_POINTS_RATE * 6) {
return 270;
}
return 0.0f;
}
@Override
protected void computeRender(float renderProgress) {
if (mRiverPath == null) {
return;
}
if (mRiverMeasure == null) {
mRiverMeasure = new PathMeasure(mRiverPath, false);
}
float fishProgress = FISH_INTERPOLATOR.getInterpolation(renderProgress);
mRiverMeasure.getPosTan(mRiverMeasure.getLength() * fishProgress, mFishHeadPos, null);
mFishRotateDegrees = calculateRotateDegrees(fishProgress);
}
@Override
protected void setAlpha(int alpha) {
}
@Override
protected void setColorFilter(ColorFilter cf) {
}
@Override
protected void reset() {
}
private Path createFishEyePath(float fishEyeCenterX, float fishEyeCenterY) {
Path path = new Path();
path.addCircle(fishEyeCenterX, fishEyeCenterY, mFishEyeSize, Path.Direction.CW);
return path;
}
private Path createFishPath(float fishCenterX, float fishCenterY) {
Path path = new Path();
float fishHeadX = fishCenterX;
float fishHeadY = fishCenterY - mFishHeight / 2.0f;
//the head of the fish
path.moveTo(fishHeadX, fishHeadY);
//the left body of the fish
path.quadTo(fishHeadX - mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.222f, fishHeadX - mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.444f);
path.lineTo(fishHeadX - mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.666f);
path.lineTo(fishHeadX - mFishWidth * 0.5f, fishHeadY + mFishHeight * 0.8f);
path.lineTo(fishHeadX - mFishWidth * 0.5f, fishHeadY + mFishHeight);
//the tail of the fish
path.lineTo(fishHeadX, fishHeadY + mFishHeight * 0.9f);
//the right body of the fish
path.lineTo(fishHeadX + mFishWidth * 0.5f, fishHeadY + mFishHeight);
path.lineTo(fishHeadX + mFishWidth * 0.5f, fishHeadY + mFishHeight * 0.8f);
path.lineTo(fishHeadX + mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.666f);
path.lineTo(fishHeadX + mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.444f);
path.quadTo(fishHeadX + mFishWidth * 0.333f, fishHeadY + mFishHeight * 0.222f, fishHeadX, fishHeadY);
path.close();
return path;
}
private Path createRiverPath(RectF arcBounds) {
if (mRiverPath != null) {
return mRiverPath;
}
mRiverPath = new Path();
RectF rectF = new RectF(arcBounds.centerX() - mRiverWidth / 2.0f, arcBounds.centerY() - mRiverHeight / 2.0f,
arcBounds.centerX() + mRiverWidth / 2.0f, arcBounds.centerY() + mRiverHeight / 2.0f);
rectF.inset(mRiverBankWidth / 2.0f, mRiverBankWidth / 2.0f);
mRiverPath.addRect(rectF, Path.Direction.CW);
return mRiverPath;
}
private class FishInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
int index = ((int) (input / FISH_MOVE_POINTS_RATE));
if (index >= FISH_MOVE_POINTS.length) {
index = FISH_MOVE_POINTS.length - 1;
}
return FISH_MOVE_POINTS[index];
}
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public FishLoadingRenderer build() {
FishLoadingRenderer loadingRenderer = new FishLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,261 @@
package app.dinus.com.loadingdrawable.render.animal;
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.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.view.animation.Interpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class GhostsEyeLoadingRenderer extends LoadingRenderer {
private Interpolator EYE_BALL_INTERPOLATOR = new EyeBallInterpolator();
private Interpolator EYE_CIRCLE_INTERPOLATOR = new EyeCircleInterpolator();
private static final float DEFAULT_WIDTH = 200.0f;
private static final float DEFAULT_HEIGHT = 176.0f;
private static final float DEFAULT_EYE_EDGE_WIDTH = 5.0f;
private static final float DEFAULT_EYE_BALL_HEIGHT = 9.0f;
private static final float DEFAULT_EYE_BALL_WIDTH = 11.0f;
private static final float DEFAULT_EYE_CIRCLE_INTERVAL = 8.0f;
private static final float DEFAULT_EYE_BALL_OFFSET_Y = 2.0f;
private static final float DEFAULT_ABOVE_RADIAN_EYE_CIRCLE_OFFSET = 6.0f;
private static final float DEFAULT_EYE_CIRCLE_RADIUS = 21.0f;
private static final float DEFAULT_MAX_EYE_JUMP_DISTANCE = 11.0f;
private static final float LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET = 0.0f;
private static final float RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET = 0.067f;
private static final float LEFT_EYE_BALL_END_JUMP_OFFSET = 0.4f;
private static final float LEFT_EYE_CIRCLE_END_JUMP_OFFSET = 0.533f;
private static final float RIGHT_EYE_BALL_END_JUMP_OFFSET = 0.467f;
private static final float RIGHT_EYE_CIRCLE_END_JUMP_OFFSET = 0.60f;
private static final int DEGREE_180 = 180;
private static final long ANIMATION_DURATION = 2333;
private static final int DEFAULT_COLOR = Color.parseColor("#ff484852");
private final Paint mPaint = new Paint();
private final RectF mTempBounds = new RectF();
private float mEyeInterval;
private float mEyeCircleRadius;
private float mMaxEyeJumptDistance;
private float mAboveRadianEyeOffsetX;
private float mEyeBallOffsetY;
private float mEyeEdgeWidth;
private float mEyeBallWidth;
private float mEyeBallHeight;
private float mLeftEyeCircleOffsetY;
private float mRightEyeCircleOffsetY;
private float mLeftEyeBallOffsetY;
private float mRightEyeBallOffsetY;
private int mColor;
private GhostsEyeLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
}
private void init(Context context) {
mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH);
mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT);
mEyeEdgeWidth = DensityUtil.dip2px(context, DEFAULT_EYE_EDGE_WIDTH);
mEyeInterval = DensityUtil.dip2px(context, DEFAULT_EYE_CIRCLE_INTERVAL);
mEyeBallOffsetY = DensityUtil.dip2px(context, DEFAULT_EYE_BALL_OFFSET_Y);
mEyeCircleRadius = DensityUtil.dip2px(context, DEFAULT_EYE_CIRCLE_RADIUS);
mMaxEyeJumptDistance = DensityUtil.dip2px(context, DEFAULT_MAX_EYE_JUMP_DISTANCE);
mAboveRadianEyeOffsetX = DensityUtil.dip2px(context, DEFAULT_ABOVE_RADIAN_EYE_CIRCLE_OFFSET);
mEyeBallWidth = DensityUtil.dip2px(context, DEFAULT_EYE_BALL_WIDTH);
mEyeBallHeight = DensityUtil.dip2px(context, DEFAULT_EYE_BALL_HEIGHT);
mColor = DEFAULT_COLOR;
mDuration = ANIMATION_DURATION;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mEyeEdgeWidth);
mPaint.setStrokeJoin(Paint.Join.ROUND);
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);
mPaint.setColor(mColor);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(createLeftEyeCircle(arcBounds, mLeftEyeCircleOffsetY), mPaint);
canvas.drawPath(createRightEyeCircle(arcBounds, mRightEyeCircleOffsetY), mPaint);
mPaint.setStyle(Paint.Style.FILL);
//create left eye ball
canvas.drawOval(createLeftEyeBall(arcBounds, mLeftEyeBallOffsetY), mPaint);
//create right eye ball
canvas.drawOval(createRightEyeBall(arcBounds, mRightEyeBallOffsetY), mPaint);
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
if (renderProgress <= LEFT_EYE_BALL_END_JUMP_OFFSET && renderProgress >= LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) {
float eyeCircle$BallJumpUpProgress = (renderProgress - LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) / (LEFT_EYE_BALL_END_JUMP_OFFSET - LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET);
mLeftEyeBallOffsetY = -mMaxEyeJumptDistance * EYE_BALL_INTERPOLATOR.getInterpolation(eyeCircle$BallJumpUpProgress);
}
if (renderProgress <= LEFT_EYE_CIRCLE_END_JUMP_OFFSET && renderProgress >= LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) {
float eyeCircle$BallJumpUpProgress = (renderProgress - LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) / (LEFT_EYE_CIRCLE_END_JUMP_OFFSET - LEFT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET);
mLeftEyeCircleOffsetY = -mMaxEyeJumptDistance * EYE_CIRCLE_INTERPOLATOR.getInterpolation(eyeCircle$BallJumpUpProgress);
}
if (renderProgress <= RIGHT_EYE_BALL_END_JUMP_OFFSET && renderProgress >= RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) {
float eyeCircle$BallJumpUpProgress = (renderProgress - RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) / (RIGHT_EYE_BALL_END_JUMP_OFFSET - RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET);
mRightEyeBallOffsetY = -mMaxEyeJumptDistance * EYE_BALL_INTERPOLATOR.getInterpolation(eyeCircle$BallJumpUpProgress);
}
if (renderProgress <= RIGHT_EYE_CIRCLE_END_JUMP_OFFSET && renderProgress >= RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) {
float eyeCircle$BallJumpUpProgress = (renderProgress - RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET) / (RIGHT_EYE_CIRCLE_END_JUMP_OFFSET - RIGHT_EYE_CIRCLE$BALL_START_JUMP_UP_OFFSET);
mRightEyeCircleOffsetY = -mMaxEyeJumptDistance * EYE_CIRCLE_INTERPOLATOR.getInterpolation(eyeCircle$BallJumpUpProgress);
}
}
@Override
protected void setAlpha(int alpha) {
}
@Override
protected void setColorFilter(ColorFilter cf) {
}
@Override
protected void reset() {
mLeftEyeBallOffsetY = 0.0f;
mRightEyeBallOffsetY = 0.0f;
mLeftEyeCircleOffsetY = 0.0f;
mRightEyeCircleOffsetY = 0.0f;
}
private RectF createLeftEyeBall(RectF arcBounds, float offsetY) {
//the center of the left eye
float leftEyeCenterX = arcBounds.centerX() - mEyeInterval / 2.0f - mEyeCircleRadius;
float leftEyeCenterY = arcBounds.centerY() - mEyeBallOffsetY + offsetY;
RectF rectF = new RectF(leftEyeCenterX - mEyeBallWidth / 2.0f, leftEyeCenterY - mEyeBallHeight / 2.0f,
leftEyeCenterX + mEyeBallWidth / 2.0f, leftEyeCenterY + mEyeBallHeight / 2.0f);
return rectF;
}
private RectF createRightEyeBall(RectF arcBounds, float offsetY) {
//the center of the right eye
float rightEyeCenterX = arcBounds.centerX() + mEyeInterval / 2.0f + mEyeCircleRadius;
float rightEyeCenterY = arcBounds.centerY() - mEyeBallOffsetY + offsetY;
RectF rectF = new RectF(rightEyeCenterX - mEyeBallWidth / 2.0f, rightEyeCenterY - mEyeBallHeight / 2.0f,
rightEyeCenterX + mEyeBallWidth / 2.0f, rightEyeCenterY + mEyeBallHeight / 2.0f);
return rectF;
}
private Path createLeftEyeCircle(RectF arcBounds, float offsetY) {
Path path = new Path();
//the center of the left eye
float leftEyeCenterX = arcBounds.centerX() - mEyeInterval / 2.0f - mEyeCircleRadius;
float leftEyeCenterY = arcBounds.centerY() + offsetY;
//the bounds of left eye
RectF leftEyeBounds = new RectF(leftEyeCenterX - mEyeCircleRadius, leftEyeCenterY - mEyeCircleRadius,
leftEyeCenterX + mEyeCircleRadius, leftEyeCenterY + mEyeCircleRadius);
path.addArc(leftEyeBounds, 0, DEGREE_180 + 15);
//the above radian of of the eye
path.quadTo(leftEyeBounds.left + mAboveRadianEyeOffsetX, leftEyeBounds.top + mEyeCircleRadius * 0.2f,
leftEyeBounds.left + mAboveRadianEyeOffsetX / 4.0f, leftEyeBounds.top - mEyeCircleRadius * 0.15f);
return path;
}
private Path createRightEyeCircle(RectF arcBounds, float offsetY) {
Path path = new Path();
//the center of the right eye
float rightEyeCenterX = arcBounds.centerX() + mEyeInterval / 2.0f + mEyeCircleRadius;
float rightEyeCenterY = arcBounds.centerY() + offsetY;
//the bounds of left eye
RectF leftEyeBounds = new RectF(rightEyeCenterX - mEyeCircleRadius, rightEyeCenterY - mEyeCircleRadius,
rightEyeCenterX + mEyeCircleRadius, rightEyeCenterY + mEyeCircleRadius);
path.addArc(leftEyeBounds, 180, -(DEGREE_180 + 15));
//the above radian of of the eye
path.quadTo(leftEyeBounds.right - mAboveRadianEyeOffsetX, leftEyeBounds.top + mEyeCircleRadius * 0.2f,
leftEyeBounds.right - mAboveRadianEyeOffsetX / 4.0f, leftEyeBounds.top - mEyeCircleRadius * 0.15f);
return path;
}
private class EyeCircleInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
if (input < 0.25f) {
return input * 4.0f;
} else if (input < 0.5f) {
return 1.0f - (input - 0.25f) * 4.0f;
} else if (input < 0.75f) {
return (input - 0.5f) * 2.0f;
} else {
return 0.5f - (input - 0.75f) * 2.0f;
}
}
}
private class EyeBallInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
if (input < 0.333333f) {
return input * 3.0f;
} else {
return 1.0f - (input - 0.333333f) * 1.5f;
}
}
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public GhostsEyeLoadingRenderer build() {
GhostsEyeLoadingRenderer loadingRenderer = new GhostsEyeLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,303 @@
package app.dinus.com.loadingdrawable.render.circle.jump;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import androidx.annotation.Size;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class CollisionLoadingRenderer extends LoadingRenderer {
private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
private static final int MAX_ALPHA = 255;
private static final int OVAL_ALPHA = 64;
private static final int DEFAULT_BALL_COUNT = 7;
private static final float DEFAULT_OVAL_HEIGHT = 1.5f;
private static final float DEFAULT_BALL_RADIUS = 7.5f;
private static final float DEFAULT_WIDTH = 15.0f * 11;
private static final float DEFAULT_HEIGHT = 15.0f * 4;
private static final float START_LEFT_DURATION_OFFSET = 0.25f;
private static final float START_RIGHT_DURATION_OFFSET = 0.5f;
private static final float END_RIGHT_DURATION_OFFSET = 0.75f;
private static final float END_LEFT_DURATION_OFFSET = 1.0f;
private static final int[] DEFAULT_COLORS = new int[]{
Color.parseColor("#FF28435D"), Color.parseColor("#FFC32720")
};
private static final float[] DEFAULT_POSITIONS = new float[]{
0.0f, 1.0f
};
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RectF mOvalRect = new RectF();
@Size(2)
private int[] mColors;
private float[] mPositions;
private float mOvalVerticalRadius;
private float mBallRadius;
private float mBallCenterY;
private float mBallSideOffsets;
private float mBallMoveXOffsets;
private float mBallQuadCoefficient;
private float mLeftBallMoveXOffsets;
private float mLeftBallMoveYOffsets;
private float mRightBallMoveXOffsets;
private float mRightBallMoveYOffsets;
private float mLeftOvalShapeRate;
private float mRightOvalShapeRate;
private int mBallCount;
private CollisionLoadingRenderer(Context context) {
super(context);
init(context);
adjustParams();
setupPaint();
}
private void init(Context context) {
mBallRadius = DensityUtil.dip2px(context, DEFAULT_BALL_RADIUS);
mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH);
mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT);
mOvalVerticalRadius = DensityUtil.dip2px(context, DEFAULT_OVAL_HEIGHT);
mColors = DEFAULT_COLORS;
mPositions = DEFAULT_POSITIONS;
mBallCount = DEFAULT_BALL_COUNT;
//mBallMoveYOffsets = mBallQuadCoefficient * mBallMoveXOffsets ^ 2
// ==> if mBallMoveYOffsets == mBallMoveXOffsets
// ==> mBallQuadCoefficient = 1.0f / mBallMoveXOffsets;
mBallMoveXOffsets = 1.5f * (2 * mBallRadius);
mBallQuadCoefficient = 1.0f / mBallMoveXOffsets;
}
private void adjustParams() {
mBallCenterY = mHeight / 2.0f;
mBallSideOffsets = (mWidth - mBallRadius * 2.0f * (mBallCount - 2)) / 2;
}
private void setupPaint() {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShader(new LinearGradient(mBallSideOffsets, 0, mWidth - mBallSideOffsets, 0,
mColors, mPositions, Shader.TileMode.CLAMP));
}
@Override
protected void draw(Canvas canvas) {
int saveCount = canvas.save();
for (int i = 1; i < mBallCount - 1; i++) {
mPaint.setAlpha(MAX_ALPHA);
canvas.drawCircle(mBallRadius * (i * 2 - 1) + mBallSideOffsets, mBallCenterY, mBallRadius, mPaint);
mOvalRect.set(mBallRadius * (i * 2 - 2) + mBallSideOffsets, mHeight - mOvalVerticalRadius * 2,
mBallRadius * (i * 2) + mBallSideOffsets, mHeight);
mPaint.setAlpha(OVAL_ALPHA);
canvas.drawOval(mOvalRect, mPaint);
}
//draw the first ball
mPaint.setAlpha(MAX_ALPHA);
canvas.drawCircle(mBallSideOffsets - mBallRadius - mLeftBallMoveXOffsets,
mBallCenterY - mLeftBallMoveYOffsets, mBallRadius, mPaint);
mOvalRect.set(mBallSideOffsets - mBallRadius - mBallRadius * mLeftOvalShapeRate - mLeftBallMoveXOffsets,
mHeight - mOvalVerticalRadius - mOvalVerticalRadius * mLeftOvalShapeRate,
mBallSideOffsets - mBallRadius + mBallRadius * mLeftOvalShapeRate - mLeftBallMoveXOffsets,
mHeight - mOvalVerticalRadius + mOvalVerticalRadius * mLeftOvalShapeRate);
mPaint.setAlpha(OVAL_ALPHA);
canvas.drawOval(mOvalRect, mPaint);
//draw the last ball
mPaint.setAlpha(MAX_ALPHA);
canvas.drawCircle(mBallRadius * (mBallCount * 2 - 3) + mBallSideOffsets + mRightBallMoveXOffsets,
mBallCenterY - mRightBallMoveYOffsets, mBallRadius, mPaint);
mOvalRect.set(mBallRadius * (mBallCount * 2 - 3) - mBallRadius * mRightOvalShapeRate + mBallSideOffsets + mRightBallMoveXOffsets,
mHeight - mOvalVerticalRadius - mOvalVerticalRadius * mRightOvalShapeRate,
mBallRadius * (mBallCount * 2 - 3) + mBallRadius * mRightOvalShapeRate + mBallSideOffsets + mRightBallMoveXOffsets,
mHeight - mOvalVerticalRadius + mOvalVerticalRadius * mRightOvalShapeRate);
mPaint.setAlpha(OVAL_ALPHA);
canvas.drawOval(mOvalRect, mPaint);
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
// Moving the left ball to the left sides only occurs in the first 25% of a jump animation
if (renderProgress <= START_LEFT_DURATION_OFFSET) {
float startLeftOffsetProgress = renderProgress / START_LEFT_DURATION_OFFSET;
computeLeftBallMoveOffsets(DECELERATE_INTERPOLATOR.getInterpolation(startLeftOffsetProgress));
return;
}
// Moving the left ball to the origin location only occurs between 25% and 50% of a jump ring animation
if (renderProgress <= START_RIGHT_DURATION_OFFSET) {
float startRightOffsetProgress = (renderProgress - START_LEFT_DURATION_OFFSET) / (START_RIGHT_DURATION_OFFSET - START_LEFT_DURATION_OFFSET);
computeLeftBallMoveOffsets(ACCELERATE_INTERPOLATOR.getInterpolation(1.0f - startRightOffsetProgress));
return;
}
// Moving the right ball to the right sides only occurs between 50% and 75% of a jump animation
if (renderProgress <= END_RIGHT_DURATION_OFFSET) {
float endRightOffsetProgress = (renderProgress - START_RIGHT_DURATION_OFFSET) / (END_RIGHT_DURATION_OFFSET - START_RIGHT_DURATION_OFFSET);
computeRightBallMoveOffsets(DECELERATE_INTERPOLATOR.getInterpolation(endRightOffsetProgress));
return;
}
// Moving the right ball to the origin location only occurs after 75% of a jump animation
if (renderProgress <= END_LEFT_DURATION_OFFSET) {
float endRightOffsetProgress = (renderProgress - END_RIGHT_DURATION_OFFSET) / (END_LEFT_DURATION_OFFSET - END_RIGHT_DURATION_OFFSET);
computeRightBallMoveOffsets(ACCELERATE_INTERPOLATOR.getInterpolation(1 - endRightOffsetProgress));
return;
}
}
private void computeLeftBallMoveOffsets(float progress) {
mRightBallMoveXOffsets = 0.0f;
mRightBallMoveYOffsets = 0.0f;
mLeftOvalShapeRate = 1.0f - progress;
mLeftBallMoveXOffsets = mBallMoveXOffsets * progress;
mLeftBallMoveYOffsets = (float) (Math.pow(mLeftBallMoveXOffsets, 2) * mBallQuadCoefficient);
}
private void computeRightBallMoveOffsets(float progress) {
mLeftBallMoveXOffsets = 0.0f;
mLeftBallMoveYOffsets = 0.0f;
mRightOvalShapeRate = 1.0f - progress;
mRightBallMoveXOffsets = mBallMoveXOffsets * progress;
mRightBallMoveYOffsets = (float) (Math.pow(mRightBallMoveXOffsets, 2) * mBallQuadCoefficient);
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
}
private void apply(Builder builder) {
this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth;
this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight;
this.mOvalVerticalRadius = builder.mOvalVerticalRadius > 0 ? builder.mOvalVerticalRadius : this.mOvalVerticalRadius;
this.mBallRadius = builder.mBallRadius > 0 ? builder.mBallRadius : this.mBallRadius;
this.mBallMoveXOffsets = builder.mBallMoveXOffsets > 0 ? builder.mBallMoveXOffsets : this.mBallMoveXOffsets;
this.mBallQuadCoefficient = builder.mBallQuadCoefficient > 0 ? builder.mBallQuadCoefficient : this.mBallQuadCoefficient;
this.mBallCount = builder.mBallCount > 0 ? builder.mBallCount : this.mBallCount;
this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration;
this.mColors = builder.mColors != null ? builder.mColors : this.mColors;
adjustParams();
setupPaint();
}
public static class Builder {
private Context mContext;
private int mWidth;
private int mHeight;
private float mOvalVerticalRadius;
private int mBallCount;
private float mBallRadius;
private float mBallMoveXOffsets;
private float mBallQuadCoefficient;
private int mDuration;
@Size(2)
private int[] mColors;
public Builder(Context mContext) {
this.mContext = mContext;
}
public Builder setWidth(int width) {
this.mWidth = width;
return this;
}
public Builder setHeight(int height) {
this.mHeight = height;
return this;
}
public Builder setOvalVerticalRadius(int ovalVerticalRadius) {
this.mOvalVerticalRadius = ovalVerticalRadius;
return this;
}
public Builder setBallRadius(int ballRadius) {
this.mBallRadius = ballRadius;
return this;
}
public Builder setBallMoveXOffsets(int ballMoveXOffsets) {
this.mBallMoveXOffsets = ballMoveXOffsets;
return this;
}
public Builder setBallQuadCoefficient(int ballQuadCoefficient) {
this.mBallQuadCoefficient = ballQuadCoefficient;
return this;
}
public Builder setBallCount(int ballCount) {
this.mBallCount = ballCount;
return this;
}
public Builder setColors(@Size(2) int[] colors) {
this.mColors = colors;
return this;
}
public Builder setDuration(int duration) {
this.mDuration = duration;
return this;
}
public CollisionLoadingRenderer build() {
CollisionLoadingRenderer loadingRenderer = new CollisionLoadingRenderer(mContext);
loadingRenderer.apply(this);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,339 @@
package app.dinus.com.loadingdrawable.render.circle.jump;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class DanceLoadingRenderer extends LoadingRenderer {
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
private static final long ANIMATION_DURATION = 1888;
private static final float DEFAULT_CENTER_RADIUS = 12.5f;
private static final float DEFAULT_STROKE_WIDTH = 1.5f;
private static final float DEFAULT_DANCE_BALL_RADIUS = 2.0f;
private static final int NUM_POINTS = 3;
private static final int DEGREE_360 = 360;
private static final int RING_START_ANGLE = -90;
private static final int DANCE_START_ANGLE = 0;
private static final int DANCE_INTERVAL_ANGLE = 60;
private static final int DEFAULT_COLOR = Color.WHITE;
//the center coordinate of the oval
private static final float[] POINT_X = new float[NUM_POINTS];
private static final float[] POINT_Y = new float[NUM_POINTS];
//1: the coordinate x from small to large; -1: the coordinate x from large to small
private static final int[] DIRECTION = new int[]{1, 1, -1};
private static final float BALL_FORWARD_START_ENTER_DURATION_OFFSET = 0f;
private static final float BALL_FORWARD_END_ENTER_DURATION_OFFSET = 0.125f;
private static final float RING_FORWARD_START_ROTATE_DURATION_OFFSET = 0.125f;
private static final float RING_FORWARD_END_ROTATE_DURATION_OFFSET = 0.375f;
private static final float CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET = 0.225f;
private static final float CENTER_CIRCLE_FORWARD_END_SCALE_DURATION_OFFSET = 0.475f;
private static final float BALL_FORWARD_START_EXIT_DURATION_OFFSET = 0.375f;
private static final float BALL_FORWARD_END_EXIT_DURATION_OFFSET = 0.54f;
private static final float RING_REVERSAL_START_ROTATE_DURATION_OFFSET = 0.5f;
private static final float RING_REVERSAL_END_ROTATE_DURATION_OFFSET = 0.75f;
private static final float BALL_REVERSAL_START_ENTER_DURATION_OFFSET = 0.6f;
private static final float BALL_REVERSAL_END_ENTER_DURATION_OFFSET = 0.725f;
private static final float CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET = 0.675f;
private static final float CENTER_CIRCLE_REVERSAL_END_SCALE_DURATION_OFFSET = 0.875f;
private static final float BALL_REVERSAL_START_EXIT_DURATION_OFFSET = 0.875f;
private static final float BALL_REVERSAL_END_EXIT_DURATION_OFFSET = 1.0f;
private final Paint mPaint = new Paint();
private final RectF mTempBounds = new RectF();
private final RectF mCurrentBounds = new RectF();
private float mScale;
private float mRotation;
private float mStrokeInset;
private float mCenterRadius;
private float mStrokeWidth;
private float mDanceBallRadius;
private float mShapeChangeWidth;
private float mShapeChangeHeight;
private int mColor;
private int mArcColor;
private DanceLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
}
private void init(Context context) {
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS);
mDanceBallRadius = DensityUtil.dip2px(context, DEFAULT_DANCE_BALL_RADIUS);
setColor(DEFAULT_COLOR);
setInsets((int) mWidth, (int) mHeight);
mDuration = ANIMATION_DURATION;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
}
@Override
protected void draw(Canvas canvas, Rect bounds) {
int saveCount = canvas.save();
mTempBounds.set(bounds);
mTempBounds.inset(mStrokeInset, mStrokeInset);
mCurrentBounds.set(mTempBounds);
float outerCircleRadius = Math.min(mTempBounds.height(), mTempBounds.width()) / 2.0f;
float interCircleRadius = outerCircleRadius / 2.0f;
float centerRingWidth = interCircleRadius - mStrokeWidth / 2;
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mColor);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawCircle(mTempBounds.centerX(), mTempBounds.centerY(), outerCircleRadius, mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mTempBounds.centerX(), mTempBounds.centerY(), interCircleRadius * mScale, mPaint);
if (mRotation != 0) {
mPaint.setColor(mArcColor);
mPaint.setStyle(Paint.Style.STROKE);
//strokeWidth / 2.0f + mStrokeWidth / 2.0f is the center of the inter circle width
mTempBounds.inset(centerRingWidth / 2.0f + mStrokeWidth / 2.0f, centerRingWidth / 2.0f + mStrokeWidth / 2.0f);
mPaint.setStrokeWidth(centerRingWidth);
canvas.drawArc(mTempBounds, RING_START_ANGLE, mRotation, false, mPaint);
}
mPaint.setColor(mColor);
mPaint.setStyle(Paint.Style.FILL);
for (int i = 0; i < NUM_POINTS; i++) {
canvas.rotate(i * DANCE_INTERVAL_ANGLE, POINT_X[i], POINT_Y[i]);
RectF rectF = new RectF(POINT_X[i] - mDanceBallRadius - mShapeChangeWidth / 2.0f,
POINT_Y[i] - mDanceBallRadius - mShapeChangeHeight / 2.0f,
POINT_X[i] + mDanceBallRadius + mShapeChangeWidth / 2.0f,
POINT_Y[i] + mDanceBallRadius + mShapeChangeHeight / 2.0f);
canvas.drawOval(rectF, mPaint);
canvas.rotate(-i * DANCE_INTERVAL_ANGLE, POINT_X[i], POINT_Y[i]);
}
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
float radius = Math.min(mCurrentBounds.height(), mCurrentBounds.width()) / 2.0f;
//the origin coordinate is the centerLeft of the field mCurrentBounds
float originCoordinateX = mCurrentBounds.left;
float originCoordinateY = mCurrentBounds.top + radius;
if (renderProgress <= BALL_FORWARD_END_ENTER_DURATION_OFFSET && renderProgress > BALL_FORWARD_START_ENTER_DURATION_OFFSET) {
final float ballForwardEnterProgress = (renderProgress - BALL_FORWARD_START_ENTER_DURATION_OFFSET) / (BALL_FORWARD_END_ENTER_DURATION_OFFSET - BALL_FORWARD_START_ENTER_DURATION_OFFSET);
mShapeChangeHeight = (0.5f - ballForwardEnterProgress) * mDanceBallRadius / 2.0f;
mShapeChangeWidth = -mShapeChangeHeight;
//y = k(x - r)--> k = tan(angle)
//(x - r)^2 + y^2 = r^2
// compute crossover point --> (k(x -r)) ^ 2 + (x - )^2 = r^2
// so x --> [r + r / sqrt(k ^ 2 + 1), r - r / sqrt(k ^ 2 + 1)]
for (int i = 0; i < NUM_POINTS; i++) {
float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI));
// progress[-1, 1]
float progress = (ACCELERATE_INTERPOLATOR.getInterpolation(ballForwardEnterProgress) / 2.0f - 0.5f) * 2.0f * DIRECTION[i];
POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f)));
POINT_Y[i] = k * (POINT_X[i] - radius);
POINT_X[i] += originCoordinateX;
POINT_Y[i] += originCoordinateY;
}
}
if (renderProgress <= RING_FORWARD_END_ROTATE_DURATION_OFFSET && renderProgress > RING_FORWARD_START_ROTATE_DURATION_OFFSET) {
final float forwardRotateProgress = (renderProgress - RING_FORWARD_START_ROTATE_DURATION_OFFSET) / (RING_FORWARD_END_ROTATE_DURATION_OFFSET - RING_FORWARD_START_ROTATE_DURATION_OFFSET);
mRotation = DEGREE_360 * MATERIAL_INTERPOLATOR.getInterpolation(forwardRotateProgress);
}
if (renderProgress <= CENTER_CIRCLE_FORWARD_END_SCALE_DURATION_OFFSET && renderProgress > CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET) {
final float centerCircleScaleProgress = (renderProgress - CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET) / (CENTER_CIRCLE_FORWARD_END_SCALE_DURATION_OFFSET - CENTER_CIRCLE_FORWARD_START_SCALE_DURATION_OFFSET);
if (centerCircleScaleProgress <= 0.5f) {
mScale = 1.0f + DECELERATE_INTERPOLATOR.getInterpolation(centerCircleScaleProgress * 2.0f) * 0.2f;
} else {
mScale = 1.2f - ACCELERATE_INTERPOLATOR.getInterpolation((centerCircleScaleProgress - 0.5f) * 2.0f) * 0.2f;
}
}
if (renderProgress <= BALL_FORWARD_END_EXIT_DURATION_OFFSET && renderProgress > BALL_FORWARD_START_EXIT_DURATION_OFFSET) {
final float ballForwardExitProgress = (renderProgress - BALL_FORWARD_START_EXIT_DURATION_OFFSET) / (BALL_FORWARD_END_EXIT_DURATION_OFFSET - BALL_FORWARD_START_EXIT_DURATION_OFFSET);
mShapeChangeHeight = (ballForwardExitProgress - 0.5f) * mDanceBallRadius / 2.0f;
mShapeChangeWidth = -mShapeChangeHeight;
for (int i = 0; i < NUM_POINTS; i++) {
float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI));
float progress = (DECELERATE_INTERPOLATOR.getInterpolation(ballForwardExitProgress) / 2.0f) * 2.0f * DIRECTION[i];
POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f)));
POINT_Y[i] = k * (POINT_X[i] - radius);
POINT_X[i] += originCoordinateX;
POINT_Y[i] += originCoordinateY;
}
}
if (renderProgress <= RING_REVERSAL_END_ROTATE_DURATION_OFFSET && renderProgress > RING_REVERSAL_START_ROTATE_DURATION_OFFSET) {
float scaledTime = (renderProgress - RING_REVERSAL_START_ROTATE_DURATION_OFFSET) / (RING_REVERSAL_END_ROTATE_DURATION_OFFSET - RING_REVERSAL_START_ROTATE_DURATION_OFFSET);
mRotation = DEGREE_360 * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime) - 360;
} else if (renderProgress > RING_REVERSAL_END_ROTATE_DURATION_OFFSET) {
mRotation = 0.0f;
}
if (renderProgress <= BALL_REVERSAL_END_ENTER_DURATION_OFFSET && renderProgress > BALL_REVERSAL_START_ENTER_DURATION_OFFSET) {
final float ballReversalEnterProgress = (renderProgress - BALL_REVERSAL_START_ENTER_DURATION_OFFSET) / (BALL_REVERSAL_END_ENTER_DURATION_OFFSET - BALL_REVERSAL_START_ENTER_DURATION_OFFSET);
mShapeChangeHeight = (0.5f - ballReversalEnterProgress) * mDanceBallRadius / 2.0f;
mShapeChangeWidth = -mShapeChangeHeight;
for (int i = 0; i < NUM_POINTS; i++) {
float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI));
float progress = (0.5f - ACCELERATE_INTERPOLATOR.getInterpolation(ballReversalEnterProgress) / 2.0f) * 2.0f * DIRECTION[i];
POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f)));
POINT_Y[i] = k * (POINT_X[i] - radius);
POINT_X[i] += originCoordinateX;
POINT_Y[i] += originCoordinateY;
}
}
if (renderProgress <= CENTER_CIRCLE_REVERSAL_END_SCALE_DURATION_OFFSET && renderProgress > CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET) {
final float centerCircleScaleProgress = (renderProgress - CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET) / (CENTER_CIRCLE_REVERSAL_END_SCALE_DURATION_OFFSET - CENTER_CIRCLE_REVERSAL_START_SCALE_DURATION_OFFSET);
if (centerCircleScaleProgress <= 0.5f) {
mScale = 1.0f + DECELERATE_INTERPOLATOR.getInterpolation(centerCircleScaleProgress * 2.0f) * 0.2f;
} else {
mScale = 1.2f - ACCELERATE_INTERPOLATOR.getInterpolation((centerCircleScaleProgress - 0.5f) * 2.0f) * 0.2f;
}
}
if (renderProgress <= BALL_REVERSAL_END_EXIT_DURATION_OFFSET && renderProgress > BALL_REVERSAL_START_EXIT_DURATION_OFFSET) {
final float ballReversalExitProgress = (renderProgress - BALL_REVERSAL_START_EXIT_DURATION_OFFSET) / (BALL_REVERSAL_END_EXIT_DURATION_OFFSET - BALL_REVERSAL_START_EXIT_DURATION_OFFSET);
mShapeChangeHeight = (ballReversalExitProgress - 0.5f) * mDanceBallRadius / 2.0f;
mShapeChangeWidth = -mShapeChangeHeight;
for (int i = 0; i < NUM_POINTS; i++) {
float k = (float) Math.tan((DANCE_START_ANGLE + DANCE_INTERVAL_ANGLE * i) / 360.0f * (2.0f * Math.PI));
float progress = (0.0f - DECELERATE_INTERPOLATOR.getInterpolation(ballReversalExitProgress) / 2.0f) * 2.0f * DIRECTION[i];
POINT_X[i] = (float) (radius + progress * (radius / Math.sqrt(Math.pow(k, 2.0f) + 1.0f)));
POINT_Y[i] = k * (POINT_X[i] - radius);
POINT_X[i] += originCoordinateX;
POINT_Y[i] += originCoordinateY;
}
}
}
@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;
mRotation = 0;
}
private void setColor(int color) {
mColor = color;
mArcColor = halfAlphaColor(mColor);
}
private void setRotation(float rotation) {
mRotation = rotation;
}
private void setDanceBallRadius(float danceBallRadius) {
this.mDanceBallRadius = danceBallRadius;
}
private float getDanceBallRadius() {
return mDanceBallRadius;
}
private float getRotation() {
return mRotation;
}
private void setInsets(int width, int height) {
final float minEdge = (float) Math.min(width, height);
float insets;
if (mCenterRadius <= 0 || minEdge < 0) {
insets = (float) Math.ceil(mStrokeWidth / 2.0f);
} else {
insets = minEdge / 2.0f - mCenterRadius;
}
mStrokeInset = insets;
}
private int halfAlphaColor(int colorValue) {
int startA = (colorValue >> 24) & 0xff;
int startR = (colorValue >> 16) & 0xff;
int startG = (colorValue >> 8) & 0xff;
int startB = colorValue & 0xff;
return ((startA / 2) << 24)
| (startR << 16)
| (startG << 8)
| startB;
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public DanceLoadingRenderer build() {
DanceLoadingRenderer loadingRenderer = new DanceLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,260 @@
package app.dinus.com.loadingdrawable.render.circle.jump;
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.PathMeasure;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class GuardLoadingRenderer extends LoadingRenderer {
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
private static final long ANIMATION_DURATION = 5000;
private static final float DEFAULT_STROKE_WIDTH = 1.0f;
private static final float DEFAULT_CENTER_RADIUS = 12.5f;
private static final float DEFAULT_SKIP_BALL_RADIUS = 1.0f;
private static final float START_TRIM_INIT_ROTATION = -0.5f;
private static final float START_TRIM_MAX_ROTATION = -0.25f;
private static final float END_TRIM_INIT_ROTATION = 0.25f;
private static final float END_TRIM_MAX_ROTATION = 0.75f;
private static final float START_TRIM_DURATION_OFFSET = 0.23f;
private static final float WAVE_DURATION_OFFSET = 0.36f;
private static final float BALL_SKIP_DURATION_OFFSET = 0.74f;
private static final float BALL_SCALE_DURATION_OFFSET = 0.82f;
private static final float END_TRIM_DURATION_OFFSET = 1.0f;
private static final int DEFAULT_COLOR = Color.WHITE;
private static final int DEFAULT_BALL_COLOR = Color.RED;
private final Paint mPaint = new Paint();
private final RectF mTempBounds = new RectF();
private final RectF mCurrentBounds = new RectF();
private final float[] mCurrentPosition = new float[2];
private float mStrokeInset;
private float mSkipBallSize;
private float mScale;
private float mEndTrim;
private float mRotation;
private float mStartTrim;
private float mWaveProgress;
private float mStrokeWidth;
private float mCenterRadius;
private int mColor;
private int mBallColor;
private PathMeasure mPathMeasure;
private GuardLoadingRenderer(Context context) {
super(context);
mDuration = ANIMATION_DURATION;
init(context);
setupPaint();
}
private void init(Context context) {
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS);
mSkipBallSize = DensityUtil.dip2px(context, DEFAULT_SKIP_BALL_RADIUS);
mColor = DEFAULT_COLOR;
mBallColor = DEFAULT_BALL_COLOR;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
setInsets((int) mWidth, (int) mHeight);
}
@Override
protected void draw(Canvas canvas, Rect bounds) {
RectF arcBounds = mTempBounds;
arcBounds.set(bounds);
arcBounds.inset(mStrokeInset, mStrokeInset);
mCurrentBounds.set(arcBounds);
int saveCount = canvas.save();
//draw circle trim
float startAngle = (mStartTrim + mRotation) * 360;
float endAngle = (mEndTrim + mRotation) * 360;
float sweepAngle = endAngle - startAngle;
if (sweepAngle != 0) {
mPaint.setColor(mColor);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
}
//draw water wave
if (mWaveProgress < 1.0f) {
mPaint.setColor(Color.argb((int) (Color.alpha(mColor) * (1.0f - mWaveProgress)),
Color.red(mColor), Color.green(mColor), Color.blue(mColor)));
mPaint.setStyle(Paint.Style.STROKE);
float radius = Math.min(arcBounds.width(), arcBounds.height()) / 2.0f;
canvas.drawCircle(arcBounds.centerX(), arcBounds.centerY(), radius * (1.0f + mWaveProgress), mPaint);
}
//draw ball bounce
if (mPathMeasure != null) {
mPaint.setColor(mBallColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mCurrentPosition[0], mCurrentPosition[1], mSkipBallSize * mScale, mPaint);
}
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
if (renderProgress <= START_TRIM_DURATION_OFFSET) {
final float startTrimProgress = (renderProgress) / START_TRIM_DURATION_OFFSET;
mEndTrim = -MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress);
mRotation = START_TRIM_INIT_ROTATION + START_TRIM_MAX_ROTATION
* MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress);
}
if (renderProgress <= WAVE_DURATION_OFFSET && renderProgress > START_TRIM_DURATION_OFFSET) {
final float waveProgress = (renderProgress - START_TRIM_DURATION_OFFSET)
/ (WAVE_DURATION_OFFSET - START_TRIM_DURATION_OFFSET);
mWaveProgress = ACCELERATE_INTERPOLATOR.getInterpolation(waveProgress);
}
if (renderProgress <= BALL_SKIP_DURATION_OFFSET && renderProgress > WAVE_DURATION_OFFSET) {
if (mPathMeasure == null) {
mPathMeasure = new PathMeasure(createSkipBallPath(), false);
}
final float ballSkipProgress = (renderProgress - WAVE_DURATION_OFFSET)
/ (BALL_SKIP_DURATION_OFFSET - WAVE_DURATION_OFFSET);
mPathMeasure.getPosTan(ballSkipProgress * mPathMeasure.getLength(), mCurrentPosition, null);
mWaveProgress = 1.0f;
}
if (renderProgress <= BALL_SCALE_DURATION_OFFSET && renderProgress > BALL_SKIP_DURATION_OFFSET) {
final float ballScaleProgress =
(renderProgress - BALL_SKIP_DURATION_OFFSET)
/ (BALL_SCALE_DURATION_OFFSET - BALL_SKIP_DURATION_OFFSET);
if (ballScaleProgress < 0.5f) {
mScale = 1.0f + DECELERATE_INTERPOLATOR.getInterpolation(ballScaleProgress * 2.0f);
} else {
mScale = 2.0f - ACCELERATE_INTERPOLATOR.getInterpolation((ballScaleProgress - 0.5f) * 2.0f) * 2.0f;
}
}
if (renderProgress >= BALL_SCALE_DURATION_OFFSET) {
final float endTrimProgress =
(renderProgress - BALL_SKIP_DURATION_OFFSET)
/ (END_TRIM_DURATION_OFFSET - BALL_SKIP_DURATION_OFFSET);
mEndTrim = -1 + MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress);
mRotation = END_TRIM_INIT_ROTATION + END_TRIM_MAX_ROTATION
* MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress);
mScale = 1.0f;
mPathMeasure = null;
}
}
@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;
mEndTrim = 0.0f;
mRotation = 0.0f;
mStartTrim = 0.0f;
mWaveProgress = 1.0f;
}
private Path createSkipBallPath() {
float radius = Math.min(mCurrentBounds.width(), mCurrentBounds.height()) / 2.0f;
float radiusPow2 = (float) Math.pow(radius, 2.0f);
float originCoordinateX = mCurrentBounds.centerX();
float originCoordinateY = mCurrentBounds.centerY();
float[] coordinateX = new float[]{0.0f, 0.0f, -0.8f * radius, 0.75f * radius,
-0.45f * radius, 0.9f * radius, -0.5f * radius};
float[] sign = new float[]{1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f};
Path path = new Path();
for (int i = 0; i < coordinateX.length; i++) {
// x^2 + y^2 = radius^2 --> y = sqrt(radius^2 - x^2)
if (i == 0) {
path.moveTo(
originCoordinateX + coordinateX[i],
originCoordinateY + sign[i]
* (float) Math.sqrt(radiusPow2 - Math.pow(coordinateX[i], 2.0f)));
continue;
}
path.lineTo(
originCoordinateX + coordinateX[i],
originCoordinateY + sign[i]
* (float) Math.sqrt(radiusPow2 - Math.pow(coordinateX[i], 2.0f)));
if (i == coordinateX.length - 1) {
path.lineTo(originCoordinateX, originCoordinateY);
}
}
return path;
}
private void setInsets(int width, int height) {
final float minEdge = (float) Math.min(width, height);
float insets;
if (mCenterRadius <= 0 || minEdge < 0) {
insets = (float) Math.ceil(mStrokeWidth / 2.0f);
} else {
insets = minEdge / 2.0f - mCenterRadius;
}
mStrokeInset = insets;
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public GuardLoadingRenderer build() {
GuardLoadingRenderer loadingRenderer = new GuardLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,227 @@
package app.dinus.com.loadingdrawable.render.circle.jump;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class SwapLoadingRenderer extends LoadingRenderer {
private static final Interpolator ACCELERATE_DECELERATE_INTERPOLATOR = new AccelerateDecelerateInterpolator();
private static final long ANIMATION_DURATION = 2500;
private static final int DEFAULT_CIRCLE_COUNT = 5;
private static final float DEFAULT_BALL_RADIUS = 7.5f;
private static final float DEFAULT_WIDTH = 15.0f * 11;
private static final float DEFAULT_HEIGHT = 15.0f * 5;
private static final float DEFAULT_STROKE_WIDTH = 1.5f;
private static final int DEFAULT_COLOR = Color.WHITE;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mColor;
private int mSwapIndex;
private int mBallCount;
private float mBallSideOffsets;
private float mBallCenterY;
private float mBallRadius;
private float mBallInterval;
private float mSwapBallOffsetX;
private float mSwapBallOffsetY;
private float mASwapThreshold;
private float mStrokeWidth;
private SwapLoadingRenderer(Context context) {
super(context);
init(context);
adjustParams();
setupPaint();
}
private void init(Context context) {
mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH);
mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT);
mBallRadius = DensityUtil.dip2px(context, DEFAULT_BALL_RADIUS);
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mColor = DEFAULT_COLOR;
mDuration = ANIMATION_DURATION;
mBallCount = DEFAULT_CIRCLE_COUNT;
mBallInterval = mBallRadius;
}
private void adjustParams() {
mBallCenterY = mHeight / 2.0f;
mBallSideOffsets = (mWidth - mBallRadius * 2 * mBallCount - mBallInterval * (mBallCount - 1)) / 2.0f;
mASwapThreshold = 1.0f / mBallCount;
}
private void setupPaint() {
mPaint.setColor(mColor);
mPaint.setStrokeWidth(mStrokeWidth);
}
@Override
protected void draw(Canvas canvas) {
int saveCount = canvas.save();
for (int i = 0; i < mBallCount; i++) {
if (i == mSwapIndex) {
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mBallSideOffsets + mBallRadius * (i * 2 + 1) + i * mBallInterval + mSwapBallOffsetX
, mBallCenterY - mSwapBallOffsetY, mBallRadius, mPaint);
} else if (i == (mSwapIndex + 1) % mBallCount) {
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(mBallSideOffsets + mBallRadius * (i * 2 + 1) + i * mBallInterval - mSwapBallOffsetX
, mBallCenterY + mSwapBallOffsetY, mBallRadius - mStrokeWidth / 2, mPaint);
} else {
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(mBallSideOffsets + mBallRadius * (i * 2 + 1) + i * mBallInterval, mBallCenterY
, mBallRadius - mStrokeWidth / 2, mPaint);
}
}
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
mSwapIndex = (int) (renderProgress / mASwapThreshold);
// Swap trace : x^2 + y^2 = r ^ 2
float swapTraceProgress = ACCELERATE_DECELERATE_INTERPOLATOR.getInterpolation(
(renderProgress - mSwapIndex * mASwapThreshold) / mASwapThreshold);
float swapTraceRadius = mSwapIndex == mBallCount - 1
? (mBallRadius * 2 * (mBallCount - 1) + mBallInterval * (mBallCount - 1)) / 2
: (mBallRadius * 2 + mBallInterval) / 2;
// Calculate the X offset of the swap ball
mSwapBallOffsetX = mSwapIndex == mBallCount - 1
? -swapTraceProgress * swapTraceRadius * 2
: swapTraceProgress * swapTraceRadius * 2;
// if mSwapIndex == mBallCount - 1 then (swapTraceRadius, swapTraceRadius) as the origin of coordinates
// else (-swapTraceRadius, -swapTraceRadius) as the origin of coordinates
float xCoordinate = mSwapIndex == mBallCount - 1
? mSwapBallOffsetX + swapTraceRadius
: mSwapBallOffsetX - swapTraceRadius;
// Calculate the Y offset of the swap ball
mSwapBallOffsetY = (float) (mSwapIndex % 2 == 0 && mSwapIndex != mBallCount - 1
? Math.sqrt(Math.pow(swapTraceRadius, 2.0f) - Math.pow(xCoordinate, 2.0f))
: -Math.sqrt(Math.pow(swapTraceRadius, 2.0f) - Math.pow(xCoordinate, 2.0f)));
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
}
private void apply(Builder builder) {
this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth;
this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight;
this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth;
this.mBallRadius = builder.mBallRadius > 0 ? builder.mBallRadius : this.mBallRadius;
this.mBallInterval = builder.mBallInterval > 0 ? builder.mBallInterval : this.mBallInterval;
this.mBallCount = builder.mBallCount > 0 ? builder.mBallCount : this.mBallCount;
this.mColor = builder.mColor != 0 ? builder.mColor : this.mColor;
this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration;
adjustParams();
setupPaint();
}
public static class Builder {
private Context mContext;
private int mWidth;
private int mHeight;
private int mStrokeWidth;
private int mBallCount;
private int mBallRadius;
private int mBallInterval;
private int mDuration;
private int mColor;
public Builder(Context mContext) {
this.mContext = mContext;
}
public Builder setWidth(int width) {
this.mWidth = width;
return this;
}
public Builder setHeight(int height) {
this.mHeight = height;
return this;
}
public Builder setStrokeWidth(int strokeWidth) {
this.mStrokeWidth = strokeWidth;
return this;
}
public Builder setBallRadius(int ballRadius) {
this.mBallRadius = ballRadius;
return this;
}
public Builder setBallInterval(int ballInterval) {
this.mBallInterval = ballInterval;
return this;
}
public Builder setBallCount(int ballCount) {
this.mBallCount = ballCount;
return this;
}
public Builder setColor(int color) {
this.mColor = color;
return this;
}
public Builder setDuration(int duration) {
this.mDuration = duration;
return this;
}
public SwapLoadingRenderer build() {
SwapLoadingRenderer loadingRenderer = new SwapLoadingRenderer(mContext);
loadingRenderer.apply(this);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,288 @@
package app.dinus.com.loadingdrawable.render.circle.rotate;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import androidx.annotation.IntRange;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class GearLoadingRenderer extends LoadingRenderer {
private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
private static final int GEAR_COUNT = 4;
private static final int NUM_POINTS = 3;
private static final int MAX_ALPHA = 255;
private static final int DEGREE_360 = 360;
private static final int DEFAULT_GEAR_SWIPE_DEGREES = 60;
private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360;
private static final float START_SCALE_DURATION_OFFSET = 0.3f;
private static final float START_TRIM_DURATION_OFFSET = 0.5f;
private static final float END_TRIM_DURATION_OFFSET = 0.7f;
private static final float END_SCALE_DURATION_OFFSET = 1.0f;
private static final float DEFAULT_CENTER_RADIUS = 12.5f;
private static final float DEFAULT_STROKE_WIDTH = 2.5f;
private static final int DEFAULT_COLOR = Color.WHITE;
private final Paint mPaint = new Paint();
private final RectF mTempBounds = new RectF();
private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animator) {
super.onAnimationRepeat(animator);
storeOriginals();
mStartDegrees = mEndDegrees;
mRotationCount = (mRotationCount + 1) % NUM_POINTS;
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mRotationCount = 0;
}
};
private int mColor;
private int mGearCount;
private int mGearSwipeDegrees;
private float mStrokeInset;
private float mRotationCount;
private float mGroupRotation;
private float mScale;
private float mEndDegrees;
private float mStartDegrees;
private float mSwipeDegrees;
private float mOriginEndDegrees;
private float mOriginStartDegrees;
private float mStrokeWidth;
private float mCenterRadius;
private GearLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
addRenderListener(mAnimatorListener);
}
private void init(Context context) {
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS);
mColor = DEFAULT_COLOR;
mGearCount = GEAR_COUNT;
mGearSwipeDegrees = DEFAULT_GEAR_SWIPE_DEGREES;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
initStrokeInset(mWidth, mHeight);
}
@Override
protected void draw(Canvas canvas) {
int saveCount = canvas.save();
mTempBounds.set(mBounds);
mTempBounds.inset(mStrokeInset, mStrokeInset);
mTempBounds.inset(mTempBounds.width() * (1.0f - mScale) / 2.0f, mTempBounds.width() * (1.0f - mScale) / 2.0f);
canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY());
mPaint.setColor(mColor);
mPaint.setAlpha((int) (MAX_ALPHA * mScale));
mPaint.setStrokeWidth(mStrokeWidth * mScale);
if (mSwipeDegrees != 0) {
for (int i = 0; i < mGearCount; i++) {
canvas.drawArc(mTempBounds, mStartDegrees + DEGREE_360 / mGearCount * i, mSwipeDegrees, false, mPaint);
}
}
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
// Scaling up the start size only occurs in the first 20% of a single ring animation
if (renderProgress <= START_SCALE_DURATION_OFFSET) {
float startScaleProgress = (renderProgress) / START_SCALE_DURATION_OFFSET;
mScale = DECELERATE_INTERPOLATOR.getInterpolation(startScaleProgress);
}
// Moving the start trim only occurs between 20% to 50% of a single ring animation
if (renderProgress <= START_TRIM_DURATION_OFFSET && renderProgress > START_SCALE_DURATION_OFFSET) {
float startTrimProgress = (renderProgress - START_SCALE_DURATION_OFFSET) / (START_TRIM_DURATION_OFFSET - START_SCALE_DURATION_OFFSET);
mStartDegrees = mOriginStartDegrees + mGearSwipeDegrees * startTrimProgress;
}
// Moving the end trim starts between 50% to 80% of a single ring animation
if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > START_TRIM_DURATION_OFFSET) {
float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET);
mEndDegrees = mOriginEndDegrees + mGearSwipeDegrees * endTrimProgress;
}
// Scaling down the end size starts after 80% of a single ring animation
if (renderProgress > END_TRIM_DURATION_OFFSET) {
float endScaleProgress = (renderProgress - END_TRIM_DURATION_OFFSET) / (END_SCALE_DURATION_OFFSET - END_TRIM_DURATION_OFFSET);
mScale = 1.0f - ACCELERATE_INTERPOLATOR.getInterpolation(endScaleProgress);
}
if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > START_SCALE_DURATION_OFFSET) {
float rotateProgress = (renderProgress - START_SCALE_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_SCALE_DURATION_OFFSET);
mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * rotateProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS));
}
if (Math.abs(mEndDegrees - mStartDegrees) > 0) {
mSwipeDegrees = mEndDegrees - mStartDegrees;
}
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
resetOriginals();
}
private void initStrokeInset(float width, float height) {
float minSize = Math.min(width, height);
float strokeInset = minSize / 2.0f - mCenterRadius;
float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f);
mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset;
}
private void storeOriginals() {
mOriginEndDegrees = mEndDegrees;
mOriginStartDegrees = mEndDegrees;
}
private void resetOriginals() {
mOriginEndDegrees = 0;
mOriginStartDegrees = 0;
mEndDegrees = 0;
mStartDegrees = 0;
mSwipeDegrees = 1;
}
private void apply(Builder builder) {
this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth;
this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight;
this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth;
this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius;
this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration;
this.mColor = builder.mColor != 0 ? builder.mColor : this.mColor;
this.mGearCount = builder.mGearCount > 0 ? builder.mGearCount : this.mGearCount;
this.mGearSwipeDegrees = builder.mGearSwipeDegrees > 0 ? builder.mGearSwipeDegrees : this.mGearSwipeDegrees;
setupPaint();
initStrokeInset(this.mWidth, this.mHeight);
}
public static class Builder {
private Context mContext;
private int mWidth;
private int mHeight;
private int mStrokeWidth;
private int mCenterRadius;
private int mDuration;
private int mColor;
private int mGearCount;
private int mGearSwipeDegrees;
public Builder(Context mContext) {
this.mContext = mContext;
}
public Builder setWidth(int width) {
this.mWidth = width;
return this;
}
public Builder setHeight(int height) {
this.mHeight = height;
return this;
}
public Builder setStrokeWidth(int strokeWidth) {
this.mStrokeWidth = strokeWidth;
return this;
}
public Builder setCenterRadius(int centerRadius) {
this.mCenterRadius = centerRadius;
return this;
}
public Builder setDuration(int duration) {
this.mDuration = duration;
return this;
}
public Builder setColor(int color) {
this.mColor = color;
return this;
}
public Builder setGearCount(int gearCount) {
this.mGearCount = gearCount;
return this;
}
public Builder setGearSwipeDegrees(@IntRange(from = 0, to = 360) int gearSwipeDegrees) {
this.mGearSwipeDegrees = gearSwipeDegrees;
return this;
}
public GearLoadingRenderer build() {
GearLoadingRenderer loadingRenderer = new GearLoadingRenderer(mContext);
loadingRenderer.apply(this);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,300 @@
package app.dinus.com.loadingdrawable.render.circle.rotate;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Size;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class LevelLoadingRenderer extends LoadingRenderer {
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
private static final int NUM_POINTS = 5;
private static final int DEGREE_360 = 360;
private static final float MAX_SWIPE_DEGREES = 0.8f * DEGREE_360;
private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360;
private static final float[] LEVEL_SWEEP_ANGLE_OFFSETS = new float[]{1.0f, 7.0f / 8.0f, 5.0f / 8.0f};
private static final float START_TRIM_DURATION_OFFSET = 0.5f;
private static final float END_TRIM_DURATION_OFFSET = 1.0f;
private static final float DEFAULT_CENTER_RADIUS = 12.5f;
private static final float DEFAULT_STROKE_WIDTH = 2.5f;
private static final int[] DEFAULT_LEVEL_COLORS = new int[]{Color.parseColor("#55ffffff"),
Color.parseColor("#b1ffffff"), Color.parseColor("#ffffffff")};
private final Paint mPaint = new Paint();
private final RectF mTempBounds = new RectF();
private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animator) {
super.onAnimationRepeat(animator);
storeOriginals();
mStartDegrees = mEndDegrees;
mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mRotationCount = 0;
}
};
@Size(3)
private int[] mLevelColors;
@Size(3)
private float[] mLevelSwipeDegrees;
private float mStrokeInset;
private float mRotationCount;
private float mGroupRotation;
private float mEndDegrees;
private float mStartDegrees;
private float mOriginEndDegrees;
private float mOriginStartDegrees;
private float mStrokeWidth;
private float mCenterRadius;
private LevelLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
addRenderListener(mAnimatorListener);
}
private void init(Context context) {
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS);
mLevelSwipeDegrees = new float[3];
mLevelColors = DEFAULT_LEVEL_COLORS;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
initStrokeInset((int) mWidth, (int) mHeight);
}
@Override
protected void draw(Canvas canvas) {
int saveCount = canvas.save();
mTempBounds.set(mBounds);
mTempBounds.inset(mStrokeInset, mStrokeInset);
canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY());
for (int i = 0; i < 3; i++) {
if (mLevelSwipeDegrees[i] != 0) {
mPaint.setColor(mLevelColors[i]);
canvas.drawArc(mTempBounds, mEndDegrees, mLevelSwipeDegrees[i], false, mPaint);
}
}
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
// Moving the start trim only occurs in the first 50% of a single ring animation
if (renderProgress <= START_TRIM_DURATION_OFFSET) {
float startTrimProgress = (renderProgress) / START_TRIM_DURATION_OFFSET;
mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress);
float mSwipeDegrees = mEndDegrees - mStartDegrees;
float levelSwipeDegreesProgress = Math.abs(mSwipeDegrees) / MAX_SWIPE_DEGREES;
float level1Increment = DECELERATE_INTERPOLATOR.getInterpolation(levelSwipeDegreesProgress) - LINEAR_INTERPOLATOR.getInterpolation(levelSwipeDegreesProgress);
float level3Increment = ACCELERATE_INTERPOLATOR.getInterpolation(levelSwipeDegreesProgress) - LINEAR_INTERPOLATOR.getInterpolation(levelSwipeDegreesProgress);
mLevelSwipeDegrees[0] = -mSwipeDegrees * LEVEL_SWEEP_ANGLE_OFFSETS[0] * (1.0f + level1Increment);
mLevelSwipeDegrees[1] = -mSwipeDegrees * LEVEL_SWEEP_ANGLE_OFFSETS[1] * 1.0f;
mLevelSwipeDegrees[2] = -mSwipeDegrees * LEVEL_SWEEP_ANGLE_OFFSETS[2] * (1.0f + level3Increment);
}
// Moving the end trim starts after 50% of a single ring animation
if (renderProgress > START_TRIM_DURATION_OFFSET) {
float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET);
mEndDegrees = mOriginEndDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress);
float mSwipeDegrees = mEndDegrees - mStartDegrees;
float levelSwipeDegreesProgress = Math.abs(mSwipeDegrees) / MAX_SWIPE_DEGREES;
if (levelSwipeDegreesProgress > LEVEL_SWEEP_ANGLE_OFFSETS[1]) {
mLevelSwipeDegrees[0] = -mSwipeDegrees;
mLevelSwipeDegrees[1] = MAX_SWIPE_DEGREES * LEVEL_SWEEP_ANGLE_OFFSETS[1];
mLevelSwipeDegrees[2] = MAX_SWIPE_DEGREES * LEVEL_SWEEP_ANGLE_OFFSETS[2];
} else if (levelSwipeDegreesProgress > LEVEL_SWEEP_ANGLE_OFFSETS[2]) {
mLevelSwipeDegrees[0] = 0;
mLevelSwipeDegrees[1] = -mSwipeDegrees;
mLevelSwipeDegrees[2] = MAX_SWIPE_DEGREES * LEVEL_SWEEP_ANGLE_OFFSETS[2];
} else {
mLevelSwipeDegrees[0] = 0;
mLevelSwipeDegrees[1] = 0;
mLevelSwipeDegrees[2] = -mSwipeDegrees;
}
}
mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS));
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
resetOriginals();
}
private void initStrokeInset(float width, float height) {
float minSize = Math.min(width, height);
float strokeInset = minSize / 2.0f - mCenterRadius;
float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f);
mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset;
}
private void storeOriginals() {
mOriginEndDegrees = mEndDegrees;
mOriginStartDegrees = mEndDegrees;
}
private void resetOriginals() {
mOriginEndDegrees = 0;
mOriginStartDegrees = 0;
mEndDegrees = 0;
mStartDegrees = 0;
mLevelSwipeDegrees[0] = 0;
mLevelSwipeDegrees[1] = 0;
mLevelSwipeDegrees[2] = 0;
}
private void apply(Builder builder) {
this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth;
this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight;
this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth;
this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius;
this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration;
this.mLevelColors = builder.mLevelColors != null ? builder.mLevelColors : this.mLevelColors;
setupPaint();
initStrokeInset(this.mWidth, this.mHeight);
}
public static class Builder {
private Context mContext;
private int mWidth;
private int mHeight;
private int mStrokeWidth;
private int mCenterRadius;
private int mDuration;
@Size(3)
private int[] mLevelColors;
public Builder(Context mContext) {
this.mContext = mContext;
}
public Builder setWidth(int width) {
this.mWidth = width;
return this;
}
public Builder setHeight(int height) {
this.mHeight = height;
return this;
}
public Builder setStrokeWidth(int strokeWidth) {
this.mStrokeWidth = strokeWidth;
return this;
}
public Builder setCenterRadius(int centerRadius) {
this.mCenterRadius = centerRadius;
return this;
}
public Builder setDuration(int duration) {
this.mDuration = duration;
return this;
}
public Builder setLevelColors(@Size(3) int[] colors) {
this.mLevelColors = colors;
return this;
}
public Builder setLevelColor(int color) {
return setLevelColors(new int[]{oneThirdAlphaColor(color), twoThirdAlphaColor(color), color});
}
public LevelLoadingRenderer build() {
LevelLoadingRenderer loadingRenderer = new LevelLoadingRenderer(mContext);
loadingRenderer.apply(this);
return loadingRenderer;
}
private int oneThirdAlphaColor(int colorValue) {
int startA = (colorValue >> 24) & 0xff;
int startR = (colorValue >> 16) & 0xff;
int startG = (colorValue >> 8) & 0xff;
int startB = colorValue & 0xff;
return (startA / 3 << 24) | (startR << 16) | (startG << 8) | startB;
}
private int twoThirdAlphaColor(int colorValue) {
int startA = (colorValue >> 24) & 0xff;
int startR = (colorValue >> 16) & 0xff;
int startG = (colorValue >> 8) & 0xff;
int startB = colorValue & 0xff;
return (startA * 2 / 3 << 24) | (startR << 16) | (startG << 8) | startB;
}
}
}

View File

@@ -0,0 +1,292 @@
package app.dinus.com.loadingdrawable.render.circle.rotate;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.animation.Interpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class MaterialLoadingRenderer extends LoadingRenderer {
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
private static final int DEGREE_360 = 360;
private static final int NUM_POINTS = 5;
private static final float MAX_SWIPE_DEGREES = 0.8f * DEGREE_360;
private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360;
private static final float COLOR_START_DELAY_OFFSET = 0.8f;
private static final float END_TRIM_DURATION_OFFSET = 1.0f;
private static final float START_TRIM_DURATION_OFFSET = 0.5f;
private static final float DEFAULT_CENTER_RADIUS = 12.5f;
private static final float DEFAULT_STROKE_WIDTH = 2.5f;
private static final int[] DEFAULT_COLORS = new int[]{
Color.RED, Color.GREEN, Color.BLUE
};
private final Paint mPaint = new Paint();
private final RectF mTempBounds = new RectF();
private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animator) {
super.onAnimationRepeat(animator);
storeOriginals();
goToNextColor();
mStartDegrees = mEndDegrees;
mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mRotationCount = 0;
}
};
private int[] mColors;
private int mColorIndex;
private int mCurrentColor;
private float mStrokeInset;
private float mRotationCount;
private float mGroupRotation;
private float mEndDegrees;
private float mStartDegrees;
private float mSwipeDegrees;
private float mOriginEndDegrees;
private float mOriginStartDegrees;
private float mStrokeWidth;
private float mCenterRadius;
private MaterialLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
addRenderListener(mAnimatorListener);
}
private void init(Context context) {
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS);
mColors = DEFAULT_COLORS;
setColorIndex(0);
initStrokeInset(mWidth, 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) {
int saveCount = canvas.save();
mTempBounds.set(mBounds);
mTempBounds.inset(mStrokeInset, mStrokeInset);
canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY());
if (mSwipeDegrees != 0) {
mPaint.setColor(mCurrentColor);
canvas.drawArc(mTempBounds, mStartDegrees, mSwipeDegrees, false, mPaint);
}
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
updateRingColor(renderProgress);
// Moving the start trim only occurs in the first 50% of a single ring animation
if (renderProgress <= START_TRIM_DURATION_OFFSET) {
float startTrimProgress = renderProgress / START_TRIM_DURATION_OFFSET;
mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES
* MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress);
}
// Moving the end trim starts after 50% of a single ring animation completes
if (renderProgress > START_TRIM_DURATION_OFFSET) {
float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET)
/ (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET);
mEndDegrees = mOriginEndDegrees + MAX_SWIPE_DEGREES
* MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress);
}
if (Math.abs(mEndDegrees - mStartDegrees) > 0) {
mSwipeDegrees = mEndDegrees - mStartDegrees;
}
mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress)
+ (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS));
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
resetOriginals();
}
private void setColorIndex(int index) {
mColorIndex = index;
mCurrentColor = mColors[mColorIndex];
}
private int getNextColor() {
return mColors[getNextColorIndex()];
}
private int getNextColorIndex() {
return (mColorIndex + 1) % (mColors.length);
}
private void goToNextColor() {
setColorIndex(getNextColorIndex());
}
private void initStrokeInset(float width, float height) {
float minSize = Math.min(width, height);
float strokeInset = minSize / 2.0f - mCenterRadius;
float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f);
mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset;
}
private void storeOriginals() {
mOriginEndDegrees = mEndDegrees;
mOriginStartDegrees = mEndDegrees;
}
private void resetOriginals() {
mOriginEndDegrees = 0;
mOriginStartDegrees = 0;
mEndDegrees = 0;
mStartDegrees = 0;
}
private int getStartingColor() {
return mColors[mColorIndex];
}
private void updateRingColor(float interpolatedTime) {
if (interpolatedTime > COLOR_START_DELAY_OFFSET) {
mCurrentColor = evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET)
/ (1.0f - COLOR_START_DELAY_OFFSET), getStartingColor(), getNextColor());
}
}
private int evaluateColorChange(float fraction, int startValue, int endValue) {
int startA = (startValue >> 24) & 0xff;
int startR = (startValue >> 16) & 0xff;
int startG = (startValue >> 8) & 0xff;
int startB = startValue & 0xff;
int endA = (endValue >> 24) & 0xff;
int endR = (endValue >> 16) & 0xff;
int endG = (endValue >> 8) & 0xff;
int endB = endValue & 0xff;
return ((startA + (int) (fraction * (endA - startA))) << 24)
| ((startR + (int) (fraction * (endR - startR))) << 16)
| ((startG + (int) (fraction * (endG - startG))) << 8)
| ((startB + (int) (fraction * (endB - startB))));
}
private void apply(Builder builder) {
this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth;
this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight;
this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth;
this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius;
this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration;
this.mColors = builder.mColors != null && builder.mColors.length > 0 ? builder.mColors : this.mColors;
setColorIndex(0);
setupPaint();
initStrokeInset(this.mWidth, this.mHeight);
}
public static class Builder {
private Context mContext;
private int mWidth;
private int mHeight;
private int mStrokeWidth;
private int mCenterRadius;
private int mDuration;
private int[] mColors;
public Builder(Context mContext) {
this.mContext = mContext;
}
public Builder setWidth(int width) {
this.mWidth = width;
return this;
}
public Builder setHeight(int height) {
this.mHeight = height;
return this;
}
public Builder setStrokeWidth(int strokeWidth) {
this.mStrokeWidth = strokeWidth;
return this;
}
public Builder setCenterRadius(int centerRadius) {
this.mCenterRadius = centerRadius;
return this;
}
public Builder setDuration(int duration) {
this.mDuration = duration;
return this;
}
public Builder setColors(int[] colors) {
this.mColors = colors;
return this;
}
public MaterialLoadingRenderer build() {
MaterialLoadingRenderer loadingRenderer = new MaterialLoadingRenderer(mContext);
loadingRenderer.apply(this);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,261 @@
package app.dinus.com.loadingdrawable.render.circle.rotate;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.animation.Interpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class WhorlLoadingRenderer extends LoadingRenderer {
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
private static final int DEGREE_180 = 180;
private static final int DEGREE_360 = 360;
private static final int NUM_POINTS = 5;
private static final float MAX_SWIPE_DEGREES = 0.6f * DEGREE_360;
private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360;
private static final float START_TRIM_DURATION_OFFSET = 0.5f;
private static final float END_TRIM_DURATION_OFFSET = 1.0f;
private static final float DEFAULT_CENTER_RADIUS = 12.5f;
private static final float DEFAULT_STROKE_WIDTH = 2.5f;
private static final int[] DEFAULT_COLORS = new int[]{
Color.RED, Color.GREEN, Color.BLUE
};
private final Paint mPaint = new Paint();
private final RectF mTempBounds = new RectF();
private final RectF mTempArcBounds = new RectF();
private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animator) {
super.onAnimationRepeat(animator);
storeOriginals();
mStartDegrees = mEndDegrees;
mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mRotationCount = 0;
}
};
private int[] mColors;
private float mStrokeInset;
private float mRotationCount;
private float mGroupRotation;
private float mEndDegrees;
private float mStartDegrees;
private float mSwipeDegrees;
private float mOriginEndDegrees;
private float mOriginStartDegrees;
private float mStrokeWidth;
private float mCenterRadius;
private WhorlLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
addRenderListener(mAnimatorListener);
}
private void init(Context context) {
mColors = DEFAULT_COLORS;
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS);
initStrokeInset(mWidth, 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) {
int saveCount = canvas.save();
mTempBounds.set(mBounds);
mTempBounds.inset(mStrokeInset, mStrokeInset);
canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY());
if (mSwipeDegrees != 0) {
for (int i = 0; i < mColors.length; i++) {
mPaint.setStrokeWidth(mStrokeWidth / (i + 1));
mPaint.setColor(mColors[i]);
canvas.drawArc(createArcBounds(mTempBounds, i), mStartDegrees + DEGREE_180 * (i % 2),
mSwipeDegrees, false, mPaint);
}
}
canvas.restoreToCount(saveCount);
}
private RectF createArcBounds(RectF sourceArcBounds, int index) {
int intervalWidth = 0;
for (int i = 0; i < index; i++) {
intervalWidth += mStrokeWidth / (i + 1.0f) * 1.5f;
}
int arcBoundsLeft = (int) (sourceArcBounds.left + intervalWidth);
int arcBoundsTop = (int) (sourceArcBounds.top + intervalWidth);
int arcBoundsRight = (int) (sourceArcBounds.right - intervalWidth);
int arcBoundsBottom = (int) (sourceArcBounds.bottom - intervalWidth);
mTempArcBounds.set(arcBoundsLeft, arcBoundsTop, arcBoundsRight, arcBoundsBottom);
return mTempArcBounds;
}
@Override
protected void computeRender(float renderProgress) {
// Moving the start trim only occurs in the first 50% of a single ring animation
if (renderProgress <= START_TRIM_DURATION_OFFSET) {
float startTrimProgress = (renderProgress) / (1.0f - START_TRIM_DURATION_OFFSET);
mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress);
}
// Moving the end trim starts after 50% of a single ring animation
if (renderProgress > START_TRIM_DURATION_OFFSET) {
float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET);
mEndDegrees = mOriginEndDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress);
}
if (Math.abs(mEndDegrees - mStartDegrees) > 0) {
mSwipeDegrees = mEndDegrees - mStartDegrees;
}
mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS));
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
resetOriginals();
}
private void initStrokeInset(float width, float height) {
float minSize = Math.min(width, height);
float strokeInset = minSize / 2.0f - mCenterRadius;
float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f);
mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset;
}
private void storeOriginals() {
mOriginEndDegrees = mEndDegrees;
mOriginStartDegrees = mEndDegrees;
}
private void resetOriginals() {
mOriginEndDegrees = 0;
mOriginStartDegrees = 0;
mEndDegrees = 0;
mStartDegrees = 0;
mSwipeDegrees = 0;
}
private void apply(Builder builder) {
this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth;
this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight;
this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth;
this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius;
this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration;
this.mColors = builder.mColors != null && builder.mColors.length > 0 ? builder.mColors : this.mColors;
setupPaint();
initStrokeInset(this.mWidth, this.mHeight);
}
public static class Builder {
private Context mContext;
private int mWidth;
private int mHeight;
private int mStrokeWidth;
private int mCenterRadius;
private int mDuration;
private int[] mColors;
public Builder(Context mContext) {
this.mContext = mContext;
}
public Builder setWidth(int width) {
this.mWidth = width;
return this;
}
public Builder setHeight(int height) {
this.mHeight = height;
return this;
}
public Builder setStrokeWidth(int strokeWidth) {
this.mStrokeWidth = strokeWidth;
return this;
}
public Builder setCenterRadius(int centerRadius) {
this.mCenterRadius = centerRadius;
return this;
}
public Builder setDuration(int duration) {
this.mDuration = duration;
return this;
}
public Builder setColors(int[] colors) {
this.mColors = colors;
return this;
}
public WhorlLoadingRenderer build() {
WhorlLoadingRenderer loadingRenderer = new WhorlLoadingRenderer(mContext);
loadingRenderer.apply(this);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,311 @@
package app.dinus.com.loadingdrawable.render.goods;
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.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class BalloonLoadingRenderer extends LoadingRenderer {
private static final String PERCENT_SIGN = "%";
private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
private static final float START_INHALE_DURATION_OFFSET = 0.4f;
private static final float DEFAULT_WIDTH = 200.0f;
private static final float DEFAULT_HEIGHT = 150.0f;
private static final float DEFAULT_STROKE_WIDTH = 2.0f;
private static final float DEFAULT_GAS_TUBE_WIDTH = 48;
private static final float DEFAULT_GAS_TUBE_HEIGHT = 20;
private static final float DEFAULT_CANNULA_WIDTH = 13;
private static final float DEFAULT_CANNULA_HEIGHT = 37;
private static final float DEFAULT_CANNULA_OFFSET_Y = 3;
private static final float DEFAULT_CANNULA_MAX_OFFSET_Y = 15;
private static final float DEFAULT_PIPE_BODY_WIDTH = 16;
private static final float DEFAULT_PIPE_BODY_HEIGHT = 36;
private static final float DEFAULT_BALLOON_WIDTH = 38;
private static final float DEFAULT_BALLOON_HEIGHT = 48;
private static final float DEFAULT_RECT_CORNER_RADIUS = 2;
private static final int DEFAULT_BALLOON_COLOR = Color.parseColor("#ffF3C211");
private static final int DEFAULT_GAS_TUBE_COLOR = Color.parseColor("#ff174469");
private static final int DEFAULT_PIPE_BODY_COLOR = Color.parseColor("#aa2369B1");
private static final int DEFAULT_CANNULA_COLOR = Color.parseColor("#ff174469");
private static final float DEFAULT_TEXT_SIZE = 7.0f;
private static final long ANIMATION_DURATION = 3333;
private final Paint mPaint = new Paint();
private final RectF mCurrentBounds = new RectF();
private final RectF mGasTubeBounds = new RectF();
private final RectF mPipeBodyBounds = new RectF();
private final RectF mCannulaBounds = new RectF();
private final RectF mBalloonBounds = new RectF();
private final Rect mProgressBounds = new Rect();
private float mTextSize;
private float mProgress;
private String mProgressText;
private float mGasTubeWidth;
private float mGasTubeHeight;
private float mCannulaWidth;
private float mCannulaHeight;
private float mCannulaMaxOffsetY;
private float mCannulaOffsetY;
private float mPipeBodyWidth;
private float mPipeBodyHeight;
private float mBalloonWidth;
private float mBalloonHeight;
private float mRectCornerRadius;
private float mStrokeWidth;
private int mBalloonColor;
private int mGasTubeColor;
private int mCannulaColor;
private int mPipeBodyColor;
private BalloonLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
}
private void init(Context context) {
mTextSize = DensityUtil.dip2px(context, DEFAULT_TEXT_SIZE);
mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH);
mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT);
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mGasTubeWidth = DensityUtil.dip2px(context, DEFAULT_GAS_TUBE_WIDTH);
mGasTubeHeight = DensityUtil.dip2px(context, DEFAULT_GAS_TUBE_HEIGHT);
mCannulaWidth = DensityUtil.dip2px(context, DEFAULT_CANNULA_WIDTH);
mCannulaHeight = DensityUtil.dip2px(context, DEFAULT_CANNULA_HEIGHT);
mCannulaOffsetY = DensityUtil.dip2px(context, DEFAULT_CANNULA_OFFSET_Y);
mCannulaMaxOffsetY = DensityUtil.dip2px(context, DEFAULT_CANNULA_MAX_OFFSET_Y);
mPipeBodyWidth = DensityUtil.dip2px(context, DEFAULT_PIPE_BODY_WIDTH);
mPipeBodyHeight = DensityUtil.dip2px(context, DEFAULT_PIPE_BODY_HEIGHT);
mBalloonWidth = DensityUtil.dip2px(context, DEFAULT_BALLOON_WIDTH);
mBalloonHeight = DensityUtil.dip2px(context, DEFAULT_BALLOON_HEIGHT);
mRectCornerRadius = DensityUtil.dip2px(context, DEFAULT_RECT_CORNER_RADIUS);
mBalloonColor = DEFAULT_BALLOON_COLOR;
mGasTubeColor = DEFAULT_GAS_TUBE_COLOR;
mCannulaColor = DEFAULT_CANNULA_COLOR;
mPipeBodyColor = DEFAULT_PIPE_BODY_COLOR;
mProgressText = 10 + PERCENT_SIGN;
mDuration = ANIMATION_DURATION;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mStrokeWidth);
}
@Override
protected void draw(Canvas canvas, Rect bounds) {
int saveCount = canvas.save();
RectF arcBounds = mCurrentBounds;
arcBounds.set(bounds);
//draw draw gas tube
mPaint.setColor(mGasTubeColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawPath(createGasTubePath(mGasTubeBounds), mPaint);
//draw balloon
mPaint.setColor(mBalloonColor);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawPath(createBalloonPath(mBalloonBounds, mProgress), mPaint);
//draw progress
mPaint.setColor(mGasTubeColor);
mPaint.setTextSize(mTextSize);
mPaint.setStrokeWidth(mStrokeWidth / 5.0f);
canvas.drawText(mProgressText, arcBounds.centerX() - mProgressBounds.width() / 2.0f,
mGasTubeBounds.centerY() + mProgressBounds.height() / 2.0f, mPaint);
//draw cannula
mPaint.setColor(mCannulaColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawPath(createCannulaHeadPath(mCannulaBounds), mPaint);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawPath(createCannulaBottomPath(mCannulaBounds), mPaint);
//draw pipe body
mPaint.setColor(mPipeBodyColor);
mPaint.setStyle(Paint.Style.FILL);
canvas.drawRoundRect(mPipeBodyBounds, mRectCornerRadius, mRectCornerRadius, mPaint);
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
RectF arcBounds = mCurrentBounds;
//compute gas tube bounds
mGasTubeBounds.set(arcBounds.centerX() - mGasTubeWidth / 2.0f, arcBounds.centerY(),
arcBounds.centerX() + mGasTubeWidth / 2.0f, arcBounds.centerY() + mGasTubeHeight);
//compute pipe body bounds
mPipeBodyBounds.set(arcBounds.centerX() + mGasTubeWidth / 2.0f - mPipeBodyWidth / 2.0f, arcBounds.centerY() - mPipeBodyHeight,
arcBounds.centerX() + mGasTubeWidth / 2.0f + mPipeBodyWidth / 2.0f, arcBounds.centerY());
//compute cannula bounds
mCannulaBounds.set(arcBounds.centerX() + mGasTubeWidth / 2.0f - mCannulaWidth / 2.0f, arcBounds.centerY() - mCannulaHeight - mCannulaOffsetY,
arcBounds.centerX() + mGasTubeWidth / 2.0f + mCannulaWidth / 2.0f, arcBounds.centerY() - mCannulaOffsetY);
//compute balloon bounds
float insetX = mBalloonWidth * 0.333f * (1 - mProgress);
float insetY = mBalloonHeight * 0.667f * (1 - mProgress);
mBalloonBounds.set(arcBounds.centerX() - mGasTubeWidth / 2.0f - mBalloonWidth / 2.0f + insetX, arcBounds.centerY() - mBalloonHeight + insetY,
arcBounds.centerX() - mGasTubeWidth / 2.0f + mBalloonWidth / 2.0f - insetX, arcBounds.centerY());
if (renderProgress <= START_INHALE_DURATION_OFFSET) {
mCannulaBounds.offset(0, -mCannulaMaxOffsetY * renderProgress / START_INHALE_DURATION_OFFSET);
mProgress = 0.0f;
mProgressText = 10 + PERCENT_SIGN;
mPaint.setTextSize(mTextSize);
mPaint.getTextBounds(mProgressText, 0, mProgressText.length(), mProgressBounds);
} else {
float exhaleProgress = ACCELERATE_INTERPOLATOR.getInterpolation(1.0f - (renderProgress - START_INHALE_DURATION_OFFSET) / (1.0f - START_INHALE_DURATION_OFFSET));
mCannulaBounds.offset(0, -mCannulaMaxOffsetY * exhaleProgress);
mProgress = 1.0f - exhaleProgress;
mProgressText = adjustProgress((int) (exhaleProgress * 100.0f)) + PERCENT_SIGN;
mPaint.setTextSize(mTextSize);
mPaint.getTextBounds(mProgressText, 0, mProgressText.length(), mProgressBounds);
}
}
private int adjustProgress(int progress) {
progress = progress / 10 * 10;
progress = 100 - progress + 10;
if (progress > 100) {
progress = 100;
}
return progress;
}
private Path createGasTubePath(RectF gasTubeRect) {
Path path = new Path();
path.moveTo(gasTubeRect.left, gasTubeRect.top);
path.lineTo(gasTubeRect.left, gasTubeRect.bottom);
path.lineTo(gasTubeRect.right, gasTubeRect.bottom);
path.lineTo(gasTubeRect.right, gasTubeRect.top);
return path;
}
private Path createCannulaHeadPath(RectF cannulaRect) {
Path path = new Path();
path.moveTo(cannulaRect.left, cannulaRect.top);
path.lineTo(cannulaRect.right, cannulaRect.top);
path.moveTo(cannulaRect.centerX(), cannulaRect.top);
path.lineTo(cannulaRect.centerX(), cannulaRect.bottom - 0.833f * cannulaRect.width());
return path;
}
private Path createCannulaBottomPath(RectF cannulaRect) {
RectF cannulaHeadRect = new RectF(cannulaRect.left, cannulaRect.bottom - 0.833f * cannulaRect.width(),
cannulaRect.right, cannulaRect.bottom);
Path path = new Path();
path.addRoundRect(cannulaHeadRect, mRectCornerRadius, mRectCornerRadius, Path.Direction.CCW);
return path;
}
/**
* Coordinates are approximate, you have better cooperate with the designer's design draft
*/
private Path createBalloonPath(RectF balloonRect, float progress) {
Path path = new Path();
path.moveTo(balloonRect.centerX(), balloonRect.bottom);
float progressWidth = balloonRect.width() * progress;
float progressHeight = balloonRect.height() * progress;
//draw left half
float leftIncrementX1 = progressWidth * -0.48f;
float leftIncrementY1 = progressHeight * 0.75f;
float leftIncrementX2 = progressWidth * -0.03f;
float leftIncrementY2 = progressHeight * -1.6f;
float leftIncrementX3 = progressWidth * 0.9f;
float leftIncrementY3 = progressHeight * -1.0f;
path.cubicTo(balloonRect.left + balloonRect.width() * 0.25f + leftIncrementX1, balloonRect.centerY() - balloonRect.height() * 0.4f + leftIncrementY1,
balloonRect.left - balloonRect.width() * 0.20f + leftIncrementX2, balloonRect.centerY() + balloonRect.height() * 1.15f + leftIncrementY2,
balloonRect.left - balloonRect.width() * 0.4f + leftIncrementX3, balloonRect.bottom + leftIncrementY3);
// the results of the left final transformation
// path.cubicTo(balloonRect.left - balloonRect.width() * 0.13f, balloonRect.centerY() + balloonRect.height() * 0.35f,
// balloonRect.left - balloonRect.width() * 0.23f, balloonRect.centerY() - balloonRect.height() * 0.45f,
// balloonRect.left + balloonRect.width() * 0.5f, balloonRect.bottom balloonRect.height());
//draw right half
float rightIncrementX1 = progressWidth * 1.51f;
float rightIncrementY1 = progressHeight * -0.05f;
float rightIncrementX2 = progressWidth * 0.03f;
float rightIncrementY2 = progressHeight * 0.5f;
float rightIncrementX3 = 0.0f;
float rightIncrementY3 = 0.0f;
path.cubicTo(balloonRect.left - balloonRect.width() * 0.38f + rightIncrementX1, balloonRect.centerY() - balloonRect.height() * 0.4f + rightIncrementY1,
balloonRect.left + balloonRect.width() * 1.1f + rightIncrementX2, balloonRect.centerY() - balloonRect.height() * 0.15f + rightIncrementY2,
balloonRect.left + balloonRect.width() * 0.5f + rightIncrementX3, balloonRect.bottom + rightIncrementY3);
// the results of the right final transformation
// path.cubicTo(balloonRect.left + balloonRect.width() * 1.23f, balloonRect.centerY() - balloonRect.height() * 0.45f,
// balloonRect.left + balloonRect.width() * 1.13f, balloonRect.centerY() + balloonRect.height() * 0.35f,
// balloonRect.left + balloonRect.width() * 0.5f, balloonRect.bottom);
return path;
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public BalloonLoadingRenderer build() {
BalloonLoadingRenderer loadingRenderer = new BalloonLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,323 @@
package app.dinus.com.loadingdrawable.render.goods;
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.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.view.animation.Interpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class WaterBottleLoadingRenderer extends LoadingRenderer {
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
private static final float DEFAULT_WIDTH = 200.0f;
private static final float DEFAULT_HEIGHT = 150.0f;
private static final float DEFAULT_STROKE_WIDTH = 1.5f;
private static final float DEFAULT_BOTTLE_WIDTH = 30;
private static final float DEFAULT_BOTTLE_HEIGHT = 43;
private static final float WATER_LOWEST_POINT_TO_BOTTLENECK_DISTANCE = 30;
private static final int DEFAULT_WAVE_COUNT = 5;
private static final int DEFAULT_WATER_DROP_COUNT = 25;
private static final int MAX_WATER_DROP_RADIUS = 5;
private static final int MIN_WATER_DROP_RADIUS = 1;
private static final int DEFAULT_BOTTLE_COLOR = Color.parseColor("#FFDAEBEB");
private static final int DEFAULT_WATER_COLOR = Color.parseColor("#FF29E3F2");
private static final float DEFAULT_TEXT_SIZE = 7.0f;
private static final String LOADING_TEXT = "loading";
private static final long ANIMATION_DURATION = 11111;
private final Random mRandom = new Random();
private final Paint mPaint = new Paint();
private final RectF mCurrentBounds = new RectF();
private final RectF mBottleBounds = new RectF();
private final RectF mWaterBounds = new RectF();
private final Rect mLoadingBounds = new Rect();
private final List<WaterDropHolder> mWaterDropHolders = new ArrayList<>();
private float mTextSize;
private float mProgress;
private float mBottleWidth;
private float mBottleHeight;
private float mStrokeWidth;
private float mWaterLowestPointToBottleneckDistance;
private int mBottleColor;
private int mWaterColor;
private int mWaveCount;
private WaterBottleLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
}
private void init(Context context) {
mTextSize = DensityUtil.dip2px(context, DEFAULT_TEXT_SIZE);
mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH);
mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT);
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mBottleWidth = DensityUtil.dip2px(context, DEFAULT_BOTTLE_WIDTH);
mBottleHeight = DensityUtil.dip2px(context, DEFAULT_BOTTLE_HEIGHT);
mWaterLowestPointToBottleneckDistance = DensityUtil.dip2px(context, WATER_LOWEST_POINT_TO_BOTTLENECK_DISTANCE);
mBottleColor = DEFAULT_BOTTLE_COLOR;
mWaterColor = DEFAULT_WATER_COLOR;
mWaveCount = DEFAULT_WAVE_COUNT;
mDuration = ANIMATION_DURATION;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStrokeJoin(Paint.Join.ROUND);
}
@Override
protected void draw(Canvas canvas, Rect bounds) {
int saveCount = canvas.save();
RectF arcBounds = mCurrentBounds;
arcBounds.set(bounds);
//draw bottle
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mBottleColor);
canvas.drawPath(createBottlePath(mBottleBounds), mPaint);
//draw water
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(mWaterColor);
canvas.drawPath(createWaterPath(mWaterBounds, mProgress), mPaint);
//draw water drop
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mWaterColor);
for (WaterDropHolder waterDropHolder : mWaterDropHolders) {
if (waterDropHolder.mNeedDraw) {
canvas.drawCircle(waterDropHolder.mInitX, waterDropHolder.mCurrentY, waterDropHolder.mRadius, mPaint);
}
}
//draw loading text
mPaint.setColor(mBottleColor);
canvas.drawText(LOADING_TEXT, mBottleBounds.centerX() - mLoadingBounds.width() / 2.0f,
mBottleBounds.bottom + mBottleBounds.height() * 0.2f, mPaint);
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
if (mCurrentBounds.width() <= 0) {
return;
}
RectF arcBounds = mCurrentBounds;
//compute gas tube bounds
mBottleBounds.set(arcBounds.centerX() - mBottleWidth / 2.0f, arcBounds.centerY() - mBottleHeight / 2.0f,
arcBounds.centerX() + mBottleWidth / 2.0f, arcBounds.centerY() + mBottleHeight / 2.0f);
//compute pipe body bounds
mWaterBounds.set(mBottleBounds.left + mStrokeWidth * 1.5f, mBottleBounds.top + mWaterLowestPointToBottleneckDistance,
mBottleBounds.right - mStrokeWidth * 1.5f, mBottleBounds.bottom - mStrokeWidth * 1.5f);
//compute wave progress
float totalWaveProgress = renderProgress * mWaveCount;
float currentWaveProgress = totalWaveProgress - ((int) totalWaveProgress);
if (currentWaveProgress > 0.5f) {
mProgress = 1.0f - MATERIAL_INTERPOLATOR.getInterpolation((currentWaveProgress - 0.5f) * 2.0f);
} else {
mProgress = MATERIAL_INTERPOLATOR.getInterpolation(currentWaveProgress * 2.0f);
}
//init water drop holders
if (mWaterDropHolders.isEmpty()) {
initWaterDropHolders(mBottleBounds, mWaterBounds);
}
//compute the location of these water drops
for (WaterDropHolder waterDropHolder : mWaterDropHolders) {
if (waterDropHolder.mDelayDuration < renderProgress
&& waterDropHolder.mDelayDuration + waterDropHolder.mDuration > renderProgress) {
float riseProgress = (renderProgress - waterDropHolder.mDelayDuration) / waterDropHolder.mDuration;
riseProgress = riseProgress < 0.5f ? riseProgress * 2.0f : 1.0f - (riseProgress - 0.5f) * 2.0f;
waterDropHolder.mCurrentY = waterDropHolder.mInitY -
MATERIAL_INTERPOLATOR.getInterpolation(riseProgress) * waterDropHolder.mRiseHeight;
waterDropHolder.mNeedDraw = true;
} else {
waterDropHolder.mNeedDraw = false;
}
}
//measure loading text
mPaint.setTextSize(mTextSize);
mPaint.getTextBounds(LOADING_TEXT, 0, LOADING_TEXT.length(), mLoadingBounds);
}
private Path createBottlePath(RectF bottleRect) {
float bottleneckWidth = bottleRect.width() * 0.3f;
float bottleneckHeight = bottleRect.height() * 0.415f;
float bottleneckDecorationWidth = bottleneckWidth * 1.1f;
float bottleneckDecorationHeight = bottleneckHeight * 0.167f;
Path path = new Path();
//draw the left side of the bottleneck decoration
path.moveTo(bottleRect.centerX() - bottleneckDecorationWidth * 0.5f, bottleRect.top);
path.quadTo(bottleRect.centerX() - bottleneckDecorationWidth * 0.5f - bottleneckWidth * 0.15f, bottleRect.top + bottleneckDecorationHeight * 0.5f,
bottleRect.centerX() - bottleneckWidth * 0.5f, bottleRect.top + bottleneckDecorationHeight);
path.lineTo(bottleRect.centerX() - bottleneckWidth * 0.5f, bottleRect.top + bottleneckHeight);
//draw the left side of the bottle's body
float radius = (bottleRect.width() - mStrokeWidth) / 2.0f;
float centerY = bottleRect.bottom - 0.86f * radius;
RectF bodyRect = new RectF(bottleRect.left, centerY - radius, bottleRect.right, centerY + radius);
path.addArc(bodyRect, 255, -135);
//draw the bottom of the bottle
float bottleBottomWidth = bottleRect.width() / 2.0f;
path.lineTo(bottleRect.centerX() - bottleBottomWidth / 2.0f, bottleRect.bottom);
path.lineTo(bottleRect.centerX() + bottleBottomWidth / 2.0f, bottleRect.bottom);
//draw the right side of the bottle's body
path.addArc(bodyRect, 60, -135);
//draw the right side of the bottleneck decoration
path.lineTo(bottleRect.centerX() + bottleneckWidth * 0.5f, bottleRect.top + bottleneckDecorationHeight);
path.quadTo(bottleRect.centerX() + bottleneckDecorationWidth * 0.5f + bottleneckWidth * 0.15f, bottleRect.top + bottleneckDecorationHeight * 0.5f,
bottleRect.centerX() + bottleneckDecorationWidth * 0.5f, bottleRect.top);
return path;
}
private Path createWaterPath(RectF waterRect, float progress) {
Path path = new Path();
path.moveTo(waterRect.left, waterRect.top);
//Similar to the way draw the bottle's bottom sides
float radius = (waterRect.width() - mStrokeWidth) / 2.0f;
float centerY = waterRect.bottom - 0.86f * radius;
float bottleBottomWidth = waterRect.width() / 2.0f;
RectF bodyRect = new RectF(waterRect.left, centerY - radius, waterRect.right, centerY + radius);
path.addArc(bodyRect, 187.5f, -67.5f);
path.lineTo(waterRect.centerX() - bottleBottomWidth / 2.0f, waterRect.bottom);
path.lineTo(waterRect.centerX() + bottleBottomWidth / 2.0f, waterRect.bottom);
path.addArc(bodyRect, 60, -67.5f);
//draw the water waves
float cubicXChangeSize = waterRect.width() * 0.35f * progress;
float cubicYChangeSize = waterRect.height() * 1.2f * progress;
path.cubicTo(waterRect.left + waterRect.width() * 0.80f - cubicXChangeSize, waterRect.top - waterRect.height() * 1.2f + cubicYChangeSize,
waterRect.left + waterRect.width() * 0.55f - cubicXChangeSize, waterRect.top - cubicYChangeSize,
waterRect.left, waterRect.top - mStrokeWidth / 2.0f);
path.lineTo(waterRect.left, waterRect.top);
return path;
}
private void initWaterDropHolders(RectF bottleRect, RectF waterRect) {
float bottleRadius = bottleRect.width() / 2.0f;
float lowestWaterPointY = waterRect.top;
float twoSidesInterval = 0.2f * bottleRect.width();
float atLeastDelayDuration = 0.1f;
float unitDuration = 0.1f;
float delayDurationRange = 0.6f;
int radiusRandomRange = MAX_WATER_DROP_RADIUS - MIN_WATER_DROP_RADIUS;
float currentXRandomRange = bottleRect.width() * 0.6f;
for (int i = 0; i < DEFAULT_WATER_DROP_COUNT; i++) {
WaterDropHolder waterDropHolder = new WaterDropHolder();
waterDropHolder.mRadius = MIN_WATER_DROP_RADIUS + mRandom.nextInt(radiusRandomRange);
waterDropHolder.mInitX = bottleRect.left + twoSidesInterval + mRandom.nextFloat() * currentXRandomRange;
waterDropHolder.mInitY = lowestWaterPointY + waterDropHolder.mRadius / 2.0f;
waterDropHolder.mRiseHeight = getMaxRiseHeight(bottleRadius, waterDropHolder.mRadius, waterDropHolder.mInitX - bottleRect.left)
* (0.2f + 0.8f * mRandom.nextFloat());
waterDropHolder.mDelayDuration = atLeastDelayDuration + mRandom.nextFloat() * delayDurationRange;
waterDropHolder.mDuration = waterDropHolder.mRiseHeight / bottleRadius * unitDuration;
mWaterDropHolders.add(waterDropHolder);
}
}
private float getMaxRiseHeight(float bottleRadius, float waterDropRadius, float currentX) {
float coordinateX = currentX - bottleRadius;
float bottleneckRadius = bottleRadius * 0.3f;
if (coordinateX - waterDropRadius > -bottleneckRadius
&& coordinateX + waterDropRadius < bottleneckRadius) {
return bottleRadius * 2.0f;
}
return (float) (Math.sqrt(Math.pow(bottleRadius, 2.0f) - Math.pow(coordinateX, 2.0f)) - waterDropRadius);
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
}
private class WaterDropHolder {
public float mCurrentY;
public float mInitX;
public float mInitY;
public float mDelayDuration;
public float mRiseHeight;
public float mRadius;
public float mDuration;
public boolean mNeedDraw;
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public WaterBottleLoadingRenderer build() {
WaterBottleLoadingRenderer loadingRenderer = new WaterBottleLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,361 @@
package app.dinus.com.loadingdrawable.render.scenery;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
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.util.DisplayMetrics;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class DayNightLoadingRenderer extends LoadingRenderer {
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
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 int MAX_ALPHA = 255;
private static final int DEGREE_360 = 360;
private static final int MAX_SUN_RAY_COUNT = 12;
private static final float DEFAULT_WIDTH = 200.0f;
private static final float DEFAULT_HEIGHT = 150.0f;
private static final float DEFAULT_STROKE_WIDTH = 2.5f;
private static final float DEFAULT_SUN$MOON_RADIUS = 12.0f;
private static final float DEFAULT_STAR_RADIUS = 2.5f;
private static final float DEFAULT_SUN_RAY_LENGTH = 10.0f;
private static final float DEFAULT_SUN_RAY_OFFSET = 3.0f;
public static final float STAR_RISE_PROGRESS_OFFSET = 0.2f;
public static final float STAR_DECREASE_PROGRESS_OFFSET = 0.8f;
public static final float STAR_FLASH_PROGRESS_PERCENTAGE = 0.2f;
private static final float MAX_SUN_ROTATE_DEGREE = DEGREE_360 / 3.0f;
private static final float MAX_MOON_ROTATE_DEGREE = DEGREE_360 / 6.0f;
private static final float SUN_RAY_INTERVAL_DEGREE = DEGREE_360 / 3.0f / 55;
private static final float SUN_RISE_DURATION_OFFSET = 0.143f;
private static final float SUN_ROTATE_DURATION_OFFSET = 0.492f;
private static final float SUN_DECREASE_DURATION_OFFSET = 0.570f;
private static final float MOON_RISE_DURATION_OFFSET = 0.713f;
private static final float MOON_DECREASE_START_DURATION_OFFSET = 0.935f;
private static final float MOON_DECREASE_END_DURATION_OFFSET = 1.0f;
private static final float STAR_RISE_START_DURATION_OFFSET = 0.684f;
private static final float STAR_DECREASE_START_DURATION_OFFSET = 1.0f;
private static final int DEFAULT_COLOR = Color.parseColor("#ff21fd8e");
private static final long ANIMATION_DURATION = 5111;
private final Random mRandom = new Random();
private final List<StarHolder> mStarHolders = new ArrayList<>();
private final Paint mPaint = new Paint();
private final RectF mTempBounds = new RectF();
private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animator) {
super.onAnimationRepeat(animator);
}
};
private int mCurrentColor;
private float mMaxStarOffsets;
private float mStrokeWidth;
private float mStarRadius;
private float mSun$MoonRadius;
private float mSunCoordinateY;
private float mMoonCoordinateY;
//the y-coordinate of the end point of the sun ray
private float mSunRayEndCoordinateY;
//the y-coordinate of the start point of the sun ray
private float mSunRayStartCoordinateY;
//the y-coordinate of the start point of the sun
private float mInitSun$MoonCoordinateY;
//the distance from the outside to the center of the drawable
private float mMaxSun$MoonRiseDistance;
private float mSunRayRotation;
private float mMoonRotation;
//the number of sun's rays is increasing
private boolean mIsExpandSunRay;
private boolean mShowStar;
private int mSunRayCount;
private DayNightLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
addRenderListener(mAnimatorListener);
}
private void init(Context context) {
mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH);
mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT);
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mStarRadius = DensityUtil.dip2px(context, DEFAULT_STAR_RADIUS);
mSun$MoonRadius = DensityUtil.dip2px(context, DEFAULT_SUN$MOON_RADIUS);
mInitSun$MoonCoordinateY = mHeight + mSun$MoonRadius + mStrokeWidth * 2.0f;
mMaxSun$MoonRiseDistance = mHeight / 2.0f + mSun$MoonRadius;
mSunRayStartCoordinateY = mInitSun$MoonCoordinateY - mMaxSun$MoonRiseDistance //the center
- mSun$MoonRadius //sub the radius
- mStrokeWidth // sub the with the sun circle
- DensityUtil.dip2px(context, DEFAULT_SUN_RAY_OFFSET); //sub the interval between the sun and the sun ray
//add strokeWidth * 2.0f because the stroke cap is Paint.Cap.ROUND
mSunRayEndCoordinateY = mSunRayStartCoordinateY - DensityUtil.dip2px(context, DEFAULT_SUN_RAY_LENGTH)
+ mStrokeWidth;
mSunCoordinateY = mInitSun$MoonCoordinateY;
mMoonCoordinateY = mInitSun$MoonCoordinateY;
mCurrentColor = DEFAULT_COLOR;
mDuration = ANIMATION_DURATION;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStrokeJoin(Paint.Join.ROUND);
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);
mPaint.setAlpha(MAX_ALPHA);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mCurrentColor);
if (mSunCoordinateY < mInitSun$MoonCoordinateY) {
canvas.drawCircle(arcBounds.centerX(), mSunCoordinateY, mSun$MoonRadius, mPaint);
}
if (mMoonCoordinateY < mInitSun$MoonCoordinateY) {
int moonSaveCount = canvas.save();
canvas.rotate(mMoonRotation, arcBounds.centerX(), mMoonCoordinateY);
canvas.drawPath(createMoonPath(arcBounds.centerX(), mMoonCoordinateY), mPaint);
canvas.restoreToCount(moonSaveCount);
}
for (int i = 0; i < mSunRayCount; i++) {
int sunRaySaveCount = canvas.save();
//rotate 45 degrees can change the direction of 0 degrees to 1:30 clock
//-mSunRayRotation means reverse rotation
canvas.rotate(45 - mSunRayRotation
+ (mIsExpandSunRay ? i : MAX_SUN_RAY_COUNT - i) * DEGREE_360 / MAX_SUN_RAY_COUNT,
arcBounds.centerX(), mSunCoordinateY);
canvas.drawLine(arcBounds.centerX(), mSunRayStartCoordinateY, arcBounds.centerX(), mSunRayEndCoordinateY, mPaint);
canvas.restoreToCount(sunRaySaveCount);
}
if (mShowStar) {
if (mStarHolders.isEmpty()) {
initStarHolders(arcBounds);
}
for (int i = 0; i < mStarHolders.size(); i++) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAlpha(mStarHolders.get(i).mAlpha);
canvas.drawCircle(mStarHolders.get(i).mCurrentPoint.x, mStarHolders.get(i).mCurrentPoint.y, mStarRadius, mPaint);
}
}
canvas.restoreToCount(saveCount);
}
@Override
protected void computeRender(float renderProgress) {
if (renderProgress <= SUN_RISE_DURATION_OFFSET) {
float sunRiseProgress = renderProgress / SUN_RISE_DURATION_OFFSET;
mSunCoordinateY = mInitSun$MoonCoordinateY - mMaxSun$MoonRiseDistance * MATERIAL_INTERPOLATOR.getInterpolation(sunRiseProgress);
mMoonCoordinateY = mInitSun$MoonCoordinateY;
mShowStar = false;
}
if (renderProgress <= SUN_ROTATE_DURATION_OFFSET && renderProgress > SUN_RISE_DURATION_OFFSET) {
float sunRotateProgress = (renderProgress - SUN_RISE_DURATION_OFFSET) / (SUN_ROTATE_DURATION_OFFSET - SUN_RISE_DURATION_OFFSET);
mSunRayRotation = sunRotateProgress * MAX_SUN_ROTATE_DEGREE;
if ((int) (mSunRayRotation / SUN_RAY_INTERVAL_DEGREE) <= MAX_SUN_RAY_COUNT) {
mIsExpandSunRay = true;
mSunRayCount = (int) (mSunRayRotation / SUN_RAY_INTERVAL_DEGREE);
}
if ((int) ((MAX_SUN_ROTATE_DEGREE - mSunRayRotation) / SUN_RAY_INTERVAL_DEGREE) <= MAX_SUN_RAY_COUNT) {
mIsExpandSunRay = false;
mSunRayCount = (int) ((MAX_SUN_ROTATE_DEGREE - mSunRayRotation) / SUN_RAY_INTERVAL_DEGREE);
}
}
if (renderProgress <= SUN_DECREASE_DURATION_OFFSET && renderProgress > SUN_ROTATE_DURATION_OFFSET) {
float sunDecreaseProgress = (renderProgress - SUN_ROTATE_DURATION_OFFSET) / (SUN_DECREASE_DURATION_OFFSET - SUN_ROTATE_DURATION_OFFSET);
mSunCoordinateY = mInitSun$MoonCoordinateY - mMaxSun$MoonRiseDistance * (1.0f - ACCELERATE_INTERPOLATOR.getInterpolation(sunDecreaseProgress));
}
if (renderProgress <= MOON_RISE_DURATION_OFFSET && renderProgress > SUN_DECREASE_DURATION_OFFSET) {
float moonRiseProgress = (renderProgress - SUN_DECREASE_DURATION_OFFSET) / (MOON_RISE_DURATION_OFFSET - SUN_DECREASE_DURATION_OFFSET);
mMoonRotation = MATERIAL_INTERPOLATOR.getInterpolation(moonRiseProgress) * MAX_MOON_ROTATE_DEGREE;
mSunCoordinateY = mInitSun$MoonCoordinateY;
mMoonCoordinateY = mInitSun$MoonCoordinateY - mMaxSun$MoonRiseDistance * MATERIAL_INTERPOLATOR.getInterpolation(moonRiseProgress);
}
if (renderProgress <= STAR_DECREASE_START_DURATION_OFFSET && renderProgress > STAR_RISE_START_DURATION_OFFSET) {
float starProgress = (renderProgress - STAR_RISE_START_DURATION_OFFSET) / (STAR_DECREASE_START_DURATION_OFFSET - STAR_RISE_START_DURATION_OFFSET);
if (starProgress <= STAR_RISE_PROGRESS_OFFSET) {
for (int i = 0; i < mStarHolders.size(); i++) {
StarHolder starHolder = mStarHolders.get(i);
starHolder.mCurrentPoint.y = starHolder.mPoint.y - (1.0f - starHolder.mInterpolator.getInterpolation(starProgress * 5.0f)) * (mMaxStarOffsets * 0.65f);
starHolder.mCurrentPoint.x = starHolder.mPoint.x;
}
}
if (starProgress > STAR_RISE_PROGRESS_OFFSET && starProgress < STAR_DECREASE_PROGRESS_OFFSET) {
for (int i = 0; i < mStarHolders.size(); i++) {
StarHolder starHolder = mStarHolders.get(i);
if (starHolder.mFlashOffset < starProgress && starProgress < starHolder.mFlashOffset + STAR_FLASH_PROGRESS_PERCENTAGE) {
starHolder.mAlpha = (int) (MAX_ALPHA * MATERIAL_INTERPOLATOR.getInterpolation(
Math.abs(starProgress - (starHolder.mFlashOffset + STAR_FLASH_PROGRESS_PERCENTAGE / 2.0f)) / (STAR_FLASH_PROGRESS_PERCENTAGE / 2.0f)));
}
}
}
if (starProgress >= STAR_DECREASE_PROGRESS_OFFSET) {
for (int i = 0; i < mStarHolders.size(); i++) {
StarHolder starHolder = mStarHolders.get(i);
starHolder.mCurrentPoint.y = starHolder.mPoint.y + starHolder.mInterpolator.getInterpolation((starProgress - STAR_DECREASE_PROGRESS_OFFSET) * 5.0f) * mMaxStarOffsets;
starHolder.mCurrentPoint.x = starHolder.mPoint.x;
}
}
mShowStar = true;
}
if (renderProgress <= MOON_DECREASE_END_DURATION_OFFSET && renderProgress > MOON_DECREASE_START_DURATION_OFFSET) {
float moonDecreaseProgress = (renderProgress - MOON_DECREASE_START_DURATION_OFFSET) / (MOON_DECREASE_END_DURATION_OFFSET - MOON_DECREASE_START_DURATION_OFFSET);
mMoonCoordinateY = mInitSun$MoonCoordinateY - mMaxSun$MoonRiseDistance * (1.0f - ACCELERATE_INTERPOLATOR.getInterpolation(moonDecreaseProgress));
}
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
}
private void initStarHolders(RectF currentBounds) {
mStarHolders.add(new StarHolder(0.3f, new PointF(currentBounds.left + currentBounds.width() * 0.175f,
currentBounds.top + currentBounds.height() * 0.0934f)));
mStarHolders.add(new StarHolder(0.2f, new PointF(currentBounds.left + currentBounds.width() * 0.175f,
currentBounds.top + currentBounds.height() * 0.62f)));
mStarHolders.add(new StarHolder(0.2f, new PointF(currentBounds.left + currentBounds.width() * 0.2525f,
currentBounds.top + currentBounds.height() * 0.43f)));
mStarHolders.add(new StarHolder(0.5f, new PointF(currentBounds.left + currentBounds.width() * 0.4075f,
currentBounds.top + currentBounds.height() * 0.0934f)));
mStarHolders.add(new StarHolder(new PointF(currentBounds.left + currentBounds.width() * 0.825f,
currentBounds.top + currentBounds.height() * 0.04f)));
mStarHolders.add(new StarHolder(new PointF(currentBounds.left + currentBounds.width() * 0.7075f,
currentBounds.top + currentBounds.height() * 0.147f)));
mStarHolders.add(new StarHolder(new PointF(currentBounds.left + currentBounds.width() * 0.3475f,
currentBounds.top + currentBounds.height() * 0.2567f)));
mStarHolders.add(new StarHolder(0.6f, new PointF(currentBounds.left + currentBounds.width() * 0.5825f,
currentBounds.top + currentBounds.height() * 0.277f)));
mStarHolders.add(new StarHolder(new PointF(currentBounds.left + currentBounds.width() * 0.84f,
currentBounds.top + currentBounds.height() * 0.32f)));
mStarHolders.add(new StarHolder(new PointF(currentBounds.left + currentBounds.width() * 0.8f,
currentBounds.top + currentBounds.height() / 0.502f)));
mStarHolders.add(new StarHolder(0.6f, new PointF(currentBounds.left + currentBounds.width() * 0.7f,
currentBounds.top + currentBounds.height() * 0.473f)));
mMaxStarOffsets = currentBounds.height();
}
private Path createMoonPath(float moonCenterX, float moonCenterY) {
RectF moonRectF = new RectF(moonCenterX - mSun$MoonRadius, moonCenterY - mSun$MoonRadius,
moonCenterX + mSun$MoonRadius, moonCenterY + mSun$MoonRadius);
Path path = new Path();
path.addArc(moonRectF, -90, 180);
path.quadTo(moonCenterX + mSun$MoonRadius / 2.0f, moonCenterY, moonCenterX, moonCenterY - mSun$MoonRadius);
return path;
}
private class StarHolder {
public int mAlpha;
public PointF mCurrentPoint;
public final PointF mPoint;
public final float mFlashOffset;
public final Interpolator mInterpolator;
public StarHolder(PointF point) {
this(1.0f, point);
}
public StarHolder(float flashOffset, PointF mPoint) {
this.mAlpha = MAX_ALPHA;
this.mCurrentPoint = new PointF();
this.mPoint = mPoint;
this.mFlashOffset = flashOffset;
this.mInterpolator = INTERPOLATORS[mRandom.nextInt(INTERPOLATORS.length)];
}
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public DayNightLoadingRenderer build() {
DayNightLoadingRenderer loadingRenderer = new DayNightLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,474 @@
package app.dinus.com.loadingdrawable.render.scenery;
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.util.DisplayMetrics;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import androidx.annotation.IntDef;
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.R;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
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<LeafHolder> 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;
private ElectricFanLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
addRenderListener(mAnimatorListener);
}
private void init(Context context) {
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(R.drawable.ic_leaf);
mLoadingDrawable = context.getResources().getDrawable(R.drawable.ic_loading);
mElectricFanDrawable = context.getResources().getDrawable(R.drawable.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<PointF> {
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);
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public ElectricFanLoadingRenderer build() {
ElectricFanLoadingRenderer loadingRenderer = new ElectricFanLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,676 @@
package app.dinus.com.loadingdrawable.render.shapechange;
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.PathMeasure;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class CircleBroodLoadingRenderer extends LoadingRenderer {
private final Interpolator MOTHER_MOVE_INTERPOLATOR = new MotherMoveInterpolator();
private final Interpolator CHILD_MOVE_INTERPOLATOR = new ChildMoveInterpolator();
private final Interpolator ACCELERATE_INTERPOLATOR03 = new AccelerateInterpolator(0.3f);
private final Interpolator ACCELERATE_INTERPOLATOR05 = new AccelerateInterpolator(0.5f);
private final Interpolator ACCELERATE_INTERPOLATOR08 = new AccelerateInterpolator(0.8f);
private final Interpolator ACCELERATE_INTERPOLATOR10 = new AccelerateInterpolator(1.0f);
private final Interpolator DECELERATE_INTERPOLATOR03 = new DecelerateInterpolator(0.3f);
private final Interpolator DECELERATE_INTERPOLATOR05 = new DecelerateInterpolator(0.5f);
private final Interpolator DECELERATE_INTERPOLATOR08 = new DecelerateInterpolator(0.8f);
private final Interpolator DECELERATE_INTERPOLATOR10 = new DecelerateInterpolator(1.0f);
private float STAGE_MOTHER_FORWARD_TOP_LEFT = 0.34f;
private float STAGE_MOTHER_BACKWARD_TOP_LEFT = 0.5f;
private float STAGE_MOTHER_FORWARD_BOTTOM_LEFT = 0.65f;
private float STAGE_MOTHER_BACKWARD_BOTTOM_LEFT = 0.833f;
private float STAGE_CHILD_DELAY = 0.1f;
private float STAGE_CHILD_PRE_FORWARD_TOP_LEFT = 0.26f;
private float STAGE_CHILD_FORWARD_TOP_LEFT = 0.34f;
private float STAGE_CHILD_PRE_BACKWARD_TOP_LEFT = 0.42f;
private float STAGE_CHILD_BACKWARD_TOP_LEFT = 0.5f;
private float STAGE_CHILD_FORWARD_BOTTOM_LEFT = 0.7f;
private float STAGE_CHILD_BACKWARD_BOTTOM_LEFT = 0.9f;
private final float OVAL_BEZIER_FACTOR = 0.55152f;
private final float DEFAULT_WIDTH = 40.0f;
private final float DEFAULT_HEIGHT = 20.0f;
private final float MAX_MATHER_OVAL_SIZE = 12;//19;
private final float MIN_CHILD_OVAL_RADIUS = 1;//5;
private final float MAX_MATHER_SHAPE_CHANGE_FACTOR = 0.8452f;
private final int DEFAULT_OVAL_COLOR = Color.parseColor("#FFBE1C23");
private final int DEFAULT_OVAL_DEEP_COLOR = Color.parseColor("#FFB21721");
private final int DEFAULT_BACKGROUND_COLOR = Color.parseColor("#FFE3C172");
private final int DEFAULT_BACKGROUND_DEEP_COLOR = Color.parseColor("#FFE2B552");
private final long ANIMATION_DURATION = 4111;
private final Paint mPaint = new Paint();
private final RectF mCurrentBounds = new RectF();
private final Path mMotherOvalPath = new Path();
private final Path mMotherMovePath = new Path();
private final Path mChildMovePath = new Path();
private final float[] mMotherPosition = new float[2];
private final float[] mChildPosition = new float[2];
private final PathMeasure mMotherMovePathMeasure = new PathMeasure();
private final PathMeasure mChildMovePathMeasure = new PathMeasure();
private float mChildOvalRadius;
private float mBasicChildOvalRadius;
private float mMaxMotherOvalSize;
private float mMotherOvalHalfWidth;
private float mMotherOvalHalfHeight;
private float mChildLeftXOffset;
private float mChildLeftYOffset;
private float mChildRightXOffset;
private float mChildRightYOffset;
private int mOvalColor;
private int mOvalDeepColor;
private int mBackgroundColor;
private int mBackgroundDeepColor;
private int mCurrentOvalColor;
private int mCurrentBackgroundColor;
private int mRevealCircleRadius;
private int mMaxRevealCircleRadius;
private int mRotateDegrees;
private float mStageMotherForwardTopLeftLength;
private float mStageMotherBackwardTopLeftLength;
private float mStageMotherForwardBottomLeftLength;
private float mStageMotherBackwardBottomLeftLength;
private float mStageChildPreForwardTopLeftLength;
private float mStageChildForwardTopLeftLength;
private float mStageChildPreBackwardTopLeftLength;
private float mStageChildBackwardTopLeftLength;
private float mStageChildForwardBottomLeftLength;
private float mStageChildBackwardBottomLeftLength;
private CircleBroodLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
}
private void init(Context context) {
mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH);
mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT);
mMaxMotherOvalSize = DensityUtil.dip2px(context, MAX_MATHER_OVAL_SIZE);
mBasicChildOvalRadius = DensityUtil.dip2px(context, MIN_CHILD_OVAL_RADIUS);
mOvalColor = DEFAULT_OVAL_COLOR;
mOvalDeepColor = DEFAULT_OVAL_DEEP_COLOR;
mBackgroundColor = DEFAULT_BACKGROUND_COLOR;
mBackgroundDeepColor = DEFAULT_BACKGROUND_DEEP_COLOR;
mMotherOvalHalfWidth = mMaxMotherOvalSize;
mMotherOvalHalfHeight = mMaxMotherOvalSize;
mMaxRevealCircleRadius = (int) (Math.sqrt(mWidth * mWidth + mHeight * mHeight) / 2 + 1);
mDuration = ANIMATION_DURATION;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(1.0f);
}
@Override
protected void draw(Canvas canvas, Rect bounds) {
int saveCount = canvas.save();
RectF arcBounds = mCurrentBounds;
arcBounds.set(bounds);
//draw background
canvas.drawColor(mCurrentBackgroundColor);
//draw reveal circle
if (mRevealCircleRadius > 0) {
mPaint.setColor(mCurrentBackgroundColor == mBackgroundColor ? mBackgroundDeepColor : mBackgroundColor);
canvas.drawCircle(arcBounds.centerX(), arcBounds.centerY(), mRevealCircleRadius, mPaint);
}
//draw mother oval
mPaint.setColor(mCurrentOvalColor);
int motherSaveCount = canvas.save();
canvas.rotate(mRotateDegrees, mMotherPosition[0], mMotherPosition[1]);
canvas.drawPath(createMotherPath(), mPaint);
canvas.drawPath(createLinkPath(), mPaint);
canvas.restoreToCount(motherSaveCount);
int childSaveCount = canvas.save();
canvas.rotate(mRotateDegrees, mChildPosition[0], mChildPosition[1]);
canvas.drawPath(createChildPath(), mPaint);
canvas.restoreToCount(childSaveCount);
canvas.restoreToCount(saveCount);
// canvas.drawPath(mMotherMovePath, mPaint);
// canvas.drawPath(mChildMovePath, mPaint);
// canvas.drawLine(mMotherPosition[0], mMotherPosition[1], mChildPosition[0], mChildPosition[1], mPaint);
}
private Path createMotherPath() {
mMotherOvalPath.reset();
mMotherOvalPath.addOval(new RectF(mMotherPosition[0] - mMotherOvalHalfWidth, mMotherPosition[1] - mMotherOvalHalfHeight,
mMotherPosition[0] + mMotherOvalHalfWidth, mMotherPosition[1] + mMotherOvalHalfHeight), Path.Direction.CW);
return mMotherOvalPath;
}
private Path createChildPath() {
float bezierOffset = mChildOvalRadius * OVAL_BEZIER_FACTOR;
Path path = new Path();
path.moveTo(mChildPosition[0], mChildPosition[1] - mChildOvalRadius);
//left_top arc
path.cubicTo(mChildPosition[0] - bezierOffset - mChildLeftXOffset, mChildPosition[1] - mChildOvalRadius,
mChildPosition[0] - mChildOvalRadius - mChildLeftXOffset, mChildPosition[1] - bezierOffset + mChildLeftYOffset,
mChildPosition[0] - mChildOvalRadius - mChildLeftXOffset, mChildPosition[1]);
//left_bottom arc
path.cubicTo(mChildPosition[0] - mChildOvalRadius - mChildLeftXOffset, mChildPosition[1] + bezierOffset - mChildLeftYOffset,
mChildPosition[0] - bezierOffset - mChildLeftXOffset, mChildPosition[1] + mChildOvalRadius,
mChildPosition[0], mChildPosition[1] + mChildOvalRadius);
//right_bottom arc
path.cubicTo(mChildPosition[0] + bezierOffset + mChildRightXOffset, mChildPosition[1] + mChildOvalRadius,
mChildPosition[0] + mChildOvalRadius + mChildRightXOffset, mChildPosition[1] + bezierOffset - mChildRightYOffset,
mChildPosition[0] + mChildOvalRadius + mChildRightXOffset, mChildPosition[1]);
//right_top arc
path.cubicTo(mChildPosition[0] + mChildOvalRadius + mChildRightXOffset, mChildPosition[1] - bezierOffset + mChildRightYOffset,
mChildPosition[0] + bezierOffset + mChildRightXOffset, mChildPosition[1] - mChildOvalRadius,
mChildPosition[0], mChildPosition[1] - mChildOvalRadius);
return path;
}
private Path createLinkPath() {
Path path = new Path();
float bezierOffset = mMotherOvalHalfWidth * OVAL_BEZIER_FACTOR;
float distance = (float) Math.sqrt(Math.pow(mMotherPosition[0] - mChildPosition[0], 2.0f) + Math.pow(mMotherPosition[1] - mChildPosition[1], 2.0f));
if (distance <= mMotherOvalHalfWidth + mChildOvalRadius * 1.2f
&& distance >= mMotherOvalHalfWidth - mChildOvalRadius * 1.2f) {
float maxOffsetY = 2 * mChildOvalRadius * 1.2f;
float offsetRate = (distance - (mMotherOvalHalfWidth - mChildOvalRadius * 1.2f)) / maxOffsetY;
float mMotherOvalOffsetY = mMotherOvalHalfHeight - offsetRate * (mMotherOvalHalfHeight - mChildOvalRadius) * 0.85f;
mMotherOvalPath.addOval(new RectF(mMotherPosition[0] - mMotherOvalHalfWidth, mMotherPosition[1] - mMotherOvalOffsetY,
mMotherPosition[0] + mMotherOvalHalfWidth, mMotherPosition[1] + mMotherOvalOffsetY), Path.Direction.CW);
float mMotherXOffset = distance - mMotherOvalHalfWidth + mChildOvalRadius;
float distanceUltraLeft = (float) Math.sqrt(Math.pow(mMotherPosition[0] - mMotherOvalHalfWidth - mChildPosition[0], 2.0f)
+ Math.pow(mMotherPosition[1] - mChildPosition[1], 2.0f));
float distanceUltraRight = (float) Math.sqrt(Math.pow(mMotherPosition[0] + mMotherOvalHalfWidth - mChildPosition[0], 2.0f)
+ Math.pow(mMotherPosition[1] - mChildPosition[1], 2.0f));
path.moveTo(mMotherPosition[0], mMotherPosition[1] + mMotherOvalOffsetY);
if (distanceUltraRight < distanceUltraLeft) {
//right_bottom arc
path.cubicTo(mMotherPosition[0] + bezierOffset + mMotherXOffset, mMotherPosition[1] + mMotherOvalOffsetY,
mMotherPosition[0] + distance + mChildOvalRadius, mMotherPosition[1] + mChildOvalRadius * 1.5f,
mMotherPosition[0] + distance + mChildOvalRadius, mMotherPosition[1]);
//right_top arc
path.cubicTo(mMotherPosition[0] + distance + mChildOvalRadius, mMotherPosition[1] - mChildOvalRadius * 1.5f,
mMotherPosition[0] + bezierOffset + mMotherXOffset, mMotherPosition[1] - mMotherOvalOffsetY,
mMotherPosition[0], mMotherPosition[1] - mMotherOvalOffsetY);
} else {
//left_bottom arc
path.cubicTo(mMotherPosition[0] - bezierOffset - mMotherXOffset, mMotherPosition[1] + mMotherOvalOffsetY,
mMotherPosition[0] - distance - mChildOvalRadius, mMotherPosition[1] + mChildOvalRadius * 1.5f,
mMotherPosition[0] - distance - mChildOvalRadius, mMotherPosition[1]);
//left_top arc
path.cubicTo(mMotherPosition[0] - distance - mChildOvalRadius, mMotherPosition[1] - mChildOvalRadius * 1.5f,
mMotherPosition[0] - bezierOffset - mMotherXOffset, mMotherPosition[1] - mMotherOvalOffsetY,
mMotherPosition[0], mMotherPosition[1] - mMotherOvalOffsetY);
}
path.lineTo(mMotherPosition[0], mMotherPosition[1] + mMotherOvalOffsetY);
}
return path;
}
@Override
protected void computeRender(float renderProgress) {
if (mCurrentBounds.isEmpty()) {
return;
}
if (mMotherMovePath.isEmpty()) {
mMotherMovePath.set(createMotherMovePath());
mMotherMovePathMeasure.setPath(mMotherMovePath, false);
mChildMovePath.set(createChildMovePath());
mChildMovePathMeasure.setPath(mChildMovePath, false);
}
//mother oval
float motherMoveProgress = MOTHER_MOVE_INTERPOLATOR.getInterpolation(renderProgress);
mMotherMovePathMeasure.getPosTan(getCurrentMotherMoveLength(motherMoveProgress), mMotherPosition, null);
mMotherOvalHalfWidth = mMaxMotherOvalSize;
mMotherOvalHalfHeight = mMaxMotherOvalSize * getMotherShapeFactor(motherMoveProgress);
//child Oval
float childMoveProgress = CHILD_MOVE_INTERPOLATOR.getInterpolation(renderProgress);
mChildMovePathMeasure.getPosTan(getCurrentChildMoveLength(childMoveProgress), mChildPosition, null);
setupChildParams(childMoveProgress);
mRotateDegrees = (int) (Math.toDegrees(Math.atan((mMotherPosition[1] - mChildPosition[1]) /
(mMotherPosition[0] - mChildPosition[0]))));
mRevealCircleRadius = getCurrentRevealCircleRadius(renderProgress);
mCurrentOvalColor = getCurrentOvalColor(renderProgress);
mCurrentBackgroundColor = getCurrentBackgroundColor(renderProgress);
}
private void setupChildParams(float input) {
mChildOvalRadius = mBasicChildOvalRadius;
mChildRightXOffset = 0.0f;
mChildLeftXOffset = 0.0f;
if (input <= STAGE_CHILD_PRE_FORWARD_TOP_LEFT) {
if (input >= 0.25) {
float shapeProgress = (input - 0.25f) / 0.01f;
mChildLeftXOffset = (1.0f - shapeProgress) * mChildOvalRadius * 0.25f;
} else {
mChildLeftXOffset = mChildOvalRadius * 0.25f;
}
} else if (input <= STAGE_CHILD_FORWARD_TOP_LEFT) {
if (input > 0.275f && input < 0.285f) {
float shapeProgress = (input - 0.275f) / 0.01f;
mChildLeftXOffset = shapeProgress * mChildOvalRadius * 0.25f;
} else if (input > 0.285f) {
mChildLeftXOffset = mChildOvalRadius * 0.25f;
}
} else if (input <= STAGE_CHILD_PRE_BACKWARD_TOP_LEFT) {
if (input > 0.38f) {
float radiusProgress = (input - 0.38f) / 0.04f;
mChildOvalRadius = mBasicChildOvalRadius * (1.0f + radiusProgress);
}
} else if (input <= STAGE_CHILD_BACKWARD_TOP_LEFT) {
if (input < 0.46f) {
float radiusProgress = (input - 0.42f) / 0.04f;
mChildOvalRadius = mBasicChildOvalRadius * (2.0f - radiusProgress);
}
} else if (input <= STAGE_CHILD_FORWARD_BOTTOM_LEFT) {
if (input > 0.65f) {
float radiusProgress = (input - 0.65f) / 0.05f;
mChildOvalRadius = mBasicChildOvalRadius * (1.0f + radiusProgress);
}
} else if (input <= STAGE_CHILD_BACKWARD_BOTTOM_LEFT) {
if (input < 0.71f) {
mChildOvalRadius = mBasicChildOvalRadius * 2.0f;
} else if (input < 0.76f) {
float radiusProgress = (input - 0.71f) / 0.05f;
mChildOvalRadius = mBasicChildOvalRadius * (2.0f - radiusProgress);
}
} else {
}
mChildRightYOffset = mChildRightXOffset / 2.5f;
mChildLeftYOffset = mChildLeftXOffset / 2.5f;
}
private float getMotherShapeFactor(float input) {
float shapeProgress;
if (input <= STAGE_MOTHER_FORWARD_TOP_LEFT) {
shapeProgress = input / STAGE_MOTHER_FORWARD_TOP_LEFT;
} else if (input <= STAGE_MOTHER_BACKWARD_TOP_LEFT) {
shapeProgress = (input - STAGE_MOTHER_FORWARD_TOP_LEFT) / (STAGE_MOTHER_BACKWARD_TOP_LEFT - STAGE_MOTHER_FORWARD_TOP_LEFT);
} else if (input <= STAGE_MOTHER_FORWARD_BOTTOM_LEFT) {
shapeProgress = (input - STAGE_MOTHER_BACKWARD_TOP_LEFT) / (STAGE_MOTHER_FORWARD_BOTTOM_LEFT - STAGE_MOTHER_BACKWARD_TOP_LEFT);
} else if (input <= STAGE_MOTHER_BACKWARD_BOTTOM_LEFT) {
shapeProgress = (input - STAGE_MOTHER_FORWARD_BOTTOM_LEFT) / (STAGE_MOTHER_BACKWARD_BOTTOM_LEFT - STAGE_MOTHER_FORWARD_BOTTOM_LEFT);
} else {
shapeProgress = 1.0f;
}
return shapeProgress < 0.5f ?
1.0f - (1.0f - MAX_MATHER_SHAPE_CHANGE_FACTOR) * shapeProgress * 2.0f :
MAX_MATHER_SHAPE_CHANGE_FACTOR + (1.0f - MAX_MATHER_SHAPE_CHANGE_FACTOR) * (shapeProgress - 0.5f) * 2.0f;
}
private float getCurrentMotherMoveLength(float input) {
float currentStartDistance = 0.0f;
float currentStageDistance = 0.0f;
float currentStateStartProgress = 0.0f;
float currentStateEndProgress = 0.0f;
if (input > 0.0f) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageMotherForwardTopLeftLength;
currentStateStartProgress = 0.0f;
currentStateEndProgress = STAGE_MOTHER_FORWARD_TOP_LEFT;
}
if (input > STAGE_MOTHER_FORWARD_TOP_LEFT) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageMotherBackwardTopLeftLength;
currentStateStartProgress = STAGE_MOTHER_FORWARD_TOP_LEFT;
currentStateEndProgress = STAGE_MOTHER_BACKWARD_TOP_LEFT;
}
if (input > STAGE_MOTHER_BACKWARD_TOP_LEFT) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageMotherForwardBottomLeftLength;
currentStateStartProgress = STAGE_MOTHER_BACKWARD_TOP_LEFT;
currentStateEndProgress = STAGE_MOTHER_FORWARD_BOTTOM_LEFT;
}
if (input > STAGE_MOTHER_FORWARD_BOTTOM_LEFT) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageMotherBackwardBottomLeftLength;
currentStateStartProgress = STAGE_MOTHER_FORWARD_BOTTOM_LEFT;
currentStateEndProgress = STAGE_MOTHER_BACKWARD_BOTTOM_LEFT;
}
if (input > STAGE_MOTHER_BACKWARD_BOTTOM_LEFT) {
return currentStartDistance + currentStageDistance;
}
return currentStartDistance + (input - currentStateStartProgress) /
(currentStateEndProgress - currentStateStartProgress) * currentStageDistance;
}
private float getCurrentChildMoveLength(float input) {
float currentStartDistance = 0.0f;
float currentStageDistance = 0.0f;
float currentStateStartProgress = 0.0f;
float currentStateEndProgress = 0.0f;
if (input > 0.0f) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageChildPreForwardTopLeftLength;
currentStateStartProgress = 0.0f;
currentStateEndProgress = STAGE_CHILD_PRE_FORWARD_TOP_LEFT;
}
if (input > STAGE_CHILD_PRE_FORWARD_TOP_LEFT) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageChildForwardTopLeftLength;
currentStateStartProgress = STAGE_CHILD_PRE_FORWARD_TOP_LEFT;
currentStateEndProgress = STAGE_CHILD_FORWARD_TOP_LEFT;
}
if (input > STAGE_CHILD_FORWARD_TOP_LEFT) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageChildPreBackwardTopLeftLength;
currentStateStartProgress = STAGE_CHILD_FORWARD_TOP_LEFT;
currentStateEndProgress = STAGE_CHILD_PRE_BACKWARD_TOP_LEFT;
}
if (input > STAGE_CHILD_PRE_BACKWARD_TOP_LEFT) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageChildBackwardTopLeftLength;
currentStateStartProgress = STAGE_CHILD_PRE_BACKWARD_TOP_LEFT;
currentStateEndProgress = STAGE_CHILD_BACKWARD_TOP_LEFT;
}
if (input > STAGE_CHILD_BACKWARD_TOP_LEFT) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageChildForwardBottomLeftLength;
currentStateStartProgress = STAGE_CHILD_BACKWARD_TOP_LEFT;
currentStateEndProgress = STAGE_CHILD_FORWARD_BOTTOM_LEFT;
}
if (input > STAGE_CHILD_FORWARD_BOTTOM_LEFT) {
currentStartDistance += currentStageDistance;
currentStageDistance = mStageChildBackwardBottomLeftLength;
currentStateStartProgress = STAGE_CHILD_FORWARD_BOTTOM_LEFT;
currentStateEndProgress = STAGE_CHILD_BACKWARD_BOTTOM_LEFT;
}
if (input > STAGE_CHILD_BACKWARD_BOTTOM_LEFT) {
return currentStartDistance + currentStageDistance;
}
return currentStartDistance + (input - currentStateStartProgress) /
(currentStateEndProgress - currentStateStartProgress) * currentStageDistance;
}
private Path createMotherMovePath() {
Path path = new Path();
float centerX = mCurrentBounds.centerX();
float centerY = mCurrentBounds.centerY();
float currentPathLength = 0.0f;
path.moveTo(centerX, centerY);
//forward top left
path.quadTo(centerX - mMotherOvalHalfWidth * 2.0f, centerY,
centerX - mMotherOvalHalfWidth * 2.0f, centerY - mMotherOvalHalfHeight);
mStageMotherForwardTopLeftLength = getRestLength(path, currentPathLength);
currentPathLength += mStageMotherForwardTopLeftLength;
//backward top left
path.quadTo(centerX - mMotherOvalHalfWidth * 1.0f, centerY - mMotherOvalHalfHeight,
centerX, centerY);
mStageMotherBackwardTopLeftLength = getRestLength(path, currentPathLength);
currentPathLength += mStageMotherBackwardTopLeftLength;
//forward bottom left
path.quadTo(centerX, centerY + mMotherOvalHalfHeight,
centerX - mMotherOvalHalfWidth / 2, centerY + mMotherOvalHalfHeight * 1.1f);
mStageMotherForwardBottomLeftLength = getRestLength(path, currentPathLength);
currentPathLength += mStageMotherForwardBottomLeftLength;
//backward bottom left
path.quadTo(centerX - mMotherOvalHalfWidth / 2, centerY + mMotherOvalHalfHeight * 0.6f,
centerX, centerY);
mStageMotherBackwardBottomLeftLength = getRestLength(path, currentPathLength);
return path;
}
private Path createChildMovePath() {
Path path = new Path();
float centerX = mCurrentBounds.centerX();
float centerY = mCurrentBounds.centerY();
float currentPathLength = 0.0f;
//start
path.moveTo(centerX, centerY);
//pre forward top left
path.lineTo(centerX + mMotherOvalHalfWidth * 0.75f, centerY);
mStageChildPreForwardTopLeftLength = getRestLength(path, currentPathLength);
currentPathLength += mStageChildPreForwardTopLeftLength;
//forward top left
path.quadTo(centerX - mMotherOvalHalfWidth * 0.5f, centerY,
centerX - mMotherOvalHalfWidth * 2.0f, centerY - mMotherOvalHalfHeight);
mStageChildForwardTopLeftLength = getRestLength(path, currentPathLength);
currentPathLength += mStageChildForwardTopLeftLength;
//pre backward top left
path.lineTo(centerX - mMotherOvalHalfWidth * 2.0f + mMotherOvalHalfWidth * 0.2f, centerY - mMotherOvalHalfHeight);
path.quadTo(centerX - mMotherOvalHalfWidth * 2.5f, centerY - mMotherOvalHalfHeight * 2,
centerX - mMotherOvalHalfWidth * 1.5f, centerY - mMotherOvalHalfHeight * 2.25f);
mStageChildPreBackwardTopLeftLength = getRestLength(path, currentPathLength);
currentPathLength += mStageChildPreBackwardTopLeftLength;
//backward top left
path.quadTo(centerX - mMotherOvalHalfWidth * 0.2f, centerY - mMotherOvalHalfHeight * 2.25f,
centerX, centerY);
mStageChildBackwardTopLeftLength = getRestLength(path, currentPathLength);
currentPathLength += mStageChildBackwardTopLeftLength;
//forward bottom left
path.cubicTo(centerX, centerY + mMotherOvalHalfHeight,
centerX - mMotherOvalHalfWidth, centerY + mMotherOvalHalfHeight * 2.5f,
centerX - mMotherOvalHalfWidth * 1.5f, centerY + mMotherOvalHalfHeight * 2.5f);
mStageChildForwardBottomLeftLength = getRestLength(path, currentPathLength);
currentPathLength += mStageChildForwardBottomLeftLength;
//backward bottom left
path.cubicTo(
centerX - mMotherOvalHalfWidth * 2.0f, centerY + mMotherOvalHalfHeight * 2.5f,
centerX - mMotherOvalHalfWidth * 3.0f, centerY + mMotherOvalHalfHeight * 0.8f,
centerX, centerY);
mStageChildBackwardBottomLeftLength = getRestLength(path, currentPathLength);
return path;
}
private int getCurrentRevealCircleRadius(float input) {
int result = 0;
if (input > 0.44f && input < 0.48f) {
result = (int) ((input - 0.44f) / 0.04f * mMaxRevealCircleRadius);
}
if (input > 0.81f && input < 0.85f) {
result = (int) ((input - 0.81f) / 0.04f * mMaxRevealCircleRadius);
}
return result;
}
private int getCurrentBackgroundColor(float input) {
return input < 0.48f || input > 0.85f ? mBackgroundColor : mBackgroundDeepColor;
}
private int getCurrentOvalColor(float input) {
int result;
if (input < 0.5f) {
result = mOvalColor;
} else if (input < 0.75f) {
float colorProgress = (input - 0.5f) / 0.2f;
result = evaluateColorChange(colorProgress, mOvalColor, mOvalDeepColor);
} else if (input < 0.85f) {
result = mOvalDeepColor;
} else {
float colorProgress = (input - 0.9f) / 0.1f;
result = evaluateColorChange(colorProgress, mOvalDeepColor, mOvalColor);
}
return result;
}
private int evaluateColorChange(float fraction, int startValue, int endValue) {
int startA = (startValue >> 24) & 0xff;
int startR = (startValue >> 16) & 0xff;
int startG = (startValue >> 8) & 0xff;
int startB = startValue & 0xff;
int endA = (endValue >> 24) & 0xff;
int endR = (endValue >> 16) & 0xff;
int endG = (endValue >> 8) & 0xff;
int endB = endValue & 0xff;
return ((startA + (int) (fraction * (endA - startA))) << 24) |
((startR + (int) (fraction * (endR - startR))) << 16) |
((startG + (int) (fraction * (endG - startG))) << 8) |
((startB + (int) (fraction * (endB - startB))));
}
private float getRestLength(Path path, float startD) {
Path tempPath = new Path();
PathMeasure pathMeasure = new PathMeasure(path, false);
pathMeasure.getSegment(startD, pathMeasure.getLength(), tempPath, true);
pathMeasure.setPath(tempPath, false);
return pathMeasure.getLength();
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
}
private class MotherMoveInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
float result;
if (input <= STAGE_MOTHER_FORWARD_TOP_LEFT) {
result = ACCELERATE_INTERPOLATOR10.getInterpolation(input * 2.941f) / 2.941f;
} else if (input <= STAGE_MOTHER_BACKWARD_TOP_LEFT) {
result = 0.34f + DECELERATE_INTERPOLATOR10.getInterpolation((input - 0.34f) * 6.25f) / 6.25f;
} else if (input <= STAGE_MOTHER_FORWARD_BOTTOM_LEFT) {
result = 0.5f + ACCELERATE_INTERPOLATOR03.getInterpolation((input - 0.5f) * 6.666f) / 4.0f;
} else if (input <= STAGE_MOTHER_BACKWARD_BOTTOM_LEFT) {
result = 0.75f + DECELERATE_INTERPOLATOR03.getInterpolation((input - 0.65f) * 5.46f) / 4.0f;
} else {
result = 1.0f;
}
return result;
}
}
private class ChildMoveInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
float result;
if (input < STAGE_CHILD_DELAY) {
return 0.0f;
} else if (input <= STAGE_CHILD_PRE_FORWARD_TOP_LEFT) {
result = DECELERATE_INTERPOLATOR10.getInterpolation((input - 0.1f) * 6.25f) / 3.846f;
} else if (input <= STAGE_CHILD_FORWARD_TOP_LEFT) {
result = 0.26f + ACCELERATE_INTERPOLATOR10.getInterpolation((input - 0.26f) * 12.5f) / 12.5f;
} else if (input <= STAGE_CHILD_PRE_BACKWARD_TOP_LEFT) {
result = 0.34f + DECELERATE_INTERPOLATOR08.getInterpolation((input - 0.34f) * 12.5f) / 12.5f;
} else if (input <= STAGE_CHILD_BACKWARD_TOP_LEFT) {
result = 0.42f + ACCELERATE_INTERPOLATOR08.getInterpolation((input - 0.42f) * 12.5f) / 12.5f;
} else if (input <= STAGE_CHILD_FORWARD_BOTTOM_LEFT) {
result = 0.5f + DECELERATE_INTERPOLATOR05.getInterpolation((input - 0.5f) * 5.0f) / 5.0f;
} else if (input <= STAGE_CHILD_BACKWARD_BOTTOM_LEFT) {
result = 0.7f + ACCELERATE_INTERPOLATOR05.getInterpolation((input - 0.7f) * 5.0f) / 3.33f;
} else {
result = 1.0f;
}
return result;
}
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public CircleBroodLoadingRenderer build() {
CircleBroodLoadingRenderer loadingRenderer = new CircleBroodLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

View File

@@ -0,0 +1,270 @@
package app.dinus.com.loadingdrawable.render.shapechange;
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.PathMeasure;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.DisplayMetrics;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import app.dinus.com.loadingdrawable.DensityUtil;
import app.dinus.com.loadingdrawable.render.LoadingRenderer;
public class CoolWaitLoadingRenderer extends LoadingRenderer {
private final Interpolator ACCELERATE_INTERPOLATOR08 = new AccelerateInterpolator(0.8f);
private final Interpolator ACCELERATE_INTERPOLATOR10 = new AccelerateInterpolator(1.0f);
private final Interpolator ACCELERATE_INTERPOLATOR15 = new AccelerateInterpolator(1.5f);
private final Interpolator DECELERATE_INTERPOLATOR03 = new DecelerateInterpolator(0.3f);
private final Interpolator DECELERATE_INTERPOLATOR05 = new DecelerateInterpolator(0.5f);
private final Interpolator DECELERATE_INTERPOLATOR08 = new DecelerateInterpolator(0.8f);
private final Interpolator DECELERATE_INTERPOLATOR10 = new DecelerateInterpolator(1.0f);
private static final Interpolator ACCELERATE_DECELERATE_INTERPOLATOR = new AccelerateDecelerateInterpolator();
private final float DEFAULT_WIDTH = 200.0f;
private final float DEFAULT_HEIGHT = 150.0f;
private final float DEFAULT_STROKE_WIDTH = 8.0f;
private final float WAIT_CIRCLE_RADIUS = 50.0f;
private static final float WAIT_TRIM_DURATION_OFFSET = 0.5f;
private static final float END_TRIM_DURATION_OFFSET = 1.0f;
private final long ANIMATION_DURATION = 2222;
private final Paint mPaint = new Paint();
private final Path mWaitPath = new Path();
private final Path mCurrentTopWaitPath = new Path();
private final Path mCurrentMiddleWaitPath = new Path();
private final Path mCurrentBottomWaitPath = new Path();
private final PathMeasure mWaitPathMeasure = new PathMeasure();
private final RectF mCurrentBounds = new RectF();
private float mStrokeWidth;
private float mWaitCircleRadius;
private float mOriginEndDistance;
private float mOriginStartDistance;
private float mWaitPathLength;
private int mTopColor;
private int mMiddleColor;
private int mBottomColor;
private CoolWaitLoadingRenderer(Context context) {
super(context);
init(context);
setupPaint();
}
private void init(Context context) {
mWidth = DensityUtil.dip2px(context, DEFAULT_WIDTH);
mHeight = DensityUtil.dip2px(context, DEFAULT_HEIGHT);
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mWaitCircleRadius = DensityUtil.dip2px(context, WAIT_CIRCLE_RADIUS);
mTopColor = Color.WHITE;
mMiddleColor = Color.parseColor("#FFF3C742");
mBottomColor = Color.parseColor("#FF89CC59");
mDuration = ANIMATION_DURATION;
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void draw(Canvas canvas, Rect bounds) {
int saveCount = canvas.save();
RectF arcBounds = mCurrentBounds;
arcBounds.set(bounds);
mPaint.setColor(mBottomColor);
canvas.drawPath(mCurrentBottomWaitPath, mPaint);
mPaint.setColor(mMiddleColor);
canvas.drawPath(mCurrentMiddleWaitPath, mPaint);
mPaint.setColor(mTopColor);
canvas.drawPath(mCurrentTopWaitPath, mPaint);
canvas.restoreToCount(saveCount);
}
private Path createWaitPath(RectF bounds) {
Path path = new Path();
//create circle
path.moveTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY());
//create w
path.cubicTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius * 0.5f,
bounds.centerX() + mWaitCircleRadius * 0.3f, bounds.centerY() - mWaitCircleRadius,
bounds.centerX() - mWaitCircleRadius * 0.35f, bounds.centerY() + mWaitCircleRadius * 0.5f);
path.quadTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius,
bounds.centerX() + mWaitCircleRadius * 0.05f, bounds.centerY() + mWaitCircleRadius * 0.5f);
path.lineTo(bounds.centerX() + mWaitCircleRadius * 0.75f, bounds.centerY() - mWaitCircleRadius * 0.2f);
path.cubicTo(bounds.centerX(), bounds.centerY() + mWaitCircleRadius * 1f,
bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius * 0.4f,
bounds.centerX() + mWaitCircleRadius, bounds.centerY());
//create arc
path.arcTo(new RectF(bounds.centerX() - mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius,
bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius), 0, -359);
path.arcTo(new RectF(bounds.centerX() - mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius,
bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius), 1, -359);
path.arcTo(new RectF(bounds.centerX() - mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius,
bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius), 2, -2);
//create w
path.cubicTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius * 0.5f,
bounds.centerX() + mWaitCircleRadius * 0.3f, bounds.centerY() - mWaitCircleRadius,
bounds.centerX() - mWaitCircleRadius * 0.35f, bounds.centerY() + mWaitCircleRadius * 0.5f);
path.quadTo(bounds.centerX() + mWaitCircleRadius, bounds.centerY() - mWaitCircleRadius,
bounds.centerX() + mWaitCircleRadius * 0.05f, bounds.centerY() + mWaitCircleRadius * 0.5f);
path.lineTo(bounds.centerX() + mWaitCircleRadius * 0.75f, bounds.centerY() - mWaitCircleRadius * 0.2f);
path.cubicTo(bounds.centerX(), bounds.centerY() + mWaitCircleRadius * 1f,
bounds.centerX() + mWaitCircleRadius, bounds.centerY() + mWaitCircleRadius * 0.4f,
bounds.centerX() + mWaitCircleRadius, bounds.centerY());
return path;
}
@Override
protected void computeRender(float renderProgress) {
if (mCurrentBounds.isEmpty()) {
return;
}
if (mWaitPath.isEmpty()) {
mWaitPath.set(createWaitPath(mCurrentBounds));
mWaitPathMeasure.setPath(mWaitPath, false);
mWaitPathLength = mWaitPathMeasure.getLength();
mOriginEndDistance = mWaitPathLength * 0.255f;
mOriginStartDistance = mWaitPathLength * 0.045f;
}
mCurrentTopWaitPath.reset();
mCurrentMiddleWaitPath.reset();
mCurrentBottomWaitPath.reset();
//draw the first half : top
if (renderProgress <= WAIT_TRIM_DURATION_OFFSET) {
float topTrimProgress = ACCELERATE_DECELERATE_INTERPOLATOR.getInterpolation(renderProgress / WAIT_TRIM_DURATION_OFFSET);
float topEndDistance = mOriginEndDistance + mWaitPathLength * 0.3f * topTrimProgress;
float topStartDistance = mOriginStartDistance + mWaitPathLength * 0.48f * topTrimProgress;
mWaitPathMeasure.getSegment(topStartDistance, topEndDistance, mCurrentTopWaitPath, true);
}
//draw the first half : middle
if (renderProgress > 0.02f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= WAIT_TRIM_DURATION_OFFSET * 0.75f) {
float middleStartTrimProgress = ACCELERATE_INTERPOLATOR10.getInterpolation((renderProgress - 0.02f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.73f));
float middleEndTrimProgress = DECELERATE_INTERPOLATOR08.getInterpolation((renderProgress - 0.02f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.73f));
float middleEndDistance = mOriginStartDistance + mWaitPathLength * 0.42f * middleEndTrimProgress;
float middleStartDistance = mOriginStartDistance + mWaitPathLength * 0.42f * middleStartTrimProgress;
mWaitPathMeasure.getSegment(middleStartDistance, middleEndDistance, mCurrentMiddleWaitPath, true);
}
//draw the first half : bottom
if (renderProgress > 0.04f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= WAIT_TRIM_DURATION_OFFSET * 0.75f) {
float bottomStartTrimProgress = ACCELERATE_INTERPOLATOR15.getInterpolation((renderProgress - 0.04f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.71f));
float bottomEndTrimProgress = DECELERATE_INTERPOLATOR05.getInterpolation((renderProgress - 0.04f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.71f));
float bottomEndDistance = mOriginStartDistance + mWaitPathLength * 0.42f * bottomEndTrimProgress;
float bottomStartDistance = mOriginStartDistance + mWaitPathLength * 0.42f * bottomStartTrimProgress;
mWaitPathMeasure.getSegment(bottomStartDistance, bottomEndDistance, mCurrentBottomWaitPath, true);
}
//draw the last half : top
if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > WAIT_TRIM_DURATION_OFFSET) {
float trimProgress = ACCELERATE_DECELERATE_INTERPOLATOR.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - WAIT_TRIM_DURATION_OFFSET));
float topEndDistance = mOriginEndDistance + mWaitPathLength * 0.3f + mWaitPathLength * 0.45f * trimProgress;
float topStartDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.27f * trimProgress;
mWaitPathMeasure.getSegment(topStartDistance, topEndDistance, mCurrentTopWaitPath, true);
}
//draw the last half : middle
if (renderProgress > WAIT_TRIM_DURATION_OFFSET + 0.02f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= WAIT_TRIM_DURATION_OFFSET + WAIT_TRIM_DURATION_OFFSET * 0.62f) {
float middleStartTrimProgress = ACCELERATE_INTERPOLATOR08.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.02f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.60f));
float middleEndTrimProgress = DECELERATE_INTERPOLATOR03.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.02f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.60f));
float middleEndDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.20f * middleEndTrimProgress;
float middleStartDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.10f * middleStartTrimProgress;
mWaitPathMeasure.getSegment(middleStartDistance, middleEndDistance, mCurrentMiddleWaitPath, true);
}
if (renderProgress > WAIT_TRIM_DURATION_OFFSET + 0.62f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= END_TRIM_DURATION_OFFSET) {
float middleStartTrimProgress = DECELERATE_INTERPOLATOR10.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.62f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.38f));
float middleEndTrimProgress = DECELERATE_INTERPOLATOR03.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.62f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.38f));
float middleEndDistance = mOriginStartDistance + mWaitPathLength * 0.68f + mWaitPathLength * 0.325f * middleEndTrimProgress;
float middleStartDistance = mOriginStartDistance + mWaitPathLength * 0.58f + mWaitPathLength * 0.17f * middleStartTrimProgress;
mWaitPathMeasure.getSegment(middleStartDistance, middleEndDistance, mCurrentMiddleWaitPath, true);
}
//draw the last half : bottom
if (renderProgress > WAIT_TRIM_DURATION_OFFSET + 0.10f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= WAIT_TRIM_DURATION_OFFSET + WAIT_TRIM_DURATION_OFFSET * 0.70f) {
float bottomStartTrimProgress = ACCELERATE_INTERPOLATOR15.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.10f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.60f));
float bottomEndTrimProgress = DECELERATE_INTERPOLATOR03.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.10f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.60f));
float bottomEndDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.20f * bottomEndTrimProgress;
float bottomStartDistance = mOriginStartDistance + mWaitPathLength * 0.48f + mWaitPathLength * 0.10f * bottomStartTrimProgress;
mWaitPathMeasure.getSegment(bottomStartDistance, bottomEndDistance, mCurrentBottomWaitPath, true);
}
if (renderProgress > WAIT_TRIM_DURATION_OFFSET + 0.70f * WAIT_TRIM_DURATION_OFFSET && renderProgress <= END_TRIM_DURATION_OFFSET) {
float bottomStartTrimProgress = DECELERATE_INTERPOLATOR05.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.70f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.30f));
float bottomEndTrimProgress = DECELERATE_INTERPOLATOR03.getInterpolation((renderProgress - WAIT_TRIM_DURATION_OFFSET - 0.70f * WAIT_TRIM_DURATION_OFFSET) / (WAIT_TRIM_DURATION_OFFSET * 0.30f));
float bottomEndDistance = mOriginStartDistance + mWaitPathLength * 0.68f + mWaitPathLength * 0.325f * bottomEndTrimProgress;
float bottomStartDistance = mOriginStartDistance + mWaitPathLength * 0.58f + mWaitPathLength * 0.17f * bottomStartTrimProgress;
mWaitPathMeasure.getSegment(bottomStartDistance, bottomEndDistance, mCurrentBottomWaitPath, true);
}
}
@Override
protected void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
}
public static class Builder {
private Context mContext;
public Builder(Context mContext) {
this.mContext = mContext;
}
public CoolWaitLoadingRenderer build() {
CoolWaitLoadingRenderer loadingRenderer = new CoolWaitLoadingRenderer(mContext);
return loadingRenderer;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LoadingView">
<attr name="loading_renderer">
<!--circle rotate-->
<enum name="MaterialLoadingRenderer" value="0"/>
<enum name="LevelLoadingRenderer" value="1"/>
<enum name="WhorlLoadingRenderer" value="2"/>
<enum name="GearLoadingRenderer" value="3"/>
<!--circle jump-->
<enum name="SwapLoadingRenderer" value="4"/>
<enum name="GuardLoadingRenderer" value="5"/>
<enum name="DanceLoadingRenderer" value="6"/>
<enum name="CollisionLoadingRenderer" value="7"/>
<!--Scenery-->
<enum name="DayNightLoadingRenderer" value="8"/>
<enum name="ElectricFanLoadingRenderer" value="9"/>
<!--Animal-->
<enum name="FishLoadingRenderer" value="10"/>
<enum name="GhostsEyeLoadingRenderer" value="11"/>
<!--Goods-->
<enum name="BalloonLoadingRenderer" value="12"/>
<enum name="WaterBottleLoadingRenderer" value="13"/>
<!--ShapeChange-->
<enum name="CircleBroodLoadingRenderer" value="14"/>
<enum name="CoolWaitLoadingRenderer" value="15"/>
</attr>
</declare-styleable>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Library</string>
</resources>