【免费下载链接】NativeScript
⚡ Write Native with TypeScript ✨ Best of all worlds (TypeScript, Swift, Objective C, Kotlin, Java, Dart). Use what you love ❤️ Angular, React, Solid, Svelte, Vue with: iOS (UIKit, SwiftUI), Android (View, Jetpack Compose), Flutter and you name it compatible.
本文是 NativeScript 核心包中ui/list-picker模块的实战指南,围绕官方文档(apps/automated/src/ui/list-picker/list-picker.md)梳理 ListPicker 的引入方式、控件创建、items数据绑定以及selectedIndex编程式选择四大核心操作,并结合@nativescript/core源码(跨平台公共实现、Android NumberPicker、iOS UIPickerView 封装)与自动化测试用例,深入解释其底层原理与边界行为。读完本文,你将能够熟练地在 NativeScript 应用中创建滚轮式选择器、绑定数组或自定义数据源,并通过属性或事件精确控制选中项。
引入 ListPicker 模块
在 NativeScript 中使用 ListPicker,首先需要引入ui/list-picker模块。官方文档给出的引用方式与测试代码(list-picker-tests.ts)完全一致:
import * as listPickerModule from '@nativescript/core/ui/list-picker';该模块导出了ListPicker类(继承自ListPickerBase)以及selectedIndexProperty、itemsProperty等属性描述符,因此既可以按命名空间方式访问(如new listPickerModule.ListPicker()),也可以直接使用具名导入:
import { ListPicker } from '@nativescript/core/ui/list-picker';从源码结构看,@nativescript/core在打包时通过平台别名(.android/.ios)将平台实现暴露为同一个模块入口:Android 端基于android.widget.NumberPicker(见 index.android.ts),iOS 端基于UIPickerView(见 index.ios.ts),开发者无需关心平台差异。
创建 ListPicker 控件
使用new关键字即可创建 ListPicker 实例:
var listPicker = new listPickerModule.ListPicker();创建完成后,可以像其他 View 一样将其加入页面内容或布局容器中(参考测试代码 list-picker-tests.ts):
listPicker.id = 'ListPicker'; page.content = listPicker;在 XML 声明式语法中,ListPicker 同样是注册过的原生视图(源码中标有@nsView ListPicker注解),可以直接写为:
<ListPicker id="listPicker" items="{{ items }}" selectedIndex="{{ selectedIndex }}" />控件创建时有两个值得注意的默认状态(均由自动化测试验证):
items默认为undefined(测试testWhenlistPickerIsCreatedItemsAreUndefined);selectedIndex默认为-1(测试testWhenlistPickerIsCreatedSelectedIndexIsUndefined)。
这一默认设计避免了在未设置数据时出现“悬空选中态”,具体边界行为详见下文。
绑定 items 数据源
ListPicker 的items属性既可以绑定普通数组,也可以绑定自定义数据源对象。先看最直接的数组绑定方式(官方文档片段,测试 list-picker-tests.ts 中原样复现):
listPicker.items = [1, 2, 3];绑定一个包含 10 个元素的数组同样简单:
listPicker.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];items 的底层行为
items在 list-picker-common.ts 中注册为普通Property,其valueChanged回调会根据新值是否具有getItem方法自动判定是否为ItemsSource:
export const itemsProperty = new Property<ListPickerBase, any[] | ItemsSource>({ name: 'items', valueChanged: (target, oldValue, newValue) => { const getItem = newValue && (<ItemsSource>newValue).getItem; target.isItemsSource = typeof getItem === 'function'; }, });因此items支持两种形态:
- 普通数组(
any[]):按索引取值,listPicker.items[index]; - 自定义数据源:满足
ItemsSource接口的对象,只需实现length属性和getItem(index): any方法:
interface ItemsSource { length: number; getItem(index: number): any; }绑定数据源后,列表展示的文本由_getItemAsString(index)决定(list-picker-common.ts):当textField属性非空时,取item[textField]作为展示文本;否则直接把item转为字符串。这为“对象数组 + 指定展示字段”的场景提供了支持:
listPicker.textField = 'name'; // 展示 item.name listPicker.items = [ { id: 1, name: 'Angular' }, { id: 2, name: 'React' }, { id: 3, name: 'Vue' }, ];对应的valueField属性则用于定义selectedValue的取值字段(见下文“selectedValue 联动”)。
数据绑定的原生联动
- Android 端:
itemsProperty.setNative会同步到原生NumberPicker的maxValue与minValue。测试 list-picker-tests.ts 验证了 Android 原生控件的maxValue恒等于items.length - 1;而createNativeView初始即设置setMinValue(0)、setMaxValue(0)、setWrapSelectorWheel(false)(index.android.ts),防止空数据时出现无限滚动。 - iOS 端:
itemsProperty.setNative调用reloadAllComponents()刷新UIPickerView,随后对selectedIndex执行一次强制收敛(index.ios.ts)。
此外,items在视图加载前设置也能被正确解析(测试testItemsIsResolvedCorrectlyIfSetBeforeViewIsLoaded),属性机制保证了延迟到原生视图创建后再同步。
编程式选择选中项
通过设置selectedIndex属性即可在代码中选中指定行(官方文档片段,测试 list-picker-tests.ts 中原样复现):
listPicker.selectedIndex = 9;例如先绑定 10 个元素,再选中索引为 9 的最后一项:
listPicker.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; listPicker.selectedIndex = 9; // 选中最后一项selectedIndex 的收敛规则
selectedIndex在 list-picker-common.ts 中被定义为CoercibleProperty,默认值为-1,赋值时自动执行范围收敛(coerce),核心逻辑如下:
- 当
items存在时,最大值取items.length - 1; - 值小于 0 时被收敛为
0; - 值大于
items.length - 1时被收敛为最大值; - 当
items为空(或未设置)时,selectedIndex一律收敛为-1。
这意味着给selectedIndex赋值一个越界索引不会抛异常,而是被安全钳制到有效区间内。valueConverter会将字符串形式的索引(如 XML 属性传来的"2")自动parseInt为数字。
不同 items 状态下的选中索引行为
自动化测试(list-picker-tests.ts)系统地验证了 items 变化时选中索引的重置规则:
| items 状态 | selectedIndex 结果 | 依据测试 |
|---|---|---|
| 绑定非空数组 | 变为0(自动选中首项) | testSelectedIndexBecomesZeroWhenItemsBoundToNonEmptyArray |
绑定空数组[] | 重置为-1 | testSelectedIndexBecomesUndefinedWhenItemsBoundToEmptyArray |
设为undefined | 重置为-1 | testSelectedIndexBecomesUndefinedWhenItemsBoundToUndefined |
设为null | 重置为-1 | testSelectedIndexBecomesUndefinedWhenItemsBoundToNull |
另外,selectedIndex在视图加载前设置也能正确生效(测试testSelectedIndexIsResolvedCorrectlyIfSetBeforeViewIsLoaded)。
平台原生同步
- iOS:
selectedIndexProperty.setNative通过selectRowInComponentAnimated(value, 0, false)定位到指定行(index.ios.ts),且仅当value >= 0时才执行,避免对未就绪的控件误操作。 - Android:用户滚动
NumberPicker时,ValueChangeListenerImpl.onValueChange会回调selectedIndexProperty.nativeValueChange(owner, newValue)并联动updateSelectedValue(index.android.ts),从而保证 UI 操作与属性状态双向一致。
监听选中变化与 selectedValue 联动
当用户通过滚轮改变选中项时,ListPicker 会触发selectedIndexChange事件(事件名常量定义于 list-picker-common.ts,d.ts 中标注其载荷类型为PropertyChangeData)。监听方式如下:
listPicker.on('selectedIndexChange', (args) => { console.log('新的选中索引:', args.value); });与此同时,selectedIndex变化还会联动更新只读的selectedValue属性。updateSelectedValue(list-picker-common.ts)的逻辑为:
- 当
valueField非空时,取item[valueField]作为selectedValue; - 否则直接取
item本身; - 索引小于 0 时
selectedValue置为null。
典型用法:配合textField展示名称、valueField取 ID,从而在选中变化时直接拿到业务主键,无需再手动查表:
listPicker.textField = 'name'; listPicker.valueField = 'id'; listPicker.items = [ { id: 101, name: 'Angular' }, { id: 202, name: 'React' }, ]; listPicker.on('selectedIndexChange', (args) => { console.log('选中 ID:', listPicker.selectedValue); // 101 或 202 });平台实现速览
ListPicker 的双端原生实现都封装在@nativescript/core的ui/list-picker目录中:
- 公共基类list-picker-common.ts:定义
ListPickerBase、ItemsSource接口及selectedIndex、items、textField、valueField、selectedValue五个属性,并设置了recycleNativeView = 'auto'以支持原生视图回收复用(自动化测试test_recycling即验证了这一点)。 - Android 实现index.android.ts:原生控件为
android.widget.NumberPicker,通过Formatter(调用_getItemAsString渲染行文本)与OnValueChangeListener(回写selectedIndex)桥接 JS 与原生;同时支持通过color属性定制选中文字颜色,API 28 及以下使用反射获取mSelectorWheelPaint,API 29+ 使用原生公开方法。 - iOS 实现index.ios.ts:原生控件为
UIPickerView,通过UIPickerViewDataSource(返回组件数与行数)和UIPickerViewDelegate(渲染行文本、回写选中行)实现桥接,并支持tintColor定制。 - 类型声明index.d.ts:对外暴露
selectedIndex与items属性,并标注 Android 端原生视图为android.widget.NumberPicker、iOS 端为UIPickerView,便于在代码中直接访问原生对象做进一步定制。
进一步阅读
- 官方 HOW-TO 文档主体:apps/automated/src/ui/list-picker/list-picker.md
- 完整自动化测试(含全部边界行为用例):apps/automated/src/ui/list-picker/list-picker-tests.ts
- 平台原生测试辅助:list-picker-tests-native.ios.ts、list-picker-tests-native.android.ts
- 双端原生封装源码:apps/ui/src/list-picker(UI 示例工程,含 XML 页面与 TS 用例)
【免费下载链接】NativeScript
⚡ Write Native with TypeScript ✨ Best of all worlds (TypeScript, Swift, Objective C, Kotlin, Java, Dart). Use what you love ❤️ Angular, React, Solid, Svelte, Vue with: iOS (UIKit, SwiftUI), Android (View, Jetpack Compose), Flutter and you name it compatible.
相关推荐
NativeScript-Vue3 响应式系统完整指南:深入理解数据绑定与更新机制
NativeScript Vue3 响应式系统完整指南:深入理解数据绑定与更新机制 NativeScript Vue3 响应式系统是现代跨平台移动应用开发的核心
移动开发前端跨平台Skinny Framework社区贡献指南:如何参与开源项目
Skinny Framework社区贡献指南:如何参与开源项目 Skinny Framework是一个以"Scala on Rails"为理念的全栈Web应用框
后端TDengine参数绑定模式高效数据写入指南
TDengine参数绑定模式高效数据写入指南 什么是参数绑定 参数绑定 Parameter Binding 是一种高效的数据写入技术,它通过将SQL语句结构与实
数据库时序数据库大数据物联网云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考