diff --git a/Loadinglibrary/build.gradle b/Loadinglibrary/build.gradle new file mode 100644 index 00000000..cb5850f3 --- /dev/null +++ b/Loadinglibrary/build.gradle @@ -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 +} diff --git a/Loadinglibrary/proguard-rules.pro b/Loadinglibrary/proguard-rules.pro new file mode 100644 index 00000000..290d4c09 --- /dev/null +++ b/Loadinglibrary/proguard-rules.pro @@ -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 *; +#} diff --git a/Loadinglibrary/src/main/AndroidManifest.xml b/Loadinglibrary/src/main/AndroidManifest.xml new file mode 100644 index 00000000..3e848d82 --- /dev/null +++ b/Loadinglibrary/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/DensityUtil.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/DensityUtil.java new file mode 100644 index 00000000..c88e7895 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/DensityUtil.java @@ -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; + } +} \ No newline at end of file diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/LoadingView.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/LoadingView.java new file mode 100644 index 00000000..ba77e642 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/LoadingView.java @@ -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(); + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/LoadingDrawable.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/LoadingDrawable.java new file mode 100644 index 00000000..0c30ed64 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/LoadingDrawable.java @@ -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; + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/LoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/LoadingRenderer.java new file mode 100644 index 00000000..fe0282b5 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/LoadingRenderer.java @@ -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); + } + +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/LoadingRendererFactory.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/LoadingRendererFactory.java new file mode 100644 index 00000000..59040b06 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/LoadingRendererFactory.java @@ -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> 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(); + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/animal/FishLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/animal/FishLoadingRenderer.java new file mode 100644 index 00000000..2c63d48e --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/animal/FishLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/animal/GhostsEyeLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/animal/GhostsEyeLoadingRenderer.java new file mode 100644 index 00000000..b13b2a86 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/animal/GhostsEyeLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/CollisionLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/CollisionLoadingRenderer.java new file mode 100644 index 00000000..9e49dcca --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/CollisionLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/DanceLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/DanceLoadingRenderer.java new file mode 100644 index 00000000..aa5291d3 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/DanceLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/GuardLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/GuardLoadingRenderer.java new file mode 100644 index 00000000..32cac092 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/GuardLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/SwapLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/SwapLoadingRenderer.java new file mode 100644 index 00000000..c759723d --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/jump/SwapLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/GearLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/GearLoadingRenderer.java new file mode 100644 index 00000000..58274f54 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/GearLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/LevelLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/LevelLoadingRenderer.java new file mode 100644 index 00000000..2e740073 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/LevelLoadingRenderer.java @@ -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; + } + } + +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/MaterialLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/MaterialLoadingRenderer.java new file mode 100644 index 00000000..700bab9c --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/MaterialLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/WhorlLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/WhorlLoadingRenderer.java new file mode 100644 index 00000000..49c1d667 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/circle/rotate/WhorlLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/goods/BalloonLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/goods/BalloonLoadingRenderer.java new file mode 100644 index 00000000..269ecacf --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/goods/BalloonLoadingRenderer.java @@ -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; + } + } +} \ No newline at end of file diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/goods/WaterBottleLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/goods/WaterBottleLoadingRenderer.java new file mode 100644 index 00000000..ba5a7b59 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/goods/WaterBottleLoadingRenderer.java @@ -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 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; + } + } +} \ No newline at end of file diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/scenery/DayNightLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/scenery/DayNightLoadingRenderer.java new file mode 100644 index 00000000..9d57c8f0 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/scenery/DayNightLoadingRenderer.java @@ -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 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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/scenery/ElectricFanLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/scenery/ElectricFanLoadingRenderer.java new file mode 100644 index 00000000..dcd02d96 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/scenery/ElectricFanLoadingRenderer.java @@ -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 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 { + + 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; + } + } +} diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/shapechange/CircleBroodLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/shapechange/CircleBroodLoadingRenderer.java new file mode 100644 index 00000000..03043416 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/shapechange/CircleBroodLoadingRenderer.java @@ -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; + } + } +} \ No newline at end of file diff --git a/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/shapechange/CoolWaitLoadingRenderer.java b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/shapechange/CoolWaitLoadingRenderer.java new file mode 100644 index 00000000..a404e786 --- /dev/null +++ b/Loadinglibrary/src/main/java/app/dinus/com/loadingdrawable/render/shapechange/CoolWaitLoadingRenderer.java @@ -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; + } + } +} diff --git a/Loadinglibrary/src/main/res/drawable-xhdpi/ic_eletric_fan.png b/Loadinglibrary/src/main/res/drawable-xhdpi/ic_eletric_fan.png new file mode 100644 index 00000000..671be789 Binary files /dev/null and b/Loadinglibrary/src/main/res/drawable-xhdpi/ic_eletric_fan.png differ diff --git a/Loadinglibrary/src/main/res/drawable-xhdpi/ic_leaf.png b/Loadinglibrary/src/main/res/drawable-xhdpi/ic_leaf.png new file mode 100644 index 00000000..d2e62924 Binary files /dev/null and b/Loadinglibrary/src/main/res/drawable-xhdpi/ic_leaf.png differ diff --git a/Loadinglibrary/src/main/res/drawable-xhdpi/ic_loading.png b/Loadinglibrary/src/main/res/drawable-xhdpi/ic_loading.png new file mode 100644 index 00000000..a87f7803 Binary files /dev/null and b/Loadinglibrary/src/main/res/drawable-xhdpi/ic_loading.png differ diff --git a/Loadinglibrary/src/main/res/values/attrs.xml b/Loadinglibrary/src/main/res/values/attrs.xml new file mode 100644 index 00000000..540c1491 --- /dev/null +++ b/Loadinglibrary/src/main/res/values/attrs.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Loadinglibrary/src/main/res/values/strings.xml b/Loadinglibrary/src/main/res/values/strings.xml new file mode 100644 index 00000000..6d518563 --- /dev/null +++ b/Loadinglibrary/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Library + diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 30daf5d0..d98ee460 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -945,3 +945,9 @@ public static java.lang.String TABLENAME; -keep class **.R$string { *; } +-keep class com.qcloud.cos.** { *; } +-dontwarn com.qcloud.cos.** +-keep class com.qcloud.cos.model.** { *; } +-keep class * implements com.qcloud.cos.** { *; } +-keepattributes Signature, *Annotation* +-keep class com.qcloud.cos.**$* { *; } diff --git a/app/releas/release/output-metadata.json b/app/releas/release/output-metadata.json index fc35392e..e350501c 100644 --- a/app/releas/release/output-metadata.json +++ b/app/releas/release/output-metadata.json @@ -11,9 +11,9 @@ "type": "SINGLE", "filters": [], "attributes": [], - "versionCode": 40, - "versionName": "1.0.5.2", - "outputFile": "羽声_1.0.5.2_40.apk" + "versionCode": 43, + "versionName": "1.0.5.3", + "outputFile": "羽声_1.0.5.3_43.apk" } ], "elementType": "File", @@ -22,14 +22,14 @@ "minApi": 28, "maxApi": 30, "baselineProfiles": [ - "baselineProfiles/1/羽声_1.0.5.2_40.dm" + "baselineProfiles/1/羽声_1.0.5.3_43.dm" ] }, { "minApi": 31, "maxApi": 2147483647, "baselineProfiles": [ - "baselineProfiles/0/羽声_1.0.5.2_40.dm" + "baselineProfiles/0/羽声_1.0.5.3_43.dm" ] } ], diff --git a/app/release/baselineProfiles/0/秘地_1.0.5_101.dm b/app/release/baselineProfiles/0/秘地_1.0.5_101.dm deleted file mode 100644 index 1451fef3..00000000 Binary files a/app/release/baselineProfiles/0/秘地_1.0.5_101.dm and /dev/null differ diff --git a/app/release/baselineProfiles/1/秘地_1.0.5_101.dm b/app/release/baselineProfiles/1/秘地_1.0.5_101.dm deleted file mode 100644 index b10faf54..00000000 Binary files a/app/release/baselineProfiles/1/秘地_1.0.5_101.dm and /dev/null differ diff --git a/app/release/秘地_1.0.5_101.apk b/app/release/秘地_1.0.5_101.apk deleted file mode 100644 index 210e2737..00000000 Binary files a/app/release/秘地_1.0.5_101.apk and /dev/null differ diff --git a/gradle.properties b/gradle.properties index 7cfdcb11..90b7bb32 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,8 +28,8 @@ isBuildModule=false #org.gradle.deamon=false android.injected.testOnly=false -APP_VERSION_NAME=1.0.5.2 -APP_VERSION_CODE=40 +APP_VERSION_NAME=1.0.5.3 +APP_VERSION_CODE=43 org.gradle.jvm.toolchain.useLegacyAdapters=false #org.gradle.java.home=C\:\\Users\\qx\\.jdks\\ms-17.0.15 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a0b83414..16a6b757 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -78,6 +78,7 @@ appcompatVersion = "1.3.1" legacySupportV4 = "1.0.0" fragmentKtx = "1.5.6" cosAndroidLite = "5.9.46" +interpolator = "1.0.0" [libraries] alipay-alipaysdk-android = { module = "com.alipay.sdk:alipaysdk-android", version.ref = "alipayAlipaysdkAndroid" } @@ -162,6 +163,7 @@ zcw-togglebutton-library = { module = "com.zcw:togglebutton-library", version.re androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompatVersion" } androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" } androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" } +androidx-interpolator = { group = "androidx.interpolator", name = "interpolator", version.ref = "interpolator" } [plugins] diff --git a/moduleLogin/src/main/java/com/xscm/modulelogin/present/ImproveInfoPresenter.java b/moduleLogin/src/main/java/com/xscm/modulelogin/present/ImproveInfoPresenter.java index bb428bb8..1245787b 100644 --- a/moduleLogin/src/main/java/com/xscm/modulelogin/present/ImproveInfoPresenter.java +++ b/moduleLogin/src/main/java/com/xscm/modulelogin/present/ImproveInfoPresenter.java @@ -4,6 +4,7 @@ import android.content.Context; import com.hjq.toast.ToastUtils; +import com.xscm.moduleutil.base.CommonAppContext; import com.xscm.moduleutil.bean.UserBean; import com.xscm.moduleutil.http.BaseObserver; import com.xscm.moduleutil.presenter.BasePresenter; @@ -26,7 +27,7 @@ public class ImproveInfoPresenter extends BasePresenter extends // 这里可以根据实际需求实现跳转逻辑 // 例如:跳转到礼物详情页面、用户主页等 // 使用缓存数据进入房间 - RoomManager.getInstance().fetchRoomDataAndEnter(getApplicationContext(), xlhBean.getRoom_id(), ""); + RoomManager.getInstance().fetchRoomDataAndEnter(getApplicationContext(), xlhBean.getRoom_id(), "",null); // ARouter.getInstance().build(ARouteConstants.ROOM_DETAILS).withString("from", "我的界面").withString("roomId", xlhBean.getRoom_id()).navigation(); } @@ -1166,7 +1166,7 @@ public abstract class BaseAppCompatActivity extends // 这里可以根据实际需求实现跳转逻辑 // 例如:跳转到礼物详情页面、用户主页等 // 使用缓存数据进入房间 - RoomManager.getInstance().fetchRoomDataAndEnter(getApplicationContext(), redBean.getRoom_id(), ""); + RoomManager.getInstance().fetchRoomDataAndEnter(getApplicationContext(), redBean.getRoom_id(), "",null); // ARouter.getInstance().build(ARouteConstants.ROOM_DETAILS).withString("from", "我的界面").withString("roomId", xlhBean.getRoom_id()).navigation(); } diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/activity/WebViewActivity.java b/moduleUtil/src/main/java/com/xscm/moduleutil/activity/WebViewActivity.java index 934dafae..ea4a4f46 100644 --- a/moduleUtil/src/main/java/com/xscm/moduleutil/activity/WebViewActivity.java +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/activity/WebViewActivity.java @@ -390,7 +390,7 @@ public class WebViewActivity extends BaseAppCompatActivity0){ - RoomManager.getInstance().fetchRoomDataAndEnter(getApplicationContext(), item.getRoom_id()+"",""); + RoomManager.getInstance().fetchRoomDataAndEnter(getApplicationContext(), item.getRoom_id()+"","",null); // ARouter.getInstance().build(ARouteConstants.ROOM_DETAILS).withString("roomId", item.getRoom_id() + "").navigation(); }else if (item.getRoom_id() == 0 && item.getUrl() != null && !item.getUrl().isEmpty()){ Intent intent = new Intent(OfficialNoticeActivity.this, WebViewActivity.class); diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/base/CommonAppContext.java b/moduleUtil/src/main/java/com/xscm/moduleutil/base/CommonAppContext.java index 3c2f757b..b74d2880 100644 --- a/moduleUtil/src/main/java/com/xscm/moduleutil/base/CommonAppContext.java +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/base/CommonAppContext.java @@ -459,7 +459,7 @@ public class CommonAppContext extends MultiDexApplication implements Applicatio SpUtil.getInstance().setBooleanValue("youth_model_shown", false); startInitSdk(); // 初始化(通常在Application或Activity的onCreate中) - CosUploadManager.getInstance().init(this); + CosUploadManager.getInstance(CommonAppContext.getInstance()); // 启动IM连接服务 // IMServiceManager.getInstance().startIMService(this); } diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/base/RoomManager.java b/moduleUtil/src/main/java/com/xscm/moduleutil/base/RoomManager.java index e886c63d..c72f432b 100644 --- a/moduleUtil/src/main/java/com/xscm/moduleutil/base/RoomManager.java +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/base/RoomManager.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import io.reactivex.disposables.Disposable; +import org.jetbrains.annotations.NotNull; /** * 房间管理器 @@ -102,10 +103,10 @@ public class RoomManager { if (roomInfo != null) { // 使用缓存数据直接进入房间 - navigateToRoom(context, roomId, password, roomInfo, false); + navigateToRoom(context, roomId, password, roomInfo, false,null); } else { // 获取房间数据后进入房间 - fetchRoomDataAndEnter(context, roomId, password); + fetchRoomDataAndEnter(context, roomId, password,null); } } @@ -116,19 +117,20 @@ public class RoomManager { * @param roomId 房间ID * @param password 房间密码 */ - public void fetchRoomDataAndEnter(Context context, String roomId, String password) { + public void fetchRoomDataAndEnter(Context context, String roomId, String password,String taskId) { // 显示加载提示 // 这里可以根据需要添加加载对话框 if (CommonAppContext.getInstance().isRoomJoininj){ return; } + CommonAppContext.getInstance().isRoomJoininj=true; // 检查是否有有效的缓存数据 RoomInfoResp roomInfo = getCachedRoomData(roomId); // 检查是否是当前房间且用户在线 // boolean isCurrentRoom = isCurrentRoom(roomId); if (CommonAppContext.getInstance().playId == null) { - fetchAndJoinRoom(context, roomId, password); + fetchAndJoinRoom(context, roomId, password,taskId); } else { if (!CommonAppContext.getInstance().playId.equals(roomId)) { MessageListenerSingleton.getInstance().joinGroup(roomId); @@ -137,11 +139,13 @@ public class RoomManager { CommonAppContext.getInstance().isPlaying = false; CommonAppContext.getInstance().isRoomJoininj=false; EventBus.getDefault().post(new RoomOutEvent()); +// fetchAndJoinRoom(context, roomId, password); +// return; } else if (CommonAppContext.getInstance().lable_id.equals("6")) { - upInfo(context, roomId, password, true, roomInfo, true); + upInfo(context, roomId, password, true, roomInfo, true,taskId); return; } - isUserOnline(context, roomId, password, roomInfo); + isUserOnline(context, roomId, password, roomInfo,taskId); } @@ -195,58 +199,26 @@ public class RoomManager { // navigateToRoom(context, roomId, password, null); } - private void upInfo(Context context, String roomId, String password, boolean isOnline, RoomInfoResp roomInfo, boolean isCurrentRoom) { + private void upInfo(Context context, String roomId, String password, boolean isOnline, RoomInfoResp roomInfo, boolean isCurrentRoom,String taskId) { if (isOnline) { - navigateToRoom(context, roomId, password, roomInfo, isOnline); + RetrofitClient.getInstance().postRoomInfo(roomId, new BaseObserver() { + + @Override + public void onSubscribe(@NotNull Disposable disposable) { + + } + + @Override + public void onNext(@NotNull RoomInfoResp roomInfoResp) { + navigateToRoom(context, roomId, password, roomInfoResp, false,taskId); + } + }); +// navigateToRoom(context, roomId, password, roomInfo, isOnline); } else { -// CommonAppContext.getInstance().isShow = false; -// CommonAppContext.getInstance().isPlaying = false; -// EventBus.getDefault().post(new RoomOutEvent()); -// try { -// Thread.sleep(300); -// } catch (InterruptedException e) { -// Thread.currentThread().interrupt(); -// } - fetchAndJoinRoom(context, roomId, password); + fetchAndJoinRoom(context, roomId, password,taskId); } - - -// if (isCurrentRoom&& isOnline) { -// if (roomInfo != null) { -// navigateToRoom(context, roomId, password, roomInfo); -// } else { -// // 即使在线,如果没有缓存数据,也需要获取数据 -// fetchAndJoinRoom(context, roomId, password); -// } -// return; -// } - - - // 如果有缓存数据且用户在线,使用缓存数据进入房间 -// if (roomInfo != null && isOnline) { -// RetrofitClient.getInstance().postRoomInfo(roomId, new BaseObserver() { -// -// @Override -// public void onSubscribe(Disposable d) { -// -// } -// -// @Override -// public void onNext(RoomInfoResp roomInfoResp) { -//// cacheRoomData(roomId, roomInfo); -// navigateToRoom(context, roomId, password, roomInfoResp); -// } -// }); - -// cacheRoomData(roomId, roomInfo); -// navigateToRoom(context, roomId, password, roomInfo); - return; -// } - - // 其他情况,获取新的房间数据并加入房间 -// fetchAndJoinRoom(context, roomId, password); } /** @@ -256,7 +228,7 @@ public class RoomManager { * @param roomId 房间ID * @param password 房间密码 */ - private void fetchAndJoinRoom(Context context, String roomId, String password) { + private void fetchAndJoinRoom(Context context, String roomId, String password,String taskId) { // 获取房间数据 // 等待一段时间确保退出完成 @@ -266,7 +238,6 @@ public class RoomManager { // Thread.currentThread().interrupt(); // } // navigateToRoom(context, roomId, password, null, false); - LogUtils.dTag("RoomActivity", "fetchAndJoinRoom:"+roomId.toString()); RetrofitClient.getInstance().roomGetIn(roomId, password, new BaseObserver() { @Override @@ -292,7 +263,12 @@ public class RoomManager { AgoraManager.getInstance(context) .joinRoom(token, roomId, uid, enableMic, enableJs); cacheRoomData(roomId, resp); - navigateToRoom(context, roomId, password, resp, false); + navigateToRoom(context, roomId, password, resp, false,taskId); + } + + @Override + public void onError(Throwable e) { + super.onError(e); } }); } @@ -328,8 +304,7 @@ public class RoomManager { * @param password 房间密码 * @param roomInfo 房间信息 */ - private void navigateToRoom(Context context, String roomId, String password, RoomInfoResp roomInfo, boolean isOnline) { - LogUtils.dTag("RoomActivity", "navigateToRoom"+roomInfo.toString()); + private void navigateToRoom(Context context, String roomId, String password, RoomInfoResp roomInfo, boolean isOnline,String taskId) { try { // 构建跳转参数 @@ -340,28 +315,23 @@ public class RoomManager { if (!TextUtils.isEmpty(password)) { bundle.putString("password", password); } + if (taskId != null){ + bundle.putString("taskId", taskId); + } if (roomInfo == null){ LogUtils.dTag("RoomActivity", "navigateToRoom:房间信息获取存在问题"); return; } - if (isOnline){ - ARouter.getInstance() - .build(ARouteConstants.ROOM_DETAILS) - - .navigation(context); - }else { // 使用ARouter跳转到房间页面 ARouter.getInstance() .build(ARouteConstants.ROOM_DETAILS) - .with(bundle) .navigation(context); - } - } catch (Exception e) { Logger.e(TAG, "跳转房间页面失败: " + e.getMessage()); + }finally { } } @@ -416,7 +386,7 @@ public class RoomManager { * @param roomId 房间ID * @return true表示用户在线,false表示不在线 */ - private boolean isUserOnline(Context context, String roomId, String password, RoomInfoResp roomInfo) { + private boolean isUserOnline(Context context, String roomId, String password, RoomInfoResp roomInfo,String taskId) { // 这里应该实现检查用户是否在线的逻辑 // 可以通过检查Agora是否还在房间中,或者通过服务端接口查询用户状态等方式实现 // 目前返回false,需要根据实际需求实现具体逻辑 @@ -426,6 +396,9 @@ public class RoomManager { // } catch (InterruptedException e) { // Thread.currentThread().interrupt(); // } + + + final boolean[] isOnline = {false}; RetrofitClient.getInstance().getRoomOnline(roomId, "1", "50", new BaseObserver() { @Override @@ -453,7 +426,8 @@ public class RoomManager { } } } - upInfo(context, roomId, password, isOnline[0], roomInfo, true); + upInfo(context, roomId, password, isOnline[0], roomInfo, true, taskId); + } else { isOnline[0] = false; } @@ -462,11 +436,11 @@ public class RoomManager { e.printStackTrace(); isOnline[0] = false; // 即使出现异常也继续执行 - upInfo(context, roomId, password, isOnline[0], roomInfo, true); + upInfo(context, roomId, password, isOnline[0], roomInfo, true, taskId); } } }); - return isOnline[0]; + return false; } /** diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/dialog/LoadingDialog.java b/moduleUtil/src/main/java/com/xscm/moduleutil/dialog/LoadingDialog.java new file mode 100644 index 00000000..c1e92316 --- /dev/null +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/dialog/LoadingDialog.java @@ -0,0 +1,49 @@ +package com.xscm.moduleutil.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.widget.TextView; +import androidx.annotation.NonNull; +import com.xscm.moduleutil.R; + +/** + * com.xscm.moduleutil.dialog + * qx + * 2025/10/27 + */ +public class LoadingDialog extends Dialog { + + + public LoadingDialog(@NonNull Context context) { + super(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 设置布局(这里假设你有一个R.layout.loading_dialog布局文件) + setContentView(R.layout.loading_dialog); + + // 设置不可取消 + setCancelable(false); + } + + /** + * 显示加载对话框 + */ + public void showLoading() { + if (!isShowing()) { + show(); + } + } + + /** + * 隐藏加载对话框 + */ + public void hideLoading() { + if (isShowing()) { + dismiss(); + } + } +} diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/dialog/RoomAuctionWebViewDialog.java b/moduleUtil/src/main/java/com/xscm/moduleutil/dialog/RoomAuctionWebViewDialog.java index ed1ff088..d737f32d 100644 --- a/moduleUtil/src/main/java/com/xscm/moduleutil/dialog/RoomAuctionWebViewDialog.java +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/dialog/RoomAuctionWebViewDialog.java @@ -178,7 +178,7 @@ public class RoomAuctionWebViewDialog extends BaseDialog { @JavascriptInterface public void jumpRoomPage(String room_id) { - RoomManager.getInstance().fetchRoomDataAndEnter(getContext(), room_id,""); + RoomManager.getInstance().fetchRoomDataAndEnter(getContext(), room_id,"",null); // ARouter.getInstance().build(ARouteConstants.ROOM_DETAILS).withString("form", "首页").withString("roomId", room_id).navigation(); } diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/http/RetrofitClient.java b/moduleUtil/src/main/java/com/xscm/moduleutil/http/RetrofitClient.java index 377fdab2..188129be 100644 --- a/moduleUtil/src/main/java/com/xscm/moduleutil/http/RetrofitClient.java +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/http/RetrofitClient.java @@ -1938,6 +1938,7 @@ public class RetrofitClient { public void onFailure(Call> call, Throwable t) { MessageListenerSingleton.getInstance().quitGroup(roomId); CommonAppContext.getInstance().isRoomJoininj = false; + observer.onError( t); } }); } diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/rtc/AgoraManager.java b/moduleUtil/src/main/java/com/xscm/moduleutil/rtc/AgoraManager.java index c15c17d3..2c69251b 100644 --- a/moduleUtil/src/main/java/com/xscm/moduleutil/rtc/AgoraManager.java +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/rtc/AgoraManager.java @@ -445,6 +445,7 @@ public class AgoraManager { @Override public void onLyricResult(String requestId, long songCode, String lyricUrl, int reason) { if (lyricUrl != null) { + LogUtils.e("roomInfoEvent",lyricUrl); getLyricsInstance(lyricUrl); } } @@ -1449,6 +1450,7 @@ public class AgoraManager { .build(); if (StatusUtil.isCompleted(task)) { MusicFileBean musicFileBean = new MusicFileBean(); + LogUtils.e("roomInfoEvent",convertFileToByteArray(task.getFile())); musicFileBean.setFileData(convertFileToByteArray(task.getFile())); EventBus.getDefault().post(musicFileBean); } else { @@ -1477,6 +1479,7 @@ public class AgoraManager { @Override public void taskEnd(@NonNull DownloadTask task, @NonNull EndCause cause, @Nullable Exception realCause, @NonNull Listener1Assist.Listener1Model model) { Logger.e("taskEnd", model); + LogUtils.e("roomInfoEvent",convertFileToByteArray(task.getFile())); MusicFileBean musicFileBean = new MusicFileBean(); musicFileBean.setFileData(convertFileToByteArray(task.getFile())); EventBus.getDefault().post(musicFileBean); diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/utils/ARouteConstants.java b/moduleUtil/src/main/java/com/xscm/moduleutil/utils/ARouteConstants.java index 3eaf1811..6252437f 100644 --- a/moduleUtil/src/main/java/com/xscm/moduleutil/utils/ARouteConstants.java +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/utils/ARouteConstants.java @@ -26,6 +26,7 @@ public class ARouteConstants { public static final String H5 ="/moduleUtil/WebViewActivity"; //网页 // public static final String GIFT_WALL ="/moduleroom/UserGiftWallFragment"; //实名认证 public static final String RECHARGE_ACTIVITY ="/modulevocal/RechargeActivity"; //实名认证 + public static final String DailyTasksActivity ="/modulevocal/DailyTasksActivity"; //每日任务 diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/utils/cos/CosUploadManager.java b/moduleUtil/src/main/java/com/xscm/moduleutil/utils/cos/CosUploadManager.java index c3d916c1..12a8e9b8 100644 --- a/moduleUtil/src/main/java/com/xscm/moduleutil/utils/cos/CosUploadManager.java +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/utils/cos/CosUploadManager.java @@ -5,6 +5,7 @@ import android.os.Handler; import android.os.Looper; import androidx.annotation.Nullable; import com.blankj.utilcode.util.LogUtils; +import com.hjq.toast.ToastUtils; import com.tencent.cos.xml.CosXmlService; import com.tencent.cos.xml.CosXmlServiceConfig; import com.tencent.cos.xml.exception.CosXmlClientException; @@ -31,33 +32,33 @@ import org.jetbrains.annotations.NotNull; */ public class CosUploadManager { private static volatile CosUploadManager instance; - private Context context; + private Context mContext; private Handler mainHandler; // 私有构造函数,防止外部实例化 - private CosUploadManager() { + private CosUploadManager(Context context) + { + mContext = context; mainHandler = new Handler(Looper.getMainLooper()); } // 双重检查锁定获取单例实例 - public static CosUploadManager getInstance() { + public static CosUploadManager getInstance(Context context) { if (instance == null) { synchronized (CosUploadManager.class) { if (instance == null) { - instance = new CosUploadManager(); + instance = new CosUploadManager(context); } } } return instance; } - public void init(Context context) { - this.context = context.getApplicationContext(); - } + public void upParameters( String objectKey, String localPath, UploadCallback callback) { // 确保已初始化 - if (context == null) { - callback.onFailure(new IllegalStateException("CosUploadManager not initialized with context")); - return; - } +// if (context == null) { +// callback.onFailure1(new IllegalStateException("CosUploadManager not initialized with context")); +// return; +// } RetrofitClient.getInstance().getTempKey(new BaseObserver() { @Override public void onSubscribe(@NotNull Disposable disposable) { @@ -91,7 +92,7 @@ public class CosUploadManager { .setRegion(region) .isHttps(true) // 使用 HTTPS 请求, 默认为 HTTP 请求 .builder(); - CosXmlService cosXmlService = new CosXmlService(context, serviceConfig); + CosXmlService cosXmlService = new CosXmlService(mContext, serviceConfig); // 任何 CosXmlRequest 都支持这种方式,例如上传 PutObjectRequest、下载 GetObjectRequest、删除 DeleteObjectRequest 等 // 以下用上传进行示例 @@ -142,5 +143,7 @@ public class CosUploadManager { public interface UploadCallback { void onSuccess(String url); // 上传成功,返回访问URL void onFailure(Exception e); // 上传失败 + + void onFailure1(IllegalStateException e); } } diff --git a/moduleUtil/src/main/java/com/xscm/moduleutil/widget/DropDayTaskView.java b/moduleUtil/src/main/java/com/xscm/moduleutil/widget/DropDayTaskView.java new file mode 100644 index 00000000..f9021651 --- /dev/null +++ b/moduleUtil/src/main/java/com/xscm/moduleutil/widget/DropDayTaskView.java @@ -0,0 +1,288 @@ +package com.xscm.moduleutil.widget; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import com.blankj.utilcode.util.ScreenUtils; +import com.xscm.moduleutil.utils.BarUtils; + +/** + * com.xscm.moduleutil.widget + * qx + * 2025/10/27 + */ +public class DropDayTaskView extends LinearLayout { + + private int rightMargin = 0; + private float lastX, lastY; + private int screenWidth; + private int screenHeight; // 添加屏幕高度变量 + + public DropDayTaskView(Context context) { + super(context); + init(); + } + + public DropDayTaskView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public DropDayTaskView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + + void init() { + // 初始化屏幕尺寸 + screenWidth = ScreenUtils.getScreenWidth(); + screenHeight = ScreenUtils.getScreenHeight(); + + post(new Runnable() { + @Override + public void run() { + //设置初始位置 + int sh = ScreenUtils.getScreenHeight(); + int sw = ScreenUtils.getScreenWidth(); +// setBackgroundResource(R.drawable.bg_home_drop_view); + int y = (int) (0.7f * sh) - getHeight(); + // 确保Y坐标不会超出屏幕范围 + y = Math.max(0, Math.min(y, sh - getHeight())); + int x = sw - getWidth(); + setTranslationX(x); + setTranslationY(y); + } + }); + + updateSize(); + mStatusBarHeight = BarUtils.getStatusBarHeight(); + } + + /** + * 更新屏幕尺寸信息 + */ + protected void updateSize() { + ViewGroup viewGroup = (ViewGroup) getParent(); + if (viewGroup != null) { + mScreenWidth = viewGroup.getWidth(); + mScreenHeight = viewGroup.getHeight(); + } else { + // 如果父视图为空,使用屏幕的实际宽度和高度 + mScreenWidth = getResources().getDisplayMetrics().widthPixels; + mScreenHeight = getResources().getDisplayMetrics().heightPixels; + } + } + + boolean starDrap = false; + float X1; + float X2; + float Y1; + float Y2; + // 记录视图初始位置 + private float originalX; + private float originalY; + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (starDrap) return true; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + X1 = event.getRawX(); + Y1 = event.getRawY(); + // 记录视图当前位置 + originalX = getTranslationX(); + originalY = getTranslationY(); + break; + + case MotionEvent.ACTION_MOVE: + X2 = event.getRawX(); + Y2 = event.getRawY(); + Action(X1, X2, Y1, Y2); + break; + + + } + return starDrap; + } + + String TAG = "DropHourlView"; + + public boolean Action(float X1, float X2, float Y1, float Y2) { + float ComparedX = X2 - X1;//第二次的X坐标的位置减去第一次X坐标的位置,代表X坐标上的变化情况 + float ComparedY = Y2 - Y1;//同理 + //当X坐标的变化量的绝对值大于Y坐标的变化量的绝对值,以X坐标的变化情况作为判断依据 + //上下左右的判断,都在一条直线上,但手指的操作不可能划直线,所有选择变化量大的方向上的量 + //作为判断依据 + if (Math.abs(ComparedX) > 30 || Math.abs(ComparedY) > 30) { + Log.i(TAG, "Action: 拖动"); + starDrap = true; +// setBackgroundResource(R.drawable.bg_home_drop_view); + return true; + } else { + starDrap = false; + return false; + } + } + + private float mOriginalRawX; + private float mOriginalRawY; + private float mOriginalX; + private float mOriginalY; + protected int mScreenWidth; + private int mScreenHeight; + private int mStatusBarHeight; + + private void updateViewPosition(MotionEvent event) { + // 计算新的Y位置 + float desY = mOriginalY + event.getRawY() - mOriginalRawY; + + // 限制Y位置不超出屏幕边界 + if (desY < mStatusBarHeight) { + desY = mStatusBarHeight; + } + if (desY > mScreenHeight - getHeight()) { + desY = mScreenHeight - getHeight(); + } + + // 计算新的X位置 + float desX = mOriginalX + event.getRawX() - mOriginalRawX; + + // 限制X位置不超出屏幕边界 + if (desX < 0) { + desX = 0; + } + if (desX > mScreenWidth - getWidth()) { + desX = mScreenWidth - getWidth(); + } + + // 设置视图的新位置 + setX(desX); + setY(desY); + } + + private void changeOriginalTouchParams(MotionEvent event) { + mOriginalX = getX();//getX()相对于控件X坐标的距离 + mOriginalY = getY(); + mOriginalRawX = event.getRawX();//getRawX()指控件在屏幕上的X坐标 + mOriginalRawY = event.getRawY(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event == null) { + return false; + } + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + changeOriginalTouchParams(event); + updateSize(); // 添加这行确保尺寸是最新的 + // ... 其他现有代码 ... + + break; + case MotionEvent.ACTION_MOVE: + + updateViewPosition(event); // 使用更新后的带边界检查的方法 + +// setBackgroundResource(R.drawable.bg_home_drop_view); + // 使用屏幕绝对坐标计算新位置 +// float newX = originalX + (event.getRawX() - X1); +// float newY = originalY + (event.getRawY() - Y1); +// +// // 限制X和Y坐标在屏幕范围内 +// newX = Math.max(0, Math.min(newX, screenWidth - getWidth())); +// newY = Math.max(0, Math.min(newY, screenHeight - getHeight())); +// +// setTranslationX(newX); +// setTranslationY(newY); +// X2 = event.getRawX(); + break; + case MotionEvent.ACTION_UP: + starDrap = false; + int sw = ScreenUtils.getScreenWidth(); + Log.i(TAG, "onTouchEvent: " + sw + "," + X2); + boolean isR = getTranslationX() + getWidth() / 2 >= sw / 2;//贴边方向 + + // 获取当前Y坐标 + float currentY = getTranslationY(); + + // 创建X轴和Y轴的动画 + ObjectAnimator animX = ObjectAnimator.ofFloat(this, "translationX", isR ? sw - getWidth() : 0f).setDuration(200); + // Y轴保持当前位置,但确保在屏幕范围内 + currentY = Math.max(0, Math.min(currentY, screenHeight - getHeight())); + ObjectAnimator animY = ObjectAnimator.ofFloat(this, "translationY", currentY).setDuration(200); + + animX.start(); + animY.start(); + + break; + + } + + return true; + } + + + public void doRevealAnimation(View mPuppet, boolean flag) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + int[] vLocation = new int[2]; + getLocationInWindow(vLocation); + int centerX = vLocation[0] + getMeasuredWidth() / 2; + int centerY = vLocation[1] + getMeasuredHeight() / 2; + + int height = ScreenUtils.getScreenHeight(); + int width = ScreenUtils.getScreenWidth(); + int maxRradius = (int) Math.hypot(height, width); + Log.e("hei", maxRradius + ""); + + if (flag) { + mPuppet.setVisibility(VISIBLE); + Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, maxRradius, 0); + animator.setDuration(600); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mPuppet.setVisibility(View.GONE); + } + }); + animator.start(); + flag = false; + } else { + Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, 0, maxRradius); + animator.setDuration(1000); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mPuppet.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + animator.start(); + flag = true; + } + } + } +} diff --git a/moduleUtil/src/main/res/drawable/bg_r8_tm.xml b/moduleUtil/src/main/res/drawable/bg_r8_tm.xml index 1f053f32..22c78766 100644 --- a/moduleUtil/src/main/res/drawable/bg_r8_tm.xml +++ b/moduleUtil/src/main/res/drawable/bg_r8_tm.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/moduleUtil/src/main/res/drawable/ease_row_pubilc_sys_bg.xml b/moduleUtil/src/main/res/drawable/ease_row_pubilc_sys_bg.xml index b2f611f8..8b9c2f00 100644 --- a/moduleUtil/src/main/res/drawable/ease_row_pubilc_sys_bg.xml +++ b/moduleUtil/src/main/res/drawable/ease_row_pubilc_sys_bg.xml @@ -4,8 +4,8 @@ xmlns:android="http://schemas.android.com/apk/res/android"> + android:topLeftRadius="12dp" + android:topRightRadius="12dp" + android:bottomLeftRadius="12dp" + android:bottomRightRadius="12dp"/> \ No newline at end of file diff --git a/moduleUtil/src/main/res/drawable/ease_row_pubilc_user_bg.xml b/moduleUtil/src/main/res/drawable/ease_row_pubilc_user_bg.xml index 3b941adc..17f9b81f 100644 --- a/moduleUtil/src/main/res/drawable/ease_row_pubilc_user_bg.xml +++ b/moduleUtil/src/main/res/drawable/ease_row_pubilc_user_bg.xml @@ -2,12 +2,12 @@ + android:bottom="2dp"> - + \ No newline at end of file diff --git a/moduleUtil/src/main/res/layout/ease_row_received_message_giftr_send.xml b/moduleUtil/src/main/res/layout/ease_row_received_message_giftr_send.xml index 204cd23f..268e7345 100644 --- a/moduleUtil/src/main/res/layout/ease_row_received_message_giftr_send.xml +++ b/moduleUtil/src/main/res/layout/ease_row_received_message_giftr_send.xml @@ -6,100 +6,31 @@ android:id="@+id/bubble1" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:minWidth="@dimen/dp_256" - android:maxWidth="@dimen/dp_256" - android:padding="@dimen/dp_8" - android:background="@drawable/ease_row_pubilc_user_bg" + android:layout_marginTop="@dimen/dp_3" + android:layout_marginStart="@dimen/dp_8" + android:paddingStart="@dimen/dp_8" + android:paddingTop="@dimen/dp_2" + android:paddingBottom="@dimen/dp_2" + android:background="@drawable/ease_row_pubilc_sys_bg" > - - - - - - - - - - - - - - - - - - - - + android:lineHeight="@dimen/dp_20" + android:lineSpacingExtra="@dimen/dp_2" + android:paddingTop="@dimen/dp_2" + android:paddingBottom="@dimen/dp_2" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + android:textColor="@color/white" + android:textSize="@dimen/sp_12" + tools:text="饶利: 潇洒亼◇生2.0:" + tools:visibility="visible"/> - - - - diff --git a/moduleUtil/src/main/res/layout/ease_row_received_message_system.xml b/moduleUtil/src/main/res/layout/ease_row_received_message_system.xml index 31765018..8b9089ad 100644 --- a/moduleUtil/src/main/res/layout/ease_row_received_message_system.xml +++ b/moduleUtil/src/main/res/layout/ease_row_received_message_system.xml @@ -4,9 +4,10 @@ android:layout_height="wrap_content" android:id="@+id/bubble" android:layout_marginTop="@dimen/dp_3" - android:layout_marginBottom="@dimen/dp_3" - android:padding="@dimen/dp_8" android:layout_marginStart="@dimen/dp_8" + android:paddingStart="@dimen/dp_8" + android:paddingTop="@dimen/dp_2" + android:paddingBottom="@dimen/dp_2" android:background="@drawable/ease_row_pubilc_sys_bg" > @@ -21,11 +22,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/white" - android:paddingTop="@dimen/dp_5" - android:paddingBottom="@dimen/dp_5" + android:paddingTop="@dimen/dp_2" + android:paddingBottom="@dimen/dp_2" android:paddingEnd="@dimen/dp_8" android:text="我是房间公告" - android:textSize="@dimen/sp_14"/> + android:textSize="@dimen/sp_11"/> diff --git a/moduleUtil/src/main/res/layout/loading_dialog.xml b/moduleUtil/src/main/res/layout/loading_dialog.xml new file mode 100644 index 00000000..964764ec --- /dev/null +++ b/moduleUtil/src/main/res/layout/loading_dialog.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/moduleUtil/src/main/res/mipmap-hdpi/day_task.webp b/moduleUtil/src/main/res/mipmap-hdpi/day_task.webp new file mode 100644 index 00000000..85de5c22 Binary files /dev/null and b/moduleUtil/src/main/res/mipmap-hdpi/day_task.webp differ diff --git a/moduleUtil/src/main/res/mipmap-hdpi/room_sound_effects.webp b/moduleUtil/src/main/res/mipmap-hdpi/room_sound_effects.webp new file mode 100644 index 00000000..a607535b Binary files /dev/null and b/moduleUtil/src/main/res/mipmap-hdpi/room_sound_effects.webp differ diff --git a/moduleUtil/src/main/res/mipmap-xhdpi/day_task.webp b/moduleUtil/src/main/res/mipmap-xhdpi/day_task.webp new file mode 100644 index 00000000..9161936e Binary files /dev/null and b/moduleUtil/src/main/res/mipmap-xhdpi/day_task.webp differ diff --git a/moduleUtil/src/main/res/mipmap-xhdpi/room_sound_effects.webp b/moduleUtil/src/main/res/mipmap-xhdpi/room_sound_effects.webp new file mode 100644 index 00000000..dc627413 Binary files /dev/null and b/moduleUtil/src/main/res/mipmap-xhdpi/room_sound_effects.webp differ diff --git a/moduleUtil/src/main/res/mipmap-xxhdpi/day_task.webp b/moduleUtil/src/main/res/mipmap-xxhdpi/day_task.webp new file mode 100644 index 00000000..3a26d66f Binary files /dev/null and b/moduleUtil/src/main/res/mipmap-xxhdpi/day_task.webp differ diff --git a/moduleUtil/src/main/res/mipmap-xxhdpi/room_sound_effects.webp b/moduleUtil/src/main/res/mipmap-xxhdpi/room_sound_effects.webp new file mode 100644 index 00000000..1eaf9b96 Binary files /dev/null and b/moduleUtil/src/main/res/mipmap-xxhdpi/room_sound_effects.webp differ diff --git a/moduleUtil/src/main/res/mipmap-xxxhdpi/room_sound_effects.webp b/moduleUtil/src/main/res/mipmap-xxxhdpi/room_sound_effects.webp deleted file mode 100644 index 251c076b..00000000 Binary files a/moduleUtil/src/main/res/mipmap-xxxhdpi/room_sound_effects.webp and /dev/null differ diff --git a/modulecircle/src/main/java/com/example/modulecircle/activity/DynamicDetailActivity.java b/modulecircle/src/main/java/com/example/modulecircle/activity/DynamicDetailActivity.java index ba52272e..d77f686e 100644 --- a/modulecircle/src/main/java/com/example/modulecircle/activity/DynamicDetailActivity.java +++ b/modulecircle/src/main/java/com/example/modulecircle/activity/DynamicDetailActivity.java @@ -129,7 +129,7 @@ public class DynamicDetailActivity extends BaseMvpActivity implem String url = OSSOperUtils.getPath(file, type); - CosUploadManager.getInstance().upParameters(url,file.getPath(), new CosUploadManager.UploadCallback() { + CosUploadManager.getInstance(CommonAppContext.getInstance()).upParameters(url,file.getPath(), new CosUploadManager.UploadCallback() { @Override public void onSuccess(String url) { if (MvpRef==null){ @@ -67,6 +68,13 @@ public class ReleasePresenter extends BasePresenter implem public void onFailure(Exception e) { ToastUtils.show("上传失败"); } + + @Override + public void onFailure1(IllegalStateException e) { + ToastUtils.show("上传失败"); + } + + }); // OSSOperUtils.newInstance().putObjectMethod(url, file.getPath(), new OSSOperUtils.OssCallback() { // @Override diff --git a/modulemain/src/main/AndroidManifest.xml b/modulemain/src/main/AndroidManifest.xml index d0d91f6c..3c40bf31 100644 --- a/modulemain/src/main/AndroidManifest.xml +++ b/modulemain/src/main/AndroidManifest.xml @@ -9,7 +9,6 @@ diff --git a/modulemain/src/main/java/com/xscm/modulemain/activity/MainActivity.java b/modulemain/src/main/java/com/xscm/modulemain/activity/MainActivity.java index bd6b03ae..48e40c86 100644 --- a/modulemain/src/main/java/com/xscm/modulemain/activity/MainActivity.java +++ b/modulemain/src/main/java/com/xscm/modulemain/activity/MainActivity.java @@ -379,9 +379,10 @@ public class MainActivity extends BaseMvpActivity + + \ No newline at end of file diff --git a/moduleroom/src/main/AndroidManifest.xml b/moduleroom/src/main/AndroidManifest.xml index 6c3fb2fa..f22d5030 100644 --- a/moduleroom/src/main/AndroidManifest.xml +++ b/moduleroom/src/main/AndroidManifest.xml @@ -17,9 +17,10 @@ android:name=".activity.RoomActivity" android:screenOrientation="portrait" android:launchMode="singleTask" - android:excludeFromRecents="true" - android:exported="true" + android:excludeFromRecents="false" + android:exported="false" android:windowSoftInputMode="adjustPan" + android:alwaysRetainTaskState="true" android:enableOnBackInvokedCallback="false" android:theme="@style/TransparentActivityTheme" android:configChanges="orientation|screenSize|keyboardHidden" diff --git a/moduleroom/src/main/java/com/example/moduleroom/activity/RoomActivity.kt b/moduleroom/src/main/java/com/example/moduleroom/activity/RoomActivity.kt index 81b10fd0..4774ec22 100644 --- a/moduleroom/src/main/java/com/example/moduleroom/activity/RoomActivity.kt +++ b/moduleroom/src/main/java/com/example/moduleroom/activity/RoomActivity.kt @@ -125,7 +125,8 @@ class RoomActivity : BaseMvpActivity(), var roomId: String? = null var mRoomInfoResp: RoomInfoResp? = null - + @JvmField + @Autowired var taskId: String? = null var likeUserAdapter: LikeUserAdapter? = null @@ -179,6 +180,7 @@ class RoomActivity : BaseMvpActivity(), roomId = intent.getStringExtra("roomId") mRoomInfoResp = intent.getSerializableExtra("roomInfo") as RoomInfoResp? LogUtils.dTag("RoomActivity", "doDone" + mRoomInfoResp.toString()) + taskId = intent.getStringExtra("taskId") } @@ -482,6 +484,20 @@ class RoomActivity : BaseMvpActivity(), private val testHandler = Handler() private var testRunnable: Runnable? = null +// override fun onNewIntent(intent: Intent?) { +// super.onNewIntent(intent) +// setIntent( intent) +// Log.d("TAG", "=== onNewIntent called ==="); +// if (intent != null) { +// val roomId = intent.getStringExtra("roomId") +// redPacketInfo = intent.getSerializableExtra("redPacketInfo") as RedPacketInfo? +// } +// } + + override fun onDestroy() { + super.onDestroy() + Log.d("TAG", "=== onDestroy called ==="); + } override fun onCreate(savedInstanceState: Bundle?) { // 在super.onCreate之前设置主题以避免闪白屏 @@ -489,7 +505,6 @@ class RoomActivity : BaseMvpActivity(), super.onCreate(savedInstanceState) // // 进入房间10s后检查是否显示提示上麦对话框 LogUtils.e("RoomActivity", "onCreate") - isSave = false sDestroied = false isMinimized = false @@ -862,6 +877,10 @@ class RoomActivity : BaseMvpActivity(), val fragment = HourlyChartDialog.newInstance() fragment.show(supportFragmentManager, "HourlyChartDialog") } + mBinding!!.clDayTask.visibility = View.VISIBLE + mBinding!!.imDayTask.setOnClickListener { + ARouter.getInstance().build(ARouteConstants.DailyTasksActivity).navigation() + } mBinding!!.drvRed.visibility = View.GONE mBinding!!.redBj.setOnClickListener { @@ -3508,8 +3527,7 @@ class RoomActivity : BaseMvpActivity(), mBinding!!.ivQuanC.setOnClickListener { v: View? -> if (mRoomInfoResp!!.room_info.head_line.room_id != null && mRoomInfoResp!!.room_info.head_line.room_id.isNotEmpty()) { if (mRoomInfoResp!!.room_info.head_line.room_id != roomId) { - RoomManager.getInstance() - .fetchRoomDataAndEnter(applicationContext, mRoomInfoResp!!.room_info.head_line.room_id, "") + RoomManager.getInstance().fetchRoomDataAndEnter(applicationContext, mRoomInfoResp!!.room_info.head_line.room_id, "",null) } else { com.blankj.utilcode.util.ToastUtils.showLong("您就在当前房间") } diff --git a/moduleroom/src/main/java/com/example/moduleroom/adapter/EaseChatAdapter.java b/moduleroom/src/main/java/com/example/moduleroom/adapter/EaseChatAdapter.java index 5ed426df..e947f94c 100644 --- a/moduleroom/src/main/java/com/example/moduleroom/adapter/EaseChatAdapter.java +++ b/moduleroom/src/main/java/com/example/moduleroom/adapter/EaseChatAdapter.java @@ -75,7 +75,7 @@ public class EaseChatAdapter extends BaseMultiItemQuickAdapter images1 = emMessage.getText().getFromUserInfo().getIcon(); // LinearLayout ll_images1 = helper.getView(com.xscm.moduleutil.R.id.line); // ll_images1.removeAllViews(); diff --git a/moduleroom/src/main/java/com/example/moduleroom/dialog/HourlyChartDialog.java b/moduleroom/src/main/java/com/example/moduleroom/dialog/HourlyChartDialog.java index 30c80ec0..29996445 100644 --- a/moduleroom/src/main/java/com/example/moduleroom/dialog/HourlyChartDialog.java +++ b/moduleroom/src/main/java/com/example/moduleroom/dialog/HourlyChartDialog.java @@ -131,7 +131,7 @@ public class HourlyChartDialog extends BaseMvpDialogFragment { - upDtaView(true); - }); - Observable.timer(500, TimeUnit.MILLISECONDS) +// Observable.timer(20, TimeUnit.MILLISECONDS) +// .observeOn(AndroidSchedulers.mainThread()) +// .subscribe(aLong -> { +// upDtaView(true); +// }); + Observable.timer(10, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(aLong -> { upDtaView(false); @@ -165,7 +166,7 @@ public class SingSongFragment extends BaseRoomFragment + + + + + + + + diff --git a/modulevocal/src/main/java/com/example/modulevocal/activity/DailyTasksActivity.java b/modulevocal/src/main/java/com/example/modulevocal/activity/DailyTasksActivity.java index d31c3d6b..394b7a3a 100644 --- a/modulevocal/src/main/java/com/example/modulevocal/activity/DailyTasksActivity.java +++ b/modulevocal/src/main/java/com/example/modulevocal/activity/DailyTasksActivity.java @@ -4,9 +4,11 @@ package com.example.modulevocal.activity; import android.content.Intent; import android.graphics.Color; +import android.view.View; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; +import com.alibaba.android.arouter.facade.annotation.Route; import com.alibaba.android.arouter.launcher.ARouter; import com.example.modulevocal.R; import com.example.modulevocal.adapter.TaskBoxAdapter; @@ -19,6 +21,7 @@ import com.hjq.toast.ToastUtils; import com.xscm.moduleutil.activity.BaseMvpActivity; import com.xscm.moduleutil.activity.WebViewActivity; import com.xscm.moduleutil.base.CommonAppContext; +import com.xscm.moduleutil.base.RoomManager; import com.xscm.moduleutil.bean.GiftBoxBean; import com.xscm.moduleutil.bean.GiftName; import com.xscm.moduleutil.bean.TaskItem; @@ -33,6 +36,7 @@ import java.util.List; * @data 2025/5/27 * @description: 每日任务 */ +@Route(path = ARouteConstants.DailyTasksActivity) public class DailyTasksActivity extends BaseMvpActivity implements DailyTasksConacts.View { private TaskBoxAdapter mTaskBoxAdapter; @@ -142,11 +146,16 @@ public class DailyTasksActivity extends BaseMvpActivity { - RoomManager.getInstance().fetchRoomDataAndEnter(getApplicationContext(), CommonAppContext.getInstance().playId, ""); + isShowLoading(true); + RoomManager.getInstance().fetchRoomDataAndEnter(getApplicationContext(), CommonAppContext.getInstance().playId, "",null); }); mBinding.ivGuanbi.setOnClickListener(v -> { mBinding.ll.setVisibility(View.INVISIBLE); @@ -202,6 +203,20 @@ public class MyRoomActivity extends BaseMvpActivity items) { + private MyRoomActivity myRoomActivity; + + // public void submitList(List items) { // viewItems.clear(); // viewItems.addAll(items); // notifyDataSetChanged(); // } + public void setMyRoomActivity(MyRoomActivity myRoomActivity) { + this.myRoomActivity = myRoomActivity; + } public void submitList(List items) { if (items == null || items.isEmpty()) { @@ -217,7 +224,7 @@ public class MyCreateAdapter extends RecyclerView.Adapter public void uploadFile(File file, int type, int index, int size) { MvpRef.get().showLoadings("上传中..."); String url = OSSOperUtils.getPath(file, type); - CosUploadManager.getInstance().upParameters(url,file.getPath(), new CosUploadManager.UploadCallback() { + CosUploadManager.getInstance(CommonAppContext.getInstance()).upParameters(url,file.getPath(), new CosUploadManager.UploadCallback() { @Override public void onSuccess(String url) { if (isViewAttach()) { @@ -64,6 +65,12 @@ public class AlbumDetailPresenter extends BasePresenter ToastUtils.show("上传失败"); MvpRef.get().disLoadings(); } + + @Override + public void onFailure1(IllegalStateException e) { + ToastUtils.show("上传失败"); + MvpRef.get().disLoadings(); + } }); // OSSOperUtils.newInstance().putObjectMethod(url, file.getPath(), new OSSOperUtils.OssCallback() { diff --git a/modulevocal/src/main/java/com/example/modulevocal/presenter/CreatedRoomPresenter.java b/modulevocal/src/main/java/com/example/modulevocal/presenter/CreatedRoomPresenter.java index 9a737660..433d49e1 100644 --- a/modulevocal/src/main/java/com/example/modulevocal/presenter/CreatedRoomPresenter.java +++ b/modulevocal/src/main/java/com/example/modulevocal/presenter/CreatedRoomPresenter.java @@ -4,6 +4,7 @@ import android.content.Context; import com.example.modulevocal.conacts.CreatedRoomConactos; import com.hjq.toast.ToastUtils; +import com.xscm.moduleutil.base.CommonAppContext; import com.xscm.moduleutil.http.BaseObserver; import com.xscm.moduleutil.presenter.BasePresenter; import com.xscm.moduleutil.utils.cos.CosUploadManager; @@ -81,7 +82,7 @@ public class CreatedRoomPresenter extends BasePresenter impl public void uploadFile(File file, int type, int index, int size) { MvpRef.get().showLoadings("上传中..."); String url = OSSOperUtils.getPath(file, type); - CosUploadManager.getInstance().upParameters(url,file.getPath(), new CosUploadManager.UploadCallback() { + CosUploadManager.getInstance(CommonAppContext.getInstance()).upParameters(url,file.getPath(), new CosUploadManager.UploadCallback() { @Override public void onSuccess(String url) { if (isViewAttach()) { @@ -41,7 +42,13 @@ public class EditUserPresenter extends BasePresenter impl @Override public void onFailure(Exception e) { - ToastUtils.show("上传失败"); + ToastUtils.show("上传失败",e); + MvpRef.get().disLoadings(); + } + + @Override + public void onFailure1(IllegalStateException e) { + ToastUtils.show("上传失败",e); MvpRef.get().disLoadings(); } }); diff --git a/modulevocal/src/main/java/com/example/modulevocal/presenter/MyAlbumPresenter.java b/modulevocal/src/main/java/com/example/modulevocal/presenter/MyAlbumPresenter.java index 19714b18..3af59501 100644 --- a/modulevocal/src/main/java/com/example/modulevocal/presenter/MyAlbumPresenter.java +++ b/modulevocal/src/main/java/com/example/modulevocal/presenter/MyAlbumPresenter.java @@ -4,6 +4,7 @@ import android.content.Context; import com.example.modulevocal.conacts.MyAlbumConacts; import com.hjq.toast.ToastUtils; +import com.xscm.moduleutil.base.CommonAppContext; import com.xscm.moduleutil.bean.AlbumBean; import com.xscm.moduleutil.http.BaseObserver; import com.xscm.moduleutil.presenter.BasePresenter; @@ -55,7 +56,7 @@ public class MyAlbumPresenter extends BasePresenter impleme public void uploadFile(File file, int type) { MvpRef.get().showLoadings("上传中..."); String url = OSSOperUtils.getPath(file, type); - CosUploadManager.getInstance().upParameters(url,file.getPath(), new CosUploadManager.UploadCallback() { + CosUploadManager.getInstance(CommonAppContext.getInstance()).upParameters(url,file.getPath(), new CosUploadManager.UploadCallback() { @Override public void onSuccess(String url) { if (isViewAttach()) { @@ -70,6 +71,12 @@ public class MyAlbumPresenter extends BasePresenter impleme ToastUtils.show("上传失败"); MvpRef.get().disLoadings(); } + + @Override + public void onFailure1(IllegalStateException e) { + ToastUtils.show("上传失败"); + MvpRef.get().disLoadings(); + } }); // OSSOperUtils.newInstance().putObjectMethod(url, file.getPath(), new OSSOperUtils.OssCallback() { // @Override diff --git a/modulevocal/src/main/res/layout/activity_daily_tasks.xml b/modulevocal/src/main/res/layout/activity_daily_tasks.xml index 15d18237..9d5e9e10 100644 --- a/modulevocal/src/main/res/layout/activity_daily_tasks.xml +++ b/modulevocal/src/main/res/layout/activity_daily_tasks.xml @@ -10,7 +10,7 @@ - + diff --git a/modulevocal/src/main/res/layout/room_activity_my_room.xml b/modulevocal/src/main/res/layout/room_activity_my_room.xml index 89353f9d..cd78b4df 100644 --- a/modulevocal/src/main/res/layout/room_activity_my_room.xml +++ b/modulevocal/src/main/res/layout/room_activity_my_room.xml @@ -242,5 +242,18 @@ android:layout_marginRight="@dimen/dp_14" android:src="@mipmap/icon_guanbi" /> + + \ No newline at end of file diff --git a/modulevoice/src/main/java/com/example/modulevoice/activity/PopularRoomActivity.java b/modulevoice/src/main/java/com/example/modulevoice/activity/PopularRoomActivity.java index 44a8643d..159df31b 100644 --- a/modulevoice/src/main/java/com/example/modulevoice/activity/PopularRoomActivity.java +++ b/modulevoice/src/main/java/com/example/modulevoice/activity/PopularRoomActivity.java @@ -94,14 +94,21 @@ public class PopularRoomActivity extends BaseMvpActivity= mAdapter.getData().size()) { return; @@ -134,7 +139,7 @@ public class HotListFragment extends BaseMvpFragment info; CarouselBannerAdapter carouselBannerAdapter; - public static VoiceCategoryFragment newInstance() { - return new VoiceCategoryFragment(); + private VoiceFragment voiceFragment; + + private VoiceCategoryFragment (){} + + + private VoiceCategoryFragment (VoiceFragment voiceFragment){ + this.voiceFragment = voiceFragment; + } + + public static VoiceCategoryFragment newInstance(VoiceFragment voiceFragment) { + return new VoiceCategoryFragment(voiceFragment); + } + + public void showLoading(){ + voiceFragment.isShowLoading(true); } @Nullable @@ -156,7 +169,8 @@ public class VoiceCategoryFragment extends BaseMvpFragment { // 示例:跳转到房间 详情页 if (data != null) { - RoomManager.getInstance().fetchRoomDataAndEnter(getActivity(), data.getRoom_id() ,""); + showLoading(); + RoomManager.getInstance().fetchRoomDataAndEnter(getActivity(), data.getRoom_id() ,"",null); } }); @@ -178,7 +192,8 @@ public class VoiceCategoryFragment extends BaseMvpFragment list; @@ -408,7 +423,7 @@ public class VoiceCategoryFragment extends BaseMvpFragment fragments = new ArrayList<>(); - fragments.add(VoiceCategoryFragment.newInstance()); + fragments.add(VoiceCategoryFragment.newInstance(VoiceFragment.this)); mBinding.viewPager.setAdapter(new MyFragmentPagerAdapter(fragments, getChildFragmentManager())); } diff --git a/modulevoice/src/main/res/layout/activity_popular_room.xml b/modulevoice/src/main/res/layout/activity_popular_room.xml index 33304ae1..c6149403 100644 --- a/modulevoice/src/main/res/layout/activity_popular_room.xml +++ b/modulevoice/src/main/res/layout/activity_popular_room.xml @@ -42,5 +42,18 @@ android:paddingBottom="@dimen/dp_80" tools:listitem="@layout/item_popular_room"/> + + \ No newline at end of file diff --git a/modulevoice/src/main/res/layout/activity_ranking_list.xml b/modulevoice/src/main/res/layout/activity_ranking_list.xml index 9f18967a..bdcd0714 100644 --- a/modulevoice/src/main/res/layout/activity_ranking_list.xml +++ b/modulevoice/src/main/res/layout/activity_ranking_list.xml @@ -29,6 +29,7 @@ android:layout_marginRight="@dimen/dp_10" app:tabIndicatorHeight="10dp" app:tabIndicator="@mipmap/tab_x" + app:tabIndicatorGravity="bottom" app:tabTextColor="@color/white" app:tabSelectedTextColor="@color/white" app:tabTextAppearance="@style/CustomTabTextAppearance" diff --git a/modulevoice/src/main/res/layout/fragment_voice.xml b/modulevoice/src/main/res/layout/fragment_voice.xml index 9ae30933..06c9e780 100644 --- a/modulevoice/src/main/res/layout/fragment_voice.xml +++ b/modulevoice/src/main/res/layout/fragment_voice.xml @@ -7,104 +7,119 @@ - + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@mipmap/home_bj"> - - - - - + android:gravity="center_vertical"> + + - + android:orientation="horizontal" + android:visibility="gone"> - + - + + - + + + + + - + - + - + - + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 97e128ee..7999babb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -61,4 +61,5 @@ include ':tuichat' include ':tuiconversation' include ':tuicore' include ':moduleroom' +include ':Loadinglibrary'