Is it possible to create a drawable that has some sort of animation, whether it is a frame by frame animation, rotation, etc, that is defined as a xml drawable and can be represented by a single Drawable object without having to deal with the animation in code?
How I am thinking to use it:
I have a list and each item in this list may at sometime have something happening to it. While it is happening, I would like to have a spinning progress animation similar to a indeterminate ProgressBar. Since there may also be several of these on screen I thought that if they all shared the same Drawable they would only need one instance of it in memory and their animations would be synced so you wouldn't have a bunch of spinning objects in various points in the spinning animation.
I'm not attached to this approach. I'm just trying to think of the most efficient way to display several spinning progress animations and ideally have them synced together so they are consistent in appearance.
Thanks
In response to Sybiam's answer:
I have tried implementing a RotateDrawable but it is not rotating.
Here is my xml for the drawable so far:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/my_drawable_to_rotate"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="800"
android:visible="true" />
I have tried using that drawable as the src and background of a ImageView and both ways only produced a non-rotating image.
Is there something that has to start the image rotation?
Yes! The (undocumented) key, which I discovered by reading the ProgressBar code is that you have to call Drawable.setLevel() in onDraw() in order for the <rotate> thing to have any effect. The ProgressBar works something like this (extra unimportant code omitted):
The drawable XML:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<rotate
android:drawable="#drawable/spinner_48_outer_holo"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="0"
android:toDegrees="1080" />
</item>
<item>
<rotate
android:drawable="#drawable/spinner_48_inner_holo"
android:pivotX="50%"
android:pivotY="50%"
android:fromDegrees="720"
android:toDegrees="0" />
</item>
</layer-list>
In onDraw():
Drawable d = getDrawable();
if (d != null)
{
// Translate canvas so a indeterminate circular progress bar with
// padding rotates properly in its animation
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop());
long time = getDrawingTime();
// I'm not sure about the +1.
float prog = (float)(time % ANIM_PERIOD+1) / (float)ANIM_PERIOD;
int level = (int)(MAX_LEVEL * prog);
d.setLevel(level);
d.draw(canvas);
canvas.restore();
ViewCompat.postInvalidateOnAnimation(this);
}
MAX_LEVEL is a constant, and is always 10000 (according to the docs).
ANIM_PERIOD is the period of your animation in milliseconds.
Unfortunately since you need to modify onDraw() you can't just put this drawable in an ImageView since ImageView never changes the drawable level. However you may be able to change the drawable level from outside the ImageView's. ProgressBar (ab)uses an AlphaAnimation to set the level. So you'd do something like this:
mMyImageView.setImageDrawable(myDrawable);
ObjectAnimator anim = ObjectAnimator.ofInt(myDrawable, "level", 0, MAX_LEVEL);
anim.setRepeatCount(ObjectAnimator.INFINITE);
anim.start();
It might work but I haven't tested it.
Edit
There is actually an ImageView.setImageLevel() method so it might be as simple as:
ObjectAnimator anim = ObjectAnimator.ofInt(myImageVew, "ImageLevel", 0, MAX_LEVEL);
anim.setRepeatCount(ObjectAnimator.INFINITE);
anim.start();
Drawables
There you go! And this one for RotateDrawable. I believe that from the Doc it should be pretty straitght forward. You can define everything in a xml file and set the background of a view as the drawable xml. /drawable/myrotate.xml -> #drawable/myrotate
Edit:
This is an answer I found here.
Drawable Rotating around its center Android
Edit 2:
You are right the RotateDrawable seem broken. I don't know I tried it too. I haven't yet succeded in making it animate. But I did succed to rotate it. You have to use setLevel which will rotate it. Though it doesn't look really useful. I browsed the code and the RotateDrawable doesn't even inflate the animation duration and the current rotation seems strangely use the level as a measure for rotation. I believe you have to use it with a AnimationDrawable but here again. It just crashed for me. I haven't used that feature yet but planned to use it in the future. I browsed the web and the RotateDrawable seems to be very undocumented like almost every Drawable objects.
Here is one of possible ways (especially useful when you have a Drawable somewhere set and need to animate it). The idea is to wrap the drawable and decorate it with animation. In my case, I had to rotate it, so below you can find sample implementation:
public class RotatableDrawable extends DrawableWrapper {
private float rotation;
private Rect bounds;
private ObjectAnimator animator;
private long defaultAnimationDuration;
public RotatableDrawable(Resources resources, Drawable drawable) {
super(vectorToBitmapDrawableIfNeeded(resources, drawable));
bounds = new Rect();
defaultAnimationDuration = resources.getInteger(android.R.integer.config_mediumAnimTime);
}
#Override
public void draw(Canvas canvas) {
copyBounds(bounds);
canvas.save();
canvas.rotate(rotation, bounds.centerX(), bounds.centerY());
super.draw(canvas);
canvas.restore();
}
public void rotate(float degrees) {
rotate(degrees, defaultAnimationDuration);
}
public void rotate(float degrees, long millis) {
if (null != animator && animator.isStarted()) {
animator.end();
} else if (null == animator) {
animator = ObjectAnimator.ofFloat(this, "rotation", 0, 0);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
}
animator.setFloatValues(rotation, degrees);
animator.setDuration(millis).start();
}
#AnimatorSetter
public void setRotation(float degrees) {
this.rotation = degrees % 360;
invalidateSelf();
}
/**
* Workaround for issues related to vector drawables rotation and scaling:
* https://code.google.com/p/android/issues/detail?id=192413
* https://code.google.com/p/android/issues/detail?id=208453
*/
private static Drawable vectorToBitmapDrawableIfNeeded(Resources resources, Drawable drawable) {
if (drawable instanceof VectorDrawable) {
Bitmap b = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
drawable.setBounds(0, 0, c.getWidth(), c.getHeight());
drawable.draw(c);
drawable = new BitmapDrawable(resources, b);
}
return drawable;
}
}
and you can use it like this (rotating toolbar navigation icon 360 degrees):
backIcon = new RotatableDrawable(getResources(), toolbar.getNavigationIcon().mutate());
toolbar.setNavigationIcon(backIcon);
backIcon.rotate(360);
It shouldn't be hard to add a method that will rotate it indefinite (setRepeatMode INFINITE for animator)
You can start from studying the ProgressBar2 from API Demos project (it is available as a part of the SDK). Specifically pay attention to R.layout.progressbar_2.
I take back to life this post just to post my solution with vector drawable:
So you need 1 vector drawable in drawable resource (#drawable/ic_brush_24dp):
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<group
android:name="rotation" <---- target name of animated-vector
android:pivotX="12.0"
android:pivotY="12.0"
android:rotation="0.0"> <--- propertyName of animator
<path
android:fillColor="#FF000000"
android:pathData="M7,14c-1.66,0 -3,1.34 -3,3 0,1.31 -1.16,2 -2,2 0.92,1.22 2.49,2 4,2 2.21,0 4,-1.79 4,-4 0,-1.66 -1.34,-3 -3,-3zM20.71,4.63l-1.34,-1.34c-0.39,-0.39 -1.02,-0.39 -1.41,0L9,12.25 11.75,15l8.96,-8.96c0.39,-0.39 0.39,-1.02 0,-1.41z" />
</group>
</vector>
Then you need your animator in animator resource folder (#animator/pendulum)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="350"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:propertyName="rotation"
android:valueFrom="0.0"
android:valueTo="-90.0" />
</set>
Then you need your animated-vector in drawable resource (#drawable/test_anim_brush2):
<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="#drawable/ic_brush_24dp">
<target
android:name="rotation"
android:animation="#animator/pendulum" />
</animated-vector>
Then you need to extend ImageView (because that's the only way i found to start the animation)
public class ImageView extends AppCompatImageView{
public ImageView(Context context){
super(context);
init(context, null, 0);
}
public ImageView(Context context, AttributeSet attrs){
super(context, attrs);
init(context, attrs, 0);
}
public ImageView(Context context, AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
init(context, attrs, 0);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr){
}
#Override
protected void onAttachedToWindow(){
super.onAttachedToWindow();
Drawable d = getBackground();
if(d instanceof AnimatedVectorDrawable){
AnimatedVectorDrawable anim = (AnimatedVectorDrawable)d;
anim.start();
}
}
#Override
protected void onDetachedFromWindow(){
super.onDetachedFromWindow();
Drawable d = getBackground();
if(d instanceof AnimatedVectorDrawable){
AnimatedVectorDrawable anim = (AnimatedVectorDrawable)d;
anim.stop();
}
}
}
And then add your imageView to your layout :
<ui.component.ImageView
android:id="#+id/test_anim_brush"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center_horizontal"
android:background="#drawable/test_anim_brush2"/>
Pro :
You can fully define your animation in xml with object animator (duration, interpolator, etc...)
Works with everything which accept drawable (as long as you add at good place the start animator)
Con:
Works only with vector
Still need to add your custom start by extending class or whenever you think it is smart...
private ValueAnimator rotateDrawable(RotateDrawable drawable, int fromDegree, int toDegree) {
drawable.setFromDegrees(fromDegree);
drawable.setToDegrees(toDegree);
return ObjectAnimator.ofInt(drawable, "level", 0, 10000);
}
level is the interpulting value from 0 to 100000. the actual animation values are set by the setter methods
you can use stateListAnimator
<ImageView
android:id="#+id/your_id"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="#drawable/your_drawable"
android:stateListAnimator="#anim/bounce" />
and bounce
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/bounce">
<translate
android:duration="900"
android:fromXDelta="100%p"
android:toXDelta="0%p" />
</set>
Related
I've tried the custom view and tilt the image to 45 degree successfully but I want to tilt the whole child layoout at some angle so that whatever I'll add in that child layout will automactically tiled .. For reference please take a look at the attached picture
even I have tried this code but it wont help
public class CustomLinear extends LinearLayout{
private Matrix mForward = new Matrix();
private Matrix mReverse = new Matrix();
private float[] mTemp = new float[2];
public CustomLinear(Context context) {
super(context);
}
public CustomLinear(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void dispatchDraw(Canvas canvas) {
canvas.rotate(75, getWidth() / 2, getHeight() / 2);
mForward = canvas.getMatrix();
mForward.invert(mReverse);
canvas.save();
canvas.setMatrix(mForward); // This is the matrix we need to use for
// proper positioning of touch events
super.dispatchDraw(canvas);
canvas.restore();
invalidate();
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
event.setLocation(getWidth() - event.getX(), getHeight() - event.getY());
return super.dispatchTouchEvent(event);
}
}
I thing it is possible using rotate animation. you can apply animation on the whole child layout with 0 duration in On_create.
Here is the animation code
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="-90"
android:pivotX="50%"
android:pivotY="50%"
android:duration="0"
android:fillAfter="true">
</rotate>
You can set the rotation angle as per your need.
Then use given code in your Activity's onCreate,
#Override
public void onCreate(Bundle b) {
super.onCreate(b);
setContentView(R.layout.myscreen);
Animation rotate_anim = AnimationUtils.loadAnimation(this, R.anim.rotation);
LayoutAnimationController anim_controller = new LayoutAnimationController(rotate_anim, 0);
FrameLayout layout = (FrameLayout)findViewById(R.id.MyScreen_ContentLayout);
layout.setLayoutAnimation(anim_controller);
}
Make custom (tilted/rotated) xml views and call it all in the main view.
Also, take a look at the following link this may help you.
http://mindfreakconcepts.blogspot.com/2013/03/how-to-rotate-listview-items-in-android.html
I could be wrong but from my understanding you have to use animation to get the effect that you want instead of draw. There is a similar thread, here that might be worth checking out.
I am trying to build a LayerDrawable in xml where upper layers will occasionally completely obscure lower layers. To make the lower layers smaller, I am using an InsetDrawable to wrap another drawable to make it smaller than the full size of the view. I find unexpectedly, however, that any layers placed on top of the layer containing the inset also has the inset applied to it. I can't find documentation supporting this behavior, and am confused why this would be the case.
In the example below, I make a LayerDrawable with 3 layers. The bottom and top layers contain oval shape drawables that are meant to take up the entire view. The middle layer is a rectangle drawable inside of an InsetDrawable. The code is below:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<shape android:shape="oval" >
<solid android:color="#00ff00" />
</shape>
</item>
<item>
<inset
android:insetBottom="4dp"
android:insetLeft="4dp"
android:insetRight="4dp"
android:insetTop="4dp" >
<shape android:shape="rectangle" >
<solid android:color="#ff0000" />
</shape>
</inset>
</item>
<item>
<shape android:shape="oval" >
<solid android:color="#0000ff" />
</shape>
</item>
</layer-list>
Calling setBackgroundDrawable(getResources().getDrawable(drawableId)); in my view produces a green oval that fills the entire view as expected, with a red rectangle inset 4dp as expected, but the blue oval on the top layer is also inset 4dp and drawn completely within the bounds of the red rectangle.
I would expect the blue oval to completely obscure the green oval and most of the red rectangle, but instead it is inset inside the red rectangle. Is there any way to make the blue circle fill the view yet keep it on top?
I also don't see where it is documented, but padding in a LayerDrawable is cumulative. That is, padding at one layer affects the bounds of all higher layers. This is from the source for LayerDrawable:
#Override
protected void onBoundsChange(Rect bounds) {
final ChildDrawable[] array = mLayerState.mChildren;
final int N = mLayerState.mNum;
int padL=0, padT=0, padR=0, padB=0;
for (int i=0; i<N; i++) {
final ChildDrawable r = array[i];
r.mDrawable.setBounds(bounds.left + r.mInsetL + padL,
bounds.top + r.mInsetT + padT,
bounds.right - r.mInsetR - padR,
bounds.bottom - r.mInsetB - padB);
padL += mPaddingL[i];
padR += mPaddingR[i];
padT += mPaddingT[i];
padB += mPaddingB[i];
}
}
(LayerDrawable.getPadding(Rect) follows the same logic.) Since an InsetDrawable uses its insets as padding (as documented), this explains the behavior you're seeing.
I think this is a poor design decision, but you're kind of stuck with it, I'm afraid. I don't think it can be overridden.
Ted's answer is the best answer, but I'll share this workaround that helped me. I was specifically having padding problems with a TextView, so I made a custom TextView instead which ignores the background drawable's padding.
public class HackTextView extends TextView {
public HackTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public HackTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HackTextView(Context context) {
super(context);
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public void setBackground(Drawable background) {
super.setBackground(hackDrawable(background));
}
#Override
public void setBackgroundDrawable(Drawable background) {
super.setBackgroundDrawable(hackDrawable(background));
}
private Drawable hackDrawable(Drawable background){
return new LayerDrawable(new Drawable[]{background}){
#Override
public boolean getPadding(Rect padding) {
padding.set(0, 0, 0, 0);
return false;
}
};
}
}
I wrote a suduko game for android, and want to animate tile, if the user insert wrong number.
But I don't figure how to do this? The problem is that, I have one big rectangel wich cover hole screen, and devide this rectangle in tiles, simply by drawnig lines. And now I can't figure how to animate tiles.
Hop I could explain my problem.
Sorry for bad english, and thaks for answers)
You should not draw your board like that. I would recommend to implement one tile as a subclass of View so you can then animate each one individually.
Try this to see how animation works:
Tile class
public class Tile extends View {
private RectF mRect;
private Paint mPaint;
public Tile(Context context) {
super(context);
init();
}
public Tile(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Tile(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init(){
mRect = new RectF(0, 0, 100, 100);
mPaint = new Paint();
mPaint.setStyle( Paint.Style.STROKE );
mPaint.setColor( Color.BLUE );
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(mRect, mPaint);
}
}
Main activity
public class MainActivity extends Activity implements OnClickListener {
private Tile mTile;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout layout = new LinearLayout( this );
layout.setBackgroundColor( Color.WHITE );
layout.setPadding(50, 50, 50, 50);
Button btn = new Button( this );
btn.setText( "Click Me" );
btn.setOnClickListener( this );
layout.addView( btn );
mTile = new Tile( this );
layout.addView( mTile );
setContentView( layout );
}
#Override
public void onClick(View v) {
Animation scaleAnim = AnimationUtils.loadAnimation(this, R.anim.scale);
mTile.startAnimation( scaleAnim );
}
}
Animation definition (This file should be named scale.xml and placed under directory /res/anim )
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<scale
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="2.0"
android:fromYScale="1.0"
android:toYScale="2.0"
android:fillAfter="false"
android:repeatCount="1"
android:repeatMode="reverse"
android:duration="700" />
</set>
Learn more about animations here.
To make your own animation have a look here here.
Hope this keeps you going.
If you use standard views, you could look into tween animation, i.e. define a set of animations, load them from the resources and attach them to your views / start the animation.
Have a look at the spaceship jump example here.
If you do custom drawing, I'm afraid you also need to do custom animation.
Working with animation on anything before Honeycomb is a real pain. I recommend downloading the NineOldAndroids library (http://nineoldandroids.com/) and using it as imports, that way you can use the Honeycomb animation API on all versions of android (even up to 1.0).
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
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" />
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!
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.
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 :(.
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 #android-developer's answer on stackoverflow.com.
*/
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 #André's answer on stackoverflow.com.
*/
#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.
I'm using a RotateAnimation to rotate an image that I'm using as a custom cyclical spinner in Android. Here's my rotate_indefinitely.xml file, which I placed in res/anim/:
<?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"
android:duration="1200" />
When I apply this to my ImageView using AndroidUtils.loadAnimation(), it works great!
spinner.startAnimation(
AnimationUtils.loadAnimation(activity, R.anim.rotate_indefinitely) );
The one problem is that the image rotation seems to pause at the top of every cycle.
In other words, the image rotates 360 degrees, pauses briefly, then rotates 360 degrees again, etc.
I suspect that the problem is that the animation is using a default interpolator like android:iterpolator="#android:anim/accelerate_interpolator" (AccelerateInterpolator), but I don't know how to tell it not to interpolate the animation.
How can I turn off interpolation (if that is indeed the problem) to make my animation cycle smoothly?
You are right about AccelerateInterpolator; you should use LinearInterpolator instead.
You can use the built-in android.R.anim.linear_interpolator from your animation XML file with android:interpolator="#android:anim/linear_interpolator".
Or you can create your own XML interpolation file in your project, e.g. name it res/anim/linear_interpolator.xml:
<?xml version="1.0" encoding="utf-8"?>
<linearInterpolator xmlns:android="http://schemas.android.com/apk/res/android" />
And add to your animation XML:
android:interpolator="#anim/linear_interpolator"
Special Note: If your rotate animation is inside a set, setting the interpolator does not seem to work. Making the rotate the top element fixes it. (this will save your time.)
I had this problem as well, and tried to set the linear interpolator in xml without success. The solution that worked for me was to create the animation as a RotateAnimation in code.
RotateAnimation rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(5000);
rotate.setInterpolator(new LinearInterpolator());
ImageView image= (ImageView) findViewById(R.id.imageView);
image.startAnimation(rotate);
This works fine
<?xml version="1.0" encoding="UTF-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1600"
android:fromDegrees="0"
android:interpolator="#android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:toDegrees="358" />
To reverse rotate:
<?xml version="1.0" encoding="UTF-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1600"
android:fromDegrees="358"
android:interpolator="#android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:toDegrees="0" />
Maybe, something like this will help:
Runnable runnable = new Runnable() {
#Override
public void run() {
imageView.animate().rotationBy(360).withEndAction(this).setDuration(3000).setInterpolator(new LinearInterpolator()).start();
}
};
imageView.animate().rotationBy(360).withEndAction(runnable).setDuration(3000).setInterpolator(new LinearInterpolator()).start();
By the way, you can rotate by more than 360 like:
imageView.animate().rotationBy(10000)...
Try using toDegrees="359" since 360° and 0° are the same.
ObjectAnimator.ofFloat(view, View.ROTATION, 0f, 360f).setDuration(300).start();
Try this.
Rotation Object programmatically.
// clockwise rotation :
public void rotate_Clockwise(View view) {
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 180f, 0f);
// rotate.setRepeatCount(10);
rotate.setDuration(500);
rotate.start();
}
// AntiClockwise rotation :
public void rotate_AntiClockwise(View view) {
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 180f);
// rotate.setRepeatCount(10);
rotate.setDuration(500);
rotate.start();
}
view is object of your ImageView or other widgets.
rotate.setRepeatCount(10); use to repeat your rotation.
500 is your animation time duration.
Pruning the <set>-Element that wrapped the <rotate>-Element solves the problem!
Thanks to Shalafi!
So your Rotation_ccw.xml should loook like this:
<?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:duration="2000"
android:fillAfter="false"
android:startOffset="0"
android:repeatCount="infinite"
android:interpolator="#android:anim/linear_interpolator"
/>
In Kotlin:
ivBall.setOnClickListener(View.OnClickListener {
//Animate using XML
// val rotateAnimation = AnimationUtils.loadAnimation(activity, R.anim.rotate_indefinitely)
//OR using Code
val rotateAnimation = RotateAnimation(
0f, 359f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
)
rotateAnimation.duration = 300
rotateAnimation.repeatCount = 2
//Either way you can add Listener like this
rotateAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation?) {
}
override fun onAnimationRepeat(animation: Animation?) {
}
override fun onAnimationEnd(animation: Animation?) {
val rand = Random()
val ballHit = rand.nextInt(50) + 1
Toast.makeText(context, "ballHit : " + ballHit, Toast.LENGTH_SHORT).show()
}
})
ivBall.startAnimation(rotateAnimation)
})
As hanry has mentioned above putting liner iterpolator is fine. But if rotation is inside a set you must put android:shareInterpolator="false" to make it smooth.
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
**android:shareInterpolator="false"**
>
<rotate
android:interpolator="#android:anim/linear_interpolator"
android:duration="300"
android:fillAfter="true"
android:repeatCount="10"
android:repeatMode="restart"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%" />
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/linear_interpolator"
android:duration="3000"
android:fillAfter="true"
android:pivotX="50%"
android:pivotY="50%"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="0"
android:toYScale="0" />
</set>
If Sharedinterpolator being not false, the above code gives glitches.
If you are using a set Animation like me you should add the interpolation inside the set tag:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#android:anim/linear_interpolator">
<rotate
android:duration="5000"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
android:startOffset="0"
android:toDegrees="360" />
<alpha
android:duration="200"
android:fromAlpha="0.7"
android:repeatCount="infinite"
android:repeatMode="reverse"
android:toAlpha="1.0" />
</set>
That Worked for me.
No matter what I tried, I couldn't get this to work right using code (and setRotation) for smooth rotation animation. What I ended up doing was making the degree changes so small, that the small pauses are unnoticeable. If you don't need to do too many rotations, the time to execute this loop is negligible. The effect is a smooth rotation:
float lastDegree = 0.0f;
float increment = 4.0f;
long moveDuration = 10;
for(int a = 0; a < 150; a++)
{
rAnim = new RotateAnimation(lastDegree, (increment * (float)a), Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rAnim.setDuration(moveDuration);
rAnim.setStartOffset(moveDuration * a);
lastDegree = (increment * (float)a);
((AnimationSet) animation).addAnimation(rAnim);
}
Try to use more than 360 to avoid restarting.
I use 3600 insted of 360 and this works fine for me:
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="3600"
android:interpolator="#android:anim/linear_interpolator"
android:repeatCount="infinite"
android:duration="8000"
android:pivotX="50%"
android:pivotY="50%" />
Here is code snippet that worked fine for me:
RotateAnimation rotate = new RotateAnimation(
0, 359,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
);
rotate.setDuration(1500);
rotate.setRepeatCount(Animation.INFINITE);
yourView.startAnimation(rotate);
Check it's not 360 it's 359 in the constructor as 0 and 360 are at the same point.
Is it possible that because you go from 0 to 360, you spend a little bit more time at 0/360 than you are expecting? Perhaps set toDegrees to 359 or 358.
private fun rotateTheView(view: View?, startAngle: Float, endAngle: Float) {
val rotate = ObjectAnimator.ofFloat(view, "rotation", startAngle, endAngle)
//rotate.setRepeatCount(10);
rotate.duration = 400
rotate.start()
}
The 100% correct answer
var rotate = RotateAnimation(
0F, 360F,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
)
rotate.duration = 999
rotate.repeatCount = Animation.INFINITE
imageview..setAnimation(rotate)
100% working!!!
In Android, if you want to animate an object and make it move an object from location1 to location2, the animation API figures out the intermediate locations (tweening) and then queues onto the main thread the appropriate move operations at the appropriate times using a timer. This works fine except that the main thread is usually used for many other things — painting, opening files, responding to user inputs etc. A queued timer can often be delayed. Well written programs will always try to do as many operations as possible in background (non main) threads however you can’t always avoid using the main thread. Operations that require you to operate on a UI object always have to be done on the main thread. Also, many APIs will funnel operations back to the main thread as a form of thread-safety.
Views are all drawn on the same GUI thread which is also used for all user interaction.
So if you need to update GUI rapidly or if the rendering takes too much time and affects user experience then use SurfaceView.
Example of rotation image:
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private DrawThread drawThread;
public MySurfaceView(Context context) {
super(context);
getHolder().addCallback(this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
drawThread = new DrawThread(getHolder(), getResources());
drawThread.setRunning(true);
drawThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
drawThread.setRunning(false);
while (retry) {
try {
drawThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
}
class DrawThread extends Thread{
private boolean runFlag = false;
private SurfaceHolder surfaceHolder;
private Bitmap picture;
private Matrix matrix;
private long prevTime;
public DrawThread(SurfaceHolder surfaceHolder, Resources resources){
this.surfaceHolder = surfaceHolder;
picture = BitmapFactory.decodeResource(resources, R.drawable.icon);
matrix = new Matrix();
matrix.postScale(3.0f, 3.0f);
matrix.postTranslate(100.0f, 100.0f);
prevTime = System.currentTimeMillis();
}
public void setRunning(boolean run) {
runFlag = run;
}
#Override
public void run() {
Canvas canvas;
while (runFlag) {
long now = System.currentTimeMillis();
long elapsedTime = now - prevTime;
if (elapsedTime > 30){
prevTime = now;
matrix.preRotate(2.0f, picture.getWidth() / 2, picture.getHeight() / 2);
}
canvas = null;
try {
canvas = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(picture, matrix, null);
}
}
finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
activity:
public class SurfaceViewActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MySurfaceView(this));
}
}
you can use this code:
view.animate().rotation(360.0f).setDuration(1000);