Question

So, my question is somewhat similar to these two questions:

Custom View onDraw is constantly called

android: onDraw is called constantly

I have a custom class extending ImageView which I apply a RotateAnimation to. The animation uses input x- and y-coordinates to perform a rotation from the last angle to the next so the user can turn the ImageView as wanted, from -360 degrees to 360 degrees. When I use this code for onDraw() everything looks perfect on screen (the animation is set as in the code further below):

@Override
protected void onDraw(Canvas canvas) {
    Log.d(TAG, "It is drawn again!");
    this.setAnimation(anim);
    super.onDraw(canvas);
}

The problem with this is that, in the same manner as in the other posts mentioned, the animation calls onDraw which calls animation and so forth, probably through invalidate() in the RotateAnimation class. Is this correctly observed? The output is perfect since the ImageView always stays at the current calculated angle, but the animation calulations thus keep on going, consuming lots of power and capacity.

To fix this I tried to move the this.setAnimation(anim) in the method where the parameters for the animation are calculated (please ignore isClockWise(), calculateAngleToMove() and other non-android stuff, they work as intended):

private void turnWheel(){
    float angle = 0;

    if ( isClockWise() ){
        angle = calculateAngleToMove();         
        anim = new RotateAnimation(current_angle, angle, center_x, center_y);
        anim.setFillAfter(true);
        anim.setFillEnabled(true);
        current_angle += angle; 
    }
    else{
        angle = - calculateAngleToMove();
        anim = new RotateAnimation(current_angle, angle, center_x, center_y);
        anim.setFillAfter(true);
        anim.setFillEnabled(true);
        current_angle += angle;
    }

    if ( current_angle > 360 ){
        current_angle = current_angle - 360;
    }
    if ( current_angle < -360 ){
        current_angle = current_angle + 360;
    }

    this.setAnimation(anim);
    this.invalidate();  //Calls onDraw()

}

This solves the problem with onDraw constantly being called, but it creates another: when the user pushes, holds and turns the ImageView, it snaps back and forth between zero angle and the current angle. When the user then lets go of the ImageView, it snaps back to zero angle. It is wanted to always have the ImageView rotated at the variable current_angle, also when the user doesn't provide input.

I have tried different versions of anim.setFillAfter(true), anim.setFillEnabled(true), invalidate() and this.startAnimation(anim), but they never seem to have any effect on this problem.

Where is the best place to call this.setAnimation(anim)?

Was it helpful?

Solution

I might recommend not using the Animation framework to apply static transformations to views so that you don't have to deal with the issues caused by resetting when you apply a new Animation each time.

If the contents you wish to rotate just includes imagery, consider wrapping your image(s) in a RotateDrawable and set that as the content of your ImageView. With this you can control how the drawable rotates by setting its level value (either Drawable.setLevel() or ImageView.setImageLevel() should do the trick) and the transformation will stick. If you have a need to automate the animation for a period, you can simply call the level setting code inside of a Handler that posts the updates every so often.

Another option would be to create a custom ViewGroup and use the getChildStaticTransformation() method to apply the rotation to any child view (be sure to enable it or override a subclass that already has it enabled). Transformation is what the Animation framework uses to modify view appearance as well. In this case, you would likely still need to call invalidate() when the user input changed in order to force the re-draw. The same Handler rule would apply if you wanted to automate the changes periodically.

RotateDrawable Example

Let's say the image you are wanting to rotate is at res/drawable/wheel.png, create a simple XML file at res/drawable/wheel_rotate.xml:

<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/wheel"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%" />

To wrap your image into a RotateDrawable. Then, in your Java code set that drawable as the content of an ImageView:

ImageView wheel;
//You only do this once
wheel.setImageResource(R.drawable.wheel_rotate);

//Adjust the progress any time you need to by adjusting the drawable's level
wheel.setImageLevel(500);

By default, a level ranges from 0 to 10,000. So that maps to 0 level = fromDegrees and 10,000 level = toDegrees.

HTH

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top