I want to create a rotating progress image, and wonder what’s the best way to proceed. I can make it work with an animation list with for example 12 images changing every 100ms. This works fine, but it’s quite tedious to create 12 images or for every size and resolution:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="@drawable/ic_loading_grey_on_black_01" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_02" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_03" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_04" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_05" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_06" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_07" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_08" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_09" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_10" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_11" android:duration="100" />
<item android:drawable="@drawable/ic_loading_grey_on_black_12" android:duration="100" />
I suppose that an easier solution is to use one image per resolution, but rather rotate it for each frame. In the platform resources (android-sdk-windows/platforms…) I found something called animated-rotate in the file drawable/search_spinner.xml, but if I copy the code get a compiler error complaining about android:framesCount and android:frameDuration (Google APIs 2.2 in Eclipse):
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/spinner_black_20"
android:pivotX="50%"
android:pivotY="50%"
android:framesCount="12"
android:frameDuration="100" />
I have also tried using a repeating rotate animation (using in the anim resource folder), but I actually prefer the look of the animation list version.
What is the recommended way of solving this problem?
Rotate drawable
suggested by Praveen won’t give you control of frame count. Let’s assume you want to implement a custom loader which consists from 8 sections:
Using animation-list
approach, you need to create 8 frames rotated by 45*frameNumber
degrees manually. Alternatively, you can use 1st frame and set rotation animation to it:
File res/anim/progress_anim.xml
:
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite" />
File MainActivity.java
Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(1000);
imageView.startAnimation(a);
This will give you smooth animation instead of 8-stepped. To fix this we need to implement custom interpolator:
a.setInterpolator(new Interpolator() {
private final int frameCount = 8;
@Override
public float getInterpolation(float input) {
return (float)Math.floor(input*frameCount)/frameCount;
}
});
Also you can create a custom widget:
File res/values/attrs.xml
:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ProgressView">
<attr name="frameCount" format="integer"/>
<attr name="duration" format="integer" />
</declare-styleable>
</resources>
File ProgressView.java
:
public class ProgressView extends ImageView {
public ProgressView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAnimation(attrs);
}
public ProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
setAnimation(attrs);
}
public ProgressView(Context context) {
super(context);
}
private void setAnimation(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ProgressView);
int frameCount = a.getInt(R.styleable.ProgressView_frameCount, 12);
int duration = a.getInt(R.styleable.ProgressView_duration, 1000);
a.recycle();
setAnimation(frameCount, duration);
}
public void setAnimation(final int frameCount, final int duration) {
Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(duration);
a.setInterpolator(new Interpolator() {
@Override
public float getInterpolation(float input) {
return (float)Math.floor(input*frameCount)/frameCount;
}
});
startAnimation(a);
}
}
File activity_main.xml
:
<com.example.widget.ProgressView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_progress"
app:frameCount="8"
app:duration="1000"/>
File res/anim/progress_anim.xml
: listed above
Answer:
You have to create a drawable xml file like below:
Code:
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:pivotX="50%" android:pivotY="50%" android:fromDegrees="0"
android:toDegrees="360" android:drawable="@drawable/imagefile_to_rotate" />
Answer:
I found vokilam’s answer to be the best one to create a nice stepped/staggered animation. I went for his final suggestion and made a custom widget, the only problem I encountered was that setting visibility wouldn’t work because it was animated and thus would always be visible…
I adjusted his code (ProgressView.java which I renamed StaggeredProgress.java) like this:
public class StaggeredProgress extends ImageView {
private Animation staggered;
public StaggeredProgress(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setAnimation(attrs);
}
public StaggeredProgress(Context context, AttributeSet attrs) {
super(context, attrs);
setAnimation(attrs);
}
public StaggeredProgress(Context context) {
super(context);
}
private void setAnimation(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StaggeredProgress);
int frameCount = a.getInt(R.styleable.StaggeredProgress_frameCount, 12);
int duration = a.getInt(R.styleable.StaggeredProgress_duration, 1000);
a.recycle();
setAnimation(frameCount, duration);
}
public void setAnimation(final int frameCount, final int duration) {
Animation a = AnimationUtils.loadAnimation(getContext(), R.anim.progress_anim);
a.setDuration(duration);
a.setInterpolator(new Interpolator() {
@Override
public float getInterpolation(float input) {
return (float)Math.floor(input*frameCount)/frameCount;
}
});
staggered = a;
//startAnimation(a);
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if( visibility == View.VISIBLE )
startAnimation(staggered);
else
clearAnimation();
}
}
This way setting the view’s visibility starts and stops the animation as required…Many thanks again to vokilam!
Answer:
see examples here
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/index.html
specifically:
Progress Bar
- Incremental
Demonstrates large and small rotating progress indicators that can be incremented or decremented in units. - Smooth
Demonstrates large and small continuously rotating progress indicators used to indicate a generic “busy” message. - Dialogs
Demonstrates a ProgressDialog, a popup dialog that hosts a progress bar. This example demonstrates both determinate and indeterminate progress indicators. - In Title Bar
Demonstrates an Activity screen with a progress indicator loaded by setting the WindowPolicy’s progress indicator feature.
Answer:
SACPK’s solution definitely works. Another solution can be to use <animated-rotate>
just like in question and remove android:framesCount="12"
attributes for those the compiler complains. It still works even for my 8-frame image.
android:frameDuration="100"
However, I havn’t figured out how to control the speed of the animation :(.
Answer:
Thank @vokilam. This similar solution (a custom view that rotates automatically) uses <animation-list>
dynamically in its implementation:
public class FramesAnimatorView extends AppCompatImageView {
private int framesCount;
private int duration;
private Bitmap frameBitmap;
public FramesAnimatorView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public FramesAnimatorView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public FramesAnimatorView(Context context) { super(context); }
private void init(Context context, AttributeSet attrs) {
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FramesAnimatorView);
framesCount = typedArray.getInt(R.styleable.FramesAnimatorView_framesCount, 12);
duration = typedArray.getInt(R.styleable.FramesAnimatorView_duration, 1200);
typedArray.recycle();
// Method 1: Use <rotate> as Animation (RotateAnimation) and startAnimation() (Rotate view itself).
//method1(framesCount, duration);
// Method 2: Use <rotate> as Drawable (RotateDrawable) and ObjectAnimator. Usable for API 21+ (because of using RotateDrawable.setDrawable).
//method2();
// Method 3 (Recommended): Use <animation-list> (AnimationDrawable) dynamically.
final int frameDuration = this.duration / framesCount;
final AnimationDrawable animationDrawable = (AnimationDrawable) getDrawable();
for (int i = 0; i < framesCount; i++)
animationDrawable.addFrame(
new RotatedDrawable(frameBitmap, i * 360f / framesCount, getResources()),
frameDuration);
animationDrawable.start();
}
@Override public void setImageResource(int resId) { //info();
frameBitmap = BitmapFactory.decodeResource(getResources(), resId);
super.setImageDrawable(new AnimationDrawable());
}
@Override public void setImageDrawable(@Nullable Drawable drawable) { //info();
frameBitmap = drawableToBitmap(drawable);
super.setImageDrawable(new AnimationDrawable());
}
@Override public void setImageBitmap(Bitmap bitmap) { //info();
frameBitmap = bitmap;
super.setImageDrawable(new AnimationDrawable());
}
/**
* See <a href="https://stackoverflow.com/a/21376008/5318303">@android-developer's answer on stackoverflow.com</a>.
*/
private static class RotatedDrawable extends BitmapDrawable {
private final float degrees;
private int pivotX;
private int pivotY;
RotatedDrawable(Bitmap bitmap, float degrees, Resources res) {
super(res, bitmap);
pivotX = bitmap.getWidth() / 2;
pivotY = bitmap.getHeight() / 2;
this.degrees = degrees;
}
@Override public void draw(final Canvas canvas) {
canvas.save();
canvas.rotate(degrees, pivotX, pivotY);
super.draw(canvas);
canvas.restore();
}
}
/**
* See <a href="https://stackoverflow.com/a/10600736/5318303">@André's answer on stackoverflow.com</a>.
*/
@NonNull private static Bitmap drawableToBitmap(Drawable drawable) {
final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
See Android-FramesAnimatorView on GitHub for full (and probably more updated) source code.
Tags: androidandroid, animation, image, list