Create "growing ripples" loading animation - OutOfMemoryException - android

I want to display this animation in an Android app:
So I created a <animation-list> to do so. It uses 46 frames, each one consisting of a 200x200px png:
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#drawable/anim_loading_ripple_frame_0" android:duration="45" />
<item android:drawable="#drawable/anim_loading_ripple_frame_1" android:duration="45" />
<item android:drawable="#drawable/anim_loading_ripple_frame_2" android:duration="45" />
<item android:drawable="#drawable/anim_loading_ripple_frame_3" android:duration="45" />
<item android:drawable="#drawable/anim_loading_ripple_frame_4" android:duration="45" />
<item android:drawable="#drawable/anim_loading_ripple_frame_5" android:duration="45" />
...
<item android:drawable="#drawable/anim_loading_ripple_frame_45" android:duration="45" />
</animation-list>
Then I set this as the background of an ImageView and start the animation using
AnimationDrawable loadingAnimation =
(AnimationDrawable) loadingAnimationView.getBackground();
loadingAnimation.start();
Everything works perfect, except for the fact that the app sometimes crashes due to an OutOfMemoryException! It seems that the memory requirements by this kind of animation are quite high, as I've read others experiencing the same issues.
Then I thought I could probably just make each frame into an XML shape drawable, so I tried experimenting, but I can't figure out how to make the circles change size - they always occupy the whole width of the view.
I tried doing something like this
<item>
<shape android:shape="oval">
<padding android:top="30dp" android:right="30dp" android:bottom="30dp" android:left="30dp" />
<size
android:width="100dp"
android:height="100dp" />
<stroke
android:color="#0F0"
android:width="4dp"/>
</shape>
</item>
And also using a layer-list to define another shape in the same XML file but with a different size, thinking that would cause the circle to become smaller, but they both ended up occupying 100% of the view.
Also, I'm not sure if the AnimationDrawable will actually use less memory if I define the frames in XML as opposed to a series of png images? Perhaps I'll still run into the same memory usage issue?
What alternatives do I have? If time wasn't an issue, I'd try to work out a custom View, but it seems like a lot of work just for this animation.

The ScaleAnimation solution provided by Ves will work, but I figured I might as well dive into it and make a proper (and I guess quite more efficient) custom View. Especially when dealing with multiple "ripples".
This is the entire View class, tweaks to the ripples can be made in init() to change colors, number of ripples, etc.
RippleView.java
public class RippleView extends View implements ValueAnimator.AnimatorUpdateListener {
public static final String TAG = "RippleView";
private class Ripple {
AnimatorSet mAnimatorSet;
ValueAnimator mRadiusAnimator;
ValueAnimator mAlphaAnimator;
Paint mPaint;
Ripple(float startRadiusFraction, float stopRadiusFraction, float startAlpha, float stopAlpha, int color, long delay, long duration, float strokeWidth, ValueAnimator.AnimatorUpdateListener updateListener){
mRadiusAnimator = ValueAnimator.ofFloat(startRadiusFraction, stopRadiusFraction);
mRadiusAnimator.setDuration(duration);
mRadiusAnimator.setRepeatCount(ValueAnimator.INFINITE);
mRadiusAnimator.addUpdateListener(updateListener);
mRadiusAnimator.setInterpolator(new DecelerateInterpolator());
mAlphaAnimator = ValueAnimator.ofFloat(startAlpha, stopAlpha);
mAlphaAnimator.setDuration(duration);
mAlphaAnimator.setRepeatCount(ValueAnimator.INFINITE);
mAlphaAnimator.addUpdateListener(updateListener);
mAlphaAnimator.setInterpolator(new DecelerateInterpolator());
mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(mRadiusAnimator, mAlphaAnimator);
mAnimatorSet.setStartDelay(delay);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(color);
mPaint.setAlpha((int)(255*startAlpha));
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(strokeWidth);
}
void draw(Canvas canvas, int centerX, int centerY, float radiusMultiplicator){
mPaint.setAlpha( (int)(255*(float)mAlphaAnimator.getAnimatedValue()) );
canvas.drawCircle(centerX, centerY, (float)mRadiusAnimator.getAnimatedValue()*radiusMultiplicator, mPaint);
}
void startAnimation(){
mAnimatorSet.start();
}
void stopAnimation(){
mAnimatorSet.cancel();
}
}
private List<Ripple> mRipples = new ArrayList<>();
public RippleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public RippleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public RippleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if( isInEditMode() )
return;
/*
Tweak your ripples here!
*/
mRipples = new ArrayList<>();
mRipples.add(new Ripple(0.0f, 1.0f, 1.0f, 0.0f, Color.RED, 0, 2000, 4, this));
mRipples.add(new Ripple(0.0f, 1.0f, 1.0f, 0.0f, Color.WHITE, 500, 2000, 4, this));
}
public void startAnimation() {
setVisibility(View.VISIBLE);
for (Ripple ripple : mRipples) {
ripple.startAnimation();
}
}
public void stopAnimation() {
for (Ripple ripple : mRipples) {
ripple.stopAnimation();
}
setVisibility(View.GONE);
}
#Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
int centerX = getWidth()/2;
int centerY = getHeight()/2;
int radiusMultiplicator = getWidth()/2;
for (Ripple ripple : mRipples) {
ripple.draw(canvas, centerX, centerY, radiusMultiplicator);
}
}
}
Just call .startAnimation() on the RippleView to start the animation.
RippleView r = findViewById(R.id.rippleView);
r.startAnimation();

You can use ScaleAnimation to change your size. I created an ImageView and used the shape you defined above:
<ImageView
android:id="#+id/circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/circle"/>
It is slightly different than you wanted because I did not bother to find the center point to set the animation. It is scaling from the upper left corner. (0,0)
ImageView iv = findViewById(R.id.circle);
float startScale = 0.5f;
float endScale = 3.0f;
Animation scaleAnim = new ScaleAnimation( startScale, endScale,
startScale, endScale,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 0);
scaleAnim.setRepeatCount(Animation.INFINITE);
scaleAnim.setRepeatMode(Animation.REPEAT);
scaleAnim.setDuration(2000);
iv.setAnimation(scaleAnim);
scaleAnim.start();
You can similarly animation the color using a ValueAnimation.

Related

How to set strike line of text view with red color in Android [duplicate]

This question already has an answer here:
How to change color of a strikethrough in android? [duplicate]
(1 answer)
Closed 7 years ago.
Using this code I am able to display text view with strike to textView :
holder.discounttext.setText("MRP " + rupee + discountcost);
holder.discounttext.setPaintFlags(holder.discounttext.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
This is my Current Screen :
Desire Screen:
Please tell me how to set strike color with red?
Make a custom TextView
class CustomTextView extends TextView {
public Paint paint;
public boolean addStrike = false;
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTextView(Context context) {
super(context);
init(context);
}
public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
paint = new Paint();
paint.setColor(Color.RED);
paint.setStrokeWidth(getResources().getDisplayMetrics().density * 1);
}
#Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if (addStrike) {
canvas.drawLine(0, getHeight() / 2, getWidth(),
getHeight() / 2, paint);
}
}
}
for adding stoke you can call
myCustomTextView.addStrike = true;
myCustomTextView.invalidate();
and for removing strike you just call
myCustomTextView.addStrike = false;
myCustomTextView.invalidate();
The easiest way is to use Drawable. The idea is to create a line drawable, and then set the background of TextView with this drawable. Let the Drawable file be line.xml
<item android:state_pressed="false">
<shape android:shape="line">
<stroke android:width="2dp"
android:color="#ffffff" />
</shape>
</item>
This is the drawable for the line with red color, and 2dp width. [Alter it according to your requirements]
Then on the TextView, set the line.xml drawable as background.
holder.discounttext.setBackground(getResources().getDrawable(R.drawable.line));
The output will be
A couple of solutions:
subclass textview and draw your own strike through. See create different color strike through.
overlay another view on top of it.

Rotating an image around a specified point doesn't work! (Android)

I'm rotating an ImageView with postRotate(float degrees, float px, float py), setting px and py to a few differnt values including (0,0) and (imgView.getHeight(),imgView.getWidth()) but it refuses to rotate around any other point than the center. I'm wondering whether it's got anything to do with the fact that my gravity is center in the LinearLayout?
My layout:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center">
<ImageView
android:id="#+id/imageTraj"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="matrix"
android:src="#drawable/impactangle" />
How I'm rotating the image:
matrix.postRotate(degrees,imageView.getHeight(),imageView.getWidth());
imageView.setImageBitmap(Bitmap.createBitmap(imageScaled, 0, 0,
imageScaled.getWidth(), imageScaled.getHeight(), matrix, true));
PS I've noticed there are a few similar questions, but none of them have suitable answers.
I am using a custom ImageView where I set the angle rotation.
public class CompassImage extends ImageView {
private float angleRotation;
public CompassImage(Context context) {
super(context);
}
public CompassImage(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CompassImage(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setAngleRotation(float angleRotation) {
this.angleRotation = angleRotation;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
Rect clipBounds = canvas.getClipBounds();
canvas.save();
canvas.rotate(angleRotation, clipBounds.exactCenterX(), clipBounds.exactCenterY());
super.onDraw(canvas);
canvas.restore();
}
}
If you play around with clipBounds you may find that helpful.
A slight "hack", you could probably adjust the image size using something like Paint.net or Gimp, depending on your OS. That would make the image appear to spin on another point. This solution would be pointless if you are planning on using a lot of images though. This is a spinning cube tutorial using opengl.

Android animation

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).

Is it possible to have an animated drawable?

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>

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

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.

Categories

Resources