Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulate animation inside custom view on Android

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: enter image description here Any ideas ?

like image 353
Alin Avatar asked Dec 26 '22 11:12

Alin


1 Answers

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());
like image 194
Kyle Ivey Avatar answered Jan 09 '23 02:01

Kyle Ivey