1:修改K歌房
2:修改房间展示每日任务 3:修改页面跳转 4:遗留问题在进入首页的时候出现首页刷新
This commit is contained in:
25
Loadinglibrary/build.gradle
Normal file
25
Loadinglibrary/build.gradle
Normal 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
17
Loadinglibrary/proguard-rules.pro
vendored
Normal 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 *;
|
||||
#}
|
||||
8
Loadinglibrary/src/main/AndroidManifest.xml
Normal file
8
Loadinglibrary/src/main/AndroidManifest.xml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Loadinglibrary/src/main/res/drawable-xhdpi/ic_eletric_fan.png
Normal file
BIN
Loadinglibrary/src/main/res/drawable-xhdpi/ic_eletric_fan.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1018 B |
BIN
Loadinglibrary/src/main/res/drawable-xhdpi/ic_leaf.png
Normal file
BIN
Loadinglibrary/src/main/res/drawable-xhdpi/ic_leaf.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 268 B |
BIN
Loadinglibrary/src/main/res/drawable-xhdpi/ic_loading.png
Normal file
BIN
Loadinglibrary/src/main/res/drawable-xhdpi/ic_loading.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 277 B |
29
Loadinglibrary/src/main/res/values/attrs.xml
Normal file
29
Loadinglibrary/src/main/res/values/attrs.xml
Normal 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>
|
||||
3
Loadinglibrary/src/main/res/values/strings.xml
Normal file
3
Loadinglibrary/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Library</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user