I have a custom view which looks like a spinning wheel with numbers from 0 to 9. Basically it scrolls down from 0 to 9 while I am downloading something from the server and when the value is returned, the animation stops on it. The animation is made by updating the Y value of the text I am drawing
Some relevant code:
public class TimeSpinner extends View
{
.....
private void initialize()
{
.....
handler = new Handler();
repetitiveRunnable = new Runnable() {
@Override
public void run() {
updateData();
}
};
}
public void updateData() {
float delta = 3;
mDigitY += delta;
mDigitAboveY += delta;
mDigitBelowY += delta;
//Test if animation needs to stop
if (mCurrentDigit == animateToDigit) {
handler.removeCallbacks(repetitiveRunnable);
} else {
handler.postDelayed(repetitiveRunnable, 5);
}
invalidate();
}
public void startLoadingDigit() {
handler.postDelayed(repetitiveRunnable, 5);
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawText(mDigitString, mDigitX, mDigitY, mDigitPaint);
canvas.drawText(mDigitAboveString, mDigitX, mDigitAboveY, mDigitPaint);
canvas.drawText(mDigitBelowString, mDigitX, mDigitBelowY, mDigitPaint);
}
}
The problem is the UI thread has some drawing to do on the other views so depending on phone's speed the animation is not fluid. Is like having bad frame rate or sometimes it stops for half of second. On powerful devices is reasonable good.
Now the question, what can I do make the animation smooth independent of what the app is doing ? The View is simple, not a SurfaceView. Should I use a thread somehow ? A code sample would be great.
Later edit.
I tried using an AsycTask for updating the Y coordinate
public class TimeSpinnerTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... arg0) {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
while (animationRunning) {
updateData();
publishProgress();
try {
Thread.sleep(35);
} catch (InterruptedException e) {
}
}
return null;
}
@Override
protected void onProgressUpdate(Void... values) {
invalidate();
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Void result) {
invalidate();
super.onPostExecute(result);
}
}
public void updateData() {
mDigitY += delta;
mDigitAboveY += delta;
mDigitBelowY += delta;
if (mDigitAboveY > findCenterY(mCurrentDigit)) {
setCurrentDigit(mDigitAbove);
}
if (mCurrentDigit == animateToDigit) {
animationRunning = false;
setCurrentDigit(animateToDigit);
}
}
While this does seem to be a bit smoother, on older devices I still get shutter, sometimes the animation even pauses for 1 seconds before continuing.
The asynctask is started with executeOnExecutor
and the view is shown over a Map control from Android Maps V2. The shuttering appears especially when the map is loading tiles.
Maybe I didn't go with the best approach. Here is the final result I want to obtain: Any ideas ?
Have you tried using a ValueAnimator
? This is designed for managing animations similar to yours. It will take care of the threading and framerate for you.
// Create a new value animator that will use the range 0 to 1
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
// It will take 5000ms for the animator to go from 0 to 1
animator.setDuration(5000);
// Callback that executes on animation steps.
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = ((Float) (animation.getAnimatedValue())).floatValue();
Log.d("ValueAnimator", "value=" + value);
// Here you can now translate or redraw your view
// You need to map 'value' to your animation in regards to time
// eg) mDigitY = value; invalidate();
}
});
You can also probably achieve that elastic effect at the end of the animation by using an OvershootInterpolator
.
animator.setInterpolator(new OvershootInterpolator());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With