I have a problem with a game I've created on Android.
My game is working with a lot of ObjectAnimator, ValueAnimator and Thread.
Some users experiment an issue that make the game unplayable : all animations are skipped.
I can reproduce this bug when the Stamina mode (on my Xperia Z3) is activated, but some users have this bug with S7 Edge too. For information, Stamina mode is a battery saver included in all Xperia's phones.
How I can prevent this bug ?
I had a similar issue with my animations. Animations are expensive for the battery, so in battery saver mode the animations are not executed and the app skips the state to the end state. One possibility, which I have used and does render animations is to replace ObjectAnimator and ValueAnimator with custom implementations. The following is an example of a custom circle view I animate to be filled in anti clockwise direction. This animation happens in battery saver mode.
Basically, I find out what the duration of the animation is, then I know the percentage of drawing to be done at every second, I draw that and then clear the canvas and then draw the percentage of drawing in the next second. This happens until I reach the end state, when I do not clear the canvas and retain the end state. The main part is the onDraw method.
public class AnimatedCircleView extends View {
private Interpolator INTERPOLATOR = new AccelerateDecelerateInterpolator();
@IntDef({START, END, ANIMATING})
@Retention(RetentionPolicy.SOURCE)
public @interface State {
}
private static final int START = 0;
private static final int END = 1;
private static final int ANIMATING = 2;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RectF mRect = new RectF();
@State private int mState = START;
private long mAnimationStartTime = 0;
private long mDuration = 0;
private Listener mListener = null;
public AnimatedCircleView(Context context) {
super(context);
}
public AnimatedCircleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void startCircleAnimation(long durationMillis, Listener listener) {
mDuration = durationMillis;
mAnimationStartTime = System.currentTimeMillis();
mState = ANIMATING;
mListener = listener;
invalidate();
}
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRect.left = 0;
mRect.right = w;
mRect.top = 0;
mRect.bottom = h;
}
@Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mState == START) {
// Draw nothing.
return;
}
float percentToDraw;
if (mState == ANIMATING) {
float currentAnimationTime = System.currentTimeMillis() - mAnimationStartTime;//Get how much animation time is remaining
float percentAnimationTime = currentAnimationTime / mDuration;//So now you how much percent you need to draw
if (percentAnimationTime >= 1f) { //If percent is greater than 100%, you have reached the end
percentToDraw = 1f;
mState = END;
if (mListener != null) mListener.onAnimationEnd();
} else {
percentToDraw = INTERPOLATOR.getInterpolation(percentAnimationTime);//Else apply the interpolator
}
} else {
// END state
percentToDraw = 1f;
}
float sweepValue = 360f * percentToDraw; //Multiply the final value of the filled circle with percentToDraw, so you know how much to draw.
float sweepAngle = -sweepValue; //Draw anti clockwise
canvas.drawArc(mRect, -90, sweepAngle, false, mPaint); //Draw on canvas
if (mState == ANIMATING) {
postInvalidateOnAnimation(); // Clear the drawing as animation is happening
}
}
public interface Listener {
void onAnimationEnd();
}
}
Include this custom view in your xml as follows:
<com.example.sunny.BatteryCircle.AnimatedCircleView
android:id="@+id/cvAnimatedCircleView"
android:layout_centerVertical="true"
android:layout_width="45dp"
android:layout_height="45dp" />
Now you can call the animation from the activity as follows:
AnimatedCircleView animatedCircleView = (AnimatedCircleView) findViewById(R.id.cvAnimatedCircleView);
animatedCircleView.startCircleAnimation(2000, new AnimatedCircleView.Listener() {
@Override public void onAnimationEnd() {
}
});