import { ListPickerBase, selectedIndexProperty, itemsProperty, colorProperty, Color } from "./list-picker-common"; import { ItemsSource } from "."; import { device } from "../../platform"; import lazy from "../../utils/lazy"; export * from "./list-picker-common"; const sdkVersion = lazy(() => parseInt(device.sdkVersion)); interface Formatter { new(owner: ListPicker): android.widget.NumberPicker.Formatter; } interface ValueChangeListener { new(owner: ListPicker): android.widget.NumberPicker.OnValueChangeListener; } let Formatter: Formatter; let ValueChangeListener: ValueChangeListener; function initializeNativeClasses(): void { if (Formatter) { return; } @Interfaces([android.widget.NumberPicker.Formatter]) class FormatterImpl extends java.lang.Object implements android.widget.NumberPicker.Formatter { constructor(private owner: ListPicker) { super(); return global.__native(this); } format(index: number): string { return this.owner._getItemAsString(index); } } @Interfaces([android.widget.NumberPicker.OnValueChangeListener]) class ValueChangeListenerImpl extends java.lang.Object implements android.widget.NumberPicker.OnValueChangeListener { constructor(private owner: ListPicker) { super(); return global.__native(this); } onValueChange(picker: android.widget.NumberPicker, oldValue: number, newValue: number): void { selectedIndexProperty.nativeValueChange(this.owner, newValue); this.owner.updateSelectedValue(newValue); } } Formatter = FormatterImpl; ValueChangeListener = ValueChangeListenerImpl; } function getEditText(picker: android.widget.NumberPicker): android.widget.EditText { for (let i = 0, count = picker.getChildCount(); i < count; i++) { let child = picker.getChildAt(i); if (child instanceof android.widget.EditText) { return child; } } return null; } let selectorWheelPaintField: java.lang.reflect.Field; function getSelectorWheelPaint(picker: android.widget.NumberPicker): android.graphics.Paint { if (!selectorWheelPaintField) { selectorWheelPaintField = picker.getClass().getDeclaredField("mSelectorWheelPaint"); selectorWheelPaintField.setAccessible(true); } return selectorWheelPaintField.get(picker); } export class ListPicker extends ListPickerBase { nativeViewProtected: android.widget.NumberPicker; private _selectorWheelPaint: android.graphics.Paint; public createNativeView() { const picker = new android.widget.NumberPicker(this._context); picker.setDescendantFocusability(android.widget.NumberPicker.FOCUS_BLOCK_DESCENDANTS); picker.setMinValue(0); picker.setMaxValue(0); picker.setValue(0); picker.setWrapSelectorWheel(false); return picker; } public initNativeView(): void { super.initNativeView(); initializeNativeClasses(); const nativeView = this.nativeViewProtected; // api28 and lower uses reflection to retrieve and manipulate // android.graphics.Paint object; this is no longer allowed on newer api levels but // equivalent public methods are exposed on api29+ directly on the native widget if (sdkVersion() < 29) { this._selectorWheelPaint = getSelectorWheelPaint(nativeView); } const formatter = new Formatter(this); nativeView.setFormatter(formatter); (nativeView).formatter = formatter; const valueChangedListener = new ValueChangeListener(this); nativeView.setOnValueChangedListener(valueChangedListener); (nativeView).valueChangedListener = valueChangedListener; const editText = getEditText(nativeView); if (editText) { (nativeView).editText = editText; //Fix the disappearing selected item. //HACK: http://stackoverflow.com/questions/17708325/android-numberpicker-with-formatter-does-not-format-on-first-rendering/26797732 editText.setFilters([]); //Since the Android NumberPicker has to always have at least one item, i.e. minValue=maxValue=value=0, we don't want this zero showing up when this.items is empty. editText.setText(" ", android.widget.TextView.BufferType.NORMAL); } } public disposeNativeView() { const nativeView = this.nativeViewProtected; (nativeView).formatter.owner = null; (nativeView).valueChangedListener.owner = null; super.disposeNativeView(); } private _fixNumberPickerRendering() { const nativeView = this.nativeViewProtected; //HACK: Force the stubborn NumberPicker to render correctly when we have 0 or 1 items. nativeView.setFormatter(null); nativeView.setFormatter((nativeView).formatter); //Force the NumberPicker to call our Formatter const editText = (nativeView).editText; if (editText) { editText.setFilters([]); editText.invalidate(); //Force the EditText to redraw } nativeView.invalidate(); } [selectedIndexProperty.getDefault](): number { return -1; } [selectedIndexProperty.setNative](value: number) { if (value >= 0) { this.nativeViewProtected.setValue(value); } } [itemsProperty.getDefault](): any[] { return null; } [itemsProperty.setNative](value: any[] | ItemsSource) { let maxValue = value && value.length > 0 ? value.length - 1 : 0; this.nativeViewProtected.setMaxValue(maxValue); this._fixNumberPickerRendering(); // Coerce selected index after we have set items to native view. selectedIndexProperty.coerce(this); } [colorProperty.getDefault](): number { // api28 and lower uses reflection to retrieve and manipulate // android.graphics.Paint object; this is no longer allowed on newer api levels but // equivalent public methods are exposed on api29+ directly on the native widget if (this._selectorWheelPaint) { return this._selectorWheelPaint.getColor(); } return this.nativeView.getTextColor(); } [colorProperty.setNative](value: number | Color) { const color = value instanceof Color ? value.android : value; // api28 and lower uses reflection to retrieve and manipulate // android.graphics.Paint object; this is no longer allowed on newer api levels but // equivalent public methods are exposed on api29+ directly on the native widget if (this._selectorWheelPaint) { this._selectorWheelPaint.setColor(color); const editText = (this.nativeViewProtected).editText; if (editText) { editText.setTextColor(color); } } else { // api29 and higher native implementation sets // both wheel color and input text color with single call this.nativeView.setTextColor(color); } } }