How to change NumberPicker's value with animation?

I solved it via refelction

        /**
         * using reflection to change the value because
         * changeValueByOne is a private function and setValue
         * doesn't call the onValueChange listener.
         * 
         * @param higherPicker
         *            the higher picker
         * @param increment
         *            the increment
         */
        private void changeValueByOne(final NumberPicker higherPicker, final boolean increment) {

            Method method;
            try {
                // refelction call for
                // higherPicker.changeValueByOne(true);
                method = higherPicker.getClass().getDeclaredMethod("changeValueByOne", boolean.class);
                method.setAccessible(true);
                method.invoke(higherPicker, increment);

            } catch (final NoSuchMethodException e) {
                e.printStackTrace();
            } catch (final IllegalArgumentException e) {
                e.printStackTrace();
            } catch (final IllegalAccessException e) {
                e.printStackTrace();
            } catch (final InvocationTargetException e) {
                e.printStackTrace();
            }
        }

works perfect. I don't know why this method is private


I don't believe that this is natively supported. I thought of a 'messy' way of doing it manually:

You can use the NumberPicker's scrollBy(int x, int y) function called iteratively to make the effect of the animation.

Some things to take into account:

  • scrollBy(x,y) works with pixels. As Android has got all that different screen densities, what you should do is first guess (I'd do it by trial and error) the 'dp' distance that corresponds to scrolling to a consecutive value, and convert that to pixels in order to use it.
  • The first parameter of scrollBy(x,y) should take a value of 0, in case it is not obvious

I haven't made animations myself in Android, but there is a very good API since Honeycomb which probably makes it easy to accomplish this.

I am sorry, but this is the easiest I could think of!

EDIT: To convert from 'dp' to pixels:

private float pxFromDp(float dp)
{
     return dp * this.getContext().getResources().getDisplayMetrics().density;
}

What you'll have to do is test calling scrollBy(0, pxFromDp(dp)) with different 'dp' values, until you get the exact one that moves the NumberPicker up one number. Once you get that value, you can create your animation method that when moving up X numbers will scroll X times this dp distance.

Please ask again if you don't understand it completely :)


Thank @passsy. Inspired his answer: This solution supports multiple increment/decrement steps. Also, can be executed with a start delay and for multiple numberPickers (without extra coding).

class IncreaseValue {
    private int counter = 0;
    private final NumberPicker picker;
    private final int incrementValue;
    private final boolean increment;

    private final Handler handler = new Handler();
    private final Runnable fire = new Runnable() { @Override public void run() { fire(); } };

    /*public*/ IncreaseValue(final NumberPicker picker, final int incrementValue) {
        this.picker = picker;
        if (incrementValue > 0) {
            increment = true;
            this.incrementValue = incrementValue;
        } else {
            increment = false;
            this.incrementValue = -incrementValue;
        }
    }

    /*public*/ void run(final int startDelay) {
        handler.postDelayed(fire, startDelay);  // This will execute the runnable passed to it (fire)
        // after [startDelay in milliseconds], ASYNCHRONOUSLY.
    }

    private void fire() {
        ++counter;
        if (counter > incrementValue) return;

        try {
            // refelction call for
            // picker.changeValueByOne(true);
            final Method method = picker.getClass().getDeclaredMethod("changeValueByOne", boolean.class);
            method.setAccessible(true);
            method.invoke(picker, increment);

        } catch (final NoSuchMethodException | InvocationTargetException | 
                IllegalAccessException | IllegalArgumentException e) {
            Log.d(TAG, "...", e);
        }

        handler.postDelayed(fire, 120);  // This will execute the runnable passed to it (fire)
        // after 120 milliseconds, ASYNCHRONOUSLY. Customize this value if necessary.
    }
}

The usage:

// Suppose picker1 as a NumberPicker
IncreaseValue increasePicker1 = new IncreaseValue(picker1, 10);  // So picker1 will be increased 10 steps.
increasePicker1.run(0);  // Increase immediately. If you want to run it from onCreate(), onStart() or ... of your activity, you will probably need to pass a positive value to it (e.g. 100 milliseconds).