Thank you @zhang for you solution! In my case, according to my project's requirements, I had different animation (without background constant opacity):
see the first animation here
Also, I had different color, radius, wanted animation to start automatically and modify color, radius from the xml. To achieve that, I modified LoadingIndicatorView.java
and LoadingIndicatorBarView.java
and added style in attrs.xml
, Toolbox.java
is unmodified (see below).
Example of usage:
<utils.ioslikespinner.LoadingIndicatorView
android:layout_width="100dp"
android:layout_height="100dp"
app:color="?colorPrimary"
app:radius="50dp"/>
If you have similar requirements, then this is my code:
LoadingIndicatorView.java
package utils.ioslikespinner;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.RotateAnimation;
import android.widget.RelativeLayout;
import com.milence.R;
import java.util.ArrayList;
/**
* Created by Zhang on 11/02/16 (https://mcmap.net/q/980942/-implement-a-spinning-activity-indicator-similar-to-ios-in-android)
* Modified for design
*
* @noinspection ALL
*/
public class LoadingIndicatorView extends RelativeLayout {
private final Handler handler = new Handler();
public ArrayList<LoadingIndicatorBarView> arrBars;
public float radius;
private Context context;
private int numberOfBars;
private int color;
private boolean isAnimating;
private int currentFrame;
private Runnable playFrameRunnable;
public LoadingIndicatorView(Context context, AttributeSet attrs) {
super(context, attrs);
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoadingIndicatorView);
this.color = typedArray.getColor(R.styleable.LoadingIndicatorView_color, Color.WHITE);
this.radius = typedArray.getDimension(R.styleable.LoadingIndicatorView_radius, 33);
}
this.context = context;
this.numberOfBars = 12;
initViews();
initLayouts();
addViews();
spreadBars();
startAnimating();
}
public void initViews() {
arrBars = new ArrayList<LoadingIndicatorBarView>();
for (int i = 0; i < numberOfBars; i++) {
LoadingIndicatorBarView bar = new LoadingIndicatorBarView(context, 100, this.color);
arrBars.add(bar);
}
}
public void initLayouts() {
for (int i = 0; i < numberOfBars; i++) {
LoadingIndicatorBarView bar = arrBars.get(i);
bar.setId(View.generateViewId());
RelativeLayout.LayoutParams barLayoutParams = new RelativeLayout.LayoutParams(
(int) (radius / 5.0f),
(int) (radius / 2.0f)
);
barLayoutParams.addRule(ALIGN_PARENT_TOP);
barLayoutParams.addRule(CENTER_HORIZONTAL);
bar.setLayoutParams(barLayoutParams);
}
}
public void addViews() {
for (int i = 0; i < numberOfBars; i++) {
LoadingIndicatorBarView bar = arrBars.get(i);
addView(bar);
}
}
public void spreadBars() {
int degrees = 0;
for (int i = 0; i < arrBars.size(); i++) {
LoadingIndicatorBarView bar = arrBars.get(i);
rotateBar(bar, degrees);
degrees += 30;
}
}
private void rotateBar(LoadingIndicatorBarView bar, float degrees) {
RotateAnimation animation = new RotateAnimation(0, degrees, radius / 10.0f, radius);
animation.setDuration(0);
animation.setFillAfter(true);
bar.setAnimation(animation);
animation.start();
}
public void startAnimating() {
setAlpha(1.0f);
isAnimating = true;
playFrameRunnable = new Runnable() {
@Override
public void run() {
playFrame();
}
};
// recursive function until isAnimating is false
playFrame();
}
public void stopAnimating() {
isAnimating = false;
setAlpha(0.0f);
invalidate();
playFrameRunnable = null;
}
private void playFrame() {
if (isAnimating) {
resetAllBarAlpha();
updateFrame();
handler.postDelayed(playFrameRunnable, 100);
}
}
private void updateFrame() {
if (isAnimating) {
showFrame(currentFrame);
currentFrame += 1;
if (currentFrame > 11) {
currentFrame = 0;
}
}
}
private void resetAllBarAlpha() {
LoadingIndicatorBarView bar = null;
float alpha = 1f;
for (int i = 0; i < arrBars.size(); i++) {
bar = arrBars.get(i);
bar.setAlpha(alpha);
}
}
private void showFrame(int frameNumber) {
int[] indexes = getFrameIndexesForFrameNumber(frameNumber);
gradientColorBarSets(indexes);
}
private int[] getFrameIndexesForFrameNumber(int frameNumber) {
if (frameNumber == 0) {
return indexesFromNumbers(0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1);
} else if (frameNumber == 1) {
return indexesFromNumbers(1, 0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2);
} else if (frameNumber == 2) {
return indexesFromNumbers(2, 1, 0, 11, 10, 9, 8, 7, 6, 5, 4, 3);
} else if (frameNumber == 3) {
return indexesFromNumbers(3, 2, 1, 0, 11, 10, 9, 8, 7, 6, 5, 4);
} else if (frameNumber == 4) {
return indexesFromNumbers(4, 3, 2, 1, 0, 11, 10, 9, 8, 7, 6, 5);
} else if (frameNumber == 5) {
return indexesFromNumbers(5, 4, 3, 2, 1, 0, 11, 10, 9, 8, 7, 6);
} else if (frameNumber == 6) {
return indexesFromNumbers(6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8, 7);
} else if (frameNumber == 7) {
return indexesFromNumbers(7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9, 8);
} else if (frameNumber == 8) {
return indexesFromNumbers(8, 7, 6, 5, 4, 3, 2, 1, 0, 11, 10, 9);
} else if (frameNumber == 9) {
return indexesFromNumbers(9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 11, 10);
} else if (frameNumber == 10) {
return indexesFromNumbers(10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 11);
} else {
return indexesFromNumbers(11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
}
}
private int[] indexesFromNumbers(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12) {
int[] indexes = {i1, i2, i3, i4, i5, i6, i7, i8, i9, i10, i11, i12};
return indexes;
}
private void gradientColorBarSets(int[] indexes) {
float alpha = 1.0f;
LoadingIndicatorBarView barView = null;
for (int i = 0; i < indexes.length; i++) {
int barIndex = indexes[i];
barView = arrBars.get(barIndex);
barView.setAlpha(alpha);
alpha -= 0.088f;
}
invalidate();
}
}
LoadingIndicatorBarView.java
package utils.ioslikespinner;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.widget.RelativeLayout;
/**
* Created by Zhang on 11/02/16 (https://mcmap.net/q/980942/-implement-a-spinning-activity-indicator-similar-to-ios-in-android)
* Modified for design
*
* @noinspection ALL
*/
@SuppressLint("ALL")
public class LoadingIndicatorBarView extends RelativeLayout {
private Context context;
private float cornerRadius;
private int color;
public LoadingIndicatorBarView(Context context, float cornerRadius, int color) {
super(context);
this.context = context;
this.cornerRadius = cornerRadius;
this.color = color;
initViews();
}
public void initViews() {
setBackground(ToolBox.roundedCornerRectWithColor(color, cornerRadius));
setAlpha(0.5f);
}
public void resetColor() {
setBackground(ToolBox.roundedCornerRectWithColor(
Color.argb(255, 255, 255, 255), cornerRadius));
setAlpha(0.5f);
}
}
Toolbox.java
package utils.ioslikespinner;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
/**
* Created by Zhang on 11/02/16 (https://mcmap.net/q/980942/-implement-a-spinning-activity-indicator-similar-to-ios-in-android)
*/
public class ToolBox {
private static ToolBox instance;
public Context context;
private ToolBox() {
}
public synchronized static ToolBox getInstance() {
if (instance == null) {
instance = new ToolBox();
}
return instance;
}
public static ShapeDrawable roundedCornerRectOutlineWithColor(int color, float cornerRadius,
float strokeWidth) {
float[] radii = new float[]{
cornerRadius, cornerRadius,
cornerRadius, cornerRadius,
cornerRadius, cornerRadius,
cornerRadius, cornerRadius
};
RoundRectShape roundedCornerShape = new RoundRectShape(radii, null, null);
ShapeDrawable shape = new ShapeDrawable();
shape.getPaint().setColor(color);
shape.setShape(roundedCornerShape);
shape.getPaint().setStrokeWidth(strokeWidth);
shape.getPaint().setStyle(Paint.Style.STROKE);
return shape;
}
public static ShapeDrawable roundedCornerRectWithColor(int color, float cornerRadius) {
float[] radii = new float[]{
cornerRadius, cornerRadius,
cornerRadius, cornerRadius,
cornerRadius, cornerRadius,
cornerRadius, cornerRadius
};
RoundRectShape roundedCornerShape = new RoundRectShape(radii, null, null);
ShapeDrawable shape = new ShapeDrawable();
shape.getPaint().setColor(color);
shape.setShape(roundedCornerShape);
return shape;
}
public static ShapeDrawable roundedCornerRectWithColor(int color, float topLeftRadius, float
topRightRadius, float bottomRightRadius, float bottomLeftRadius) {
float[] radii = new float[]{
topLeftRadius, topLeftRadius,
topRightRadius, topRightRadius,
bottomRightRadius, bottomRightRadius,
bottomLeftRadius, bottomLeftRadius
};
RoundRectShape roundedCornerShape = new RoundRectShape(radii, null, null);
ShapeDrawable shape = new ShapeDrawable();
shape.getPaint().setColor(color);
shape.setShape(roundedCornerShape);
return shape;
}
public static int getScreenWidth() {
return Resources.getSystem().getDisplayMetrics().widthPixels;
}
public static int getScreenHeight() {
return Resources.getSystem().getDisplayMetrics().heightPixels;
}
public static int getScreenOrientation(Context context) {
return context.getResources().getConfiguration().orientation;
}
public static boolean isLandscapeOrientation(Context context) {
return getScreenOrientation(context) == Configuration.ORIENTATION_LANDSCAPE;
}
}
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- .... -->
<resources>
<declare-styleable name="LoadingIndicatorView">
<attr name="color" format="color" />
<attr name="radius" format="dimension" />
</declare-styleable>
</resources>