Home » Android » Rotating image. Animation list or animated rotate? (Android)

Rotating image. Animation list or animated rotate? (Android)

Posted by: admin April 23, 2020 Leave a comment

Questions:

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?

How to&Answers:

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:

gif_icon

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:

progress_icon

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

  1. Incremental
    Demonstrates large and small rotating progress indicators that can be incremented or decremented in units.
  2. Smooth
    Demonstrates large and small continuously rotating progress indicators used to indicate a generic “busy” message.
  3. Dialogs
    Demonstrates a ProgressDialog, a popup dialog that hosts a progress bar. This example demonstrates both determinate and indeterminate progress indicators.
  4. 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"
android:frameDuration="100"
attributes for those the compiler complains. It still works even for my 8-frame image.

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.