我创建了一个Android应用程序,其中有一个NumberPicker。我需要更改此 NumberPicker 的值,但要具有平滑的动画,就像您触摸它并更改其值时一样。
例如,假设当前值为 1,它将是 5,我希望 NumberPicker 从 1 旋转到 2,然后旋转到 3,依此类推。但我希望它旋转而不仅仅是立即改变值!
如果我使用以下代码:
numberPicker.setValue(5);
它的值会立即变为 5,但我希望它从 1 滚动到 5,就像你手动触摸它并使其旋转一样。
我通过反思解决了这个问题
/**
* 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();
}
}
工作完美。我不知道为什么这个方法是私有的
我不相信这是原生支持的。我想到了一种手动完成的“混乱”方式:
可以使用NumberPicker的scrollBy(int x, int y)函数迭代调用来制作动画的效果。
需要考虑的一些事项:
我自己没有在 Android 中制作动画,但是自 Honeycomb 以来有一个非常好的 API,这可能可以轻松完成此任务。
我很抱歉,但这是我能想到的最简单的方法!
编辑:从“dp”转换为像素:
private float pxFromDp(float dp)
{
return dp * this.getContext().getResources().getDisplayMetrics().density;
}
您需要做的是测试使用不同的“dp”值调用scrollBy(0, pxFromDp(dp)),直到获得将 NumberPicker 向上移动一个数字的确切值。一旦获得该值,您就可以创建动画方法,当向上移动 X 个数字时,将滚动 X 倍此 dp 距离。
如果不完全明白请再问:)
感谢@passsy。启发了他的回答:该解决方案支持多个递增/递减步骤。此外,可以通过启动延迟和多个数字选择器(无需额外编码)来执行。
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).
NumberPicker 内部有一个私有方法,即
changeCurrentByOne(boolean increment)
您可以公开并使用它。当按下 NumberPicker 的 UP 或 DOWN 箭头时,您可以看到此方法的作用。可能这不是您想要的,因为这个方法没有提供太多的动画控制,但它绝对比 setValue 更好。没有任何动画setValue对用户来说看起来很糟糕。
感谢@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)
}
}
}
您可以通过伪造键盘事件来增加或减少而不进行反射:
numberPicker.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, increment ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP));