10

我为 Android 创建了一个应用程序,其中有一个 NumberPicker。我需要更改此 NumberPicker 的值,但要使用平滑的动画,例如当您触摸它并更改其值时。

例如,假设当前值为 1,它将为 5,我希望 NumberPicker 从 1 旋转到 2,然后旋转到 3,依此类推。但我希望它旋转而不仅仅是立即改变值!

如果我使用以下代码:

numberPicker.setValue(5);

它的值会立即变为 5,但我希望它从 1 滚动到 5,就像您手动触摸它并使其旋转时一样。

4

5 回答 5

16

我通过反射解决了它

        /**
         * 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();
            }
        }

完美运行。我不知道为什么这个方法是私有的

于 2013-03-18T11:23:26.547 回答
3

我不相信这是本机支持的。我想到了一种手动操作的“混乱”方式:

您可以使用迭代调用的 NumberPicker 的 scrollBy(int x, int y) 函数来制作动画效果。

需要考虑的一些事项:

  • scrollBy(x,y) 适用于像素。由于 Android 具有所有不同的屏幕密度,您应该首先猜测(我会通过反复试验来完成)对应于滚动到连续值的“dp”距离,并将其转换为像素以便用它。
  • scrollBy(x,y)的第一个参数应该取0值,以防不明显

我自己没有在 Android 中制作动画,但是自 Honeycomb 以来有一个非常好的 API,它可能很容易实现这一点。

我很抱歉,但这是我能想到的最简单的方法!

编辑:从 'dp' 转换为像素:

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

您需要做的是使用不同的 'dp' 值测试调用 scrollBy(0, pxFromDp(dp)) ,直到获得将 NumberPicker 向上移动一个数字的确切值。获得该值后,您可以创建动画方法,当向上移动 X 数字时,将滚动该 dp 距离的 X 倍。

如果您不完全理解,请再次询问:)

于 2012-11-21T20:19:38.710 回答
3

谢谢@passsy。启发了他的答案:此解决方案支持多个递增/递减步骤。此外,可以使用启动延迟多个 numberPickers 执行(无需额外编码)

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.
    }
}

用法:

// 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).
于 2017-02-11T13:46:18.717 回答
1

NumberPicker 内部有一个私有方法,即

changeCurrentByOne(boolean increment)

您可以将其公开并使用它。当按下 NumberPicker 的向上向下箭头时,您可以看到此方法的实际效果。可能这不是您正在寻找的,因为这种方法不能对动画进行太多控制,但它肯定比setValue更好。没有任何动画setValue对用户来说看起来很糟糕。

于 2013-01-07T20:21:16.483 回答
1

感谢@passy 的回答。可以使用 Kotlin 扩展函数进一步简化:

fun NumberPicker.animateChange(isIncrement: Boolean) {
    Handler().post {
        try {
            javaClass.getDeclaredMethod("changeValueByOne", Boolean::class.javaPrimitiveType).also { function ->
                function.isAccessible = true
                function.invoke(this, isIncrement)
            }
        } catch (e: Exception) {
            Log.e(javaClass.simpleName, e.message)
        }
    }
}
于 2019-05-18T17:09:36.510 回答