NativeScript UI组件 #
组件概述 #
NativeScript 提供了丰富的原生 UI 组件,这些组件直接映射到 iOS 和 Android 的原生控件。
text
┌─────────────────────────────────────────────────────────────┐
│ UI 组件分类 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 基础组件 │
│ ├── Label 文本显示 │
│ ├── Button 按钮 │
│ ├── Image 图片 │
│ └── HtmlView HTML 内容 │
│ │
│ 表单组件 │
│ ├── TextField 单行输入 │
│ ├── TextView 多行输入 │
│ ├── Switch 开关 │
│ ├── Slider 滑块 │
│ ├── CheckBox 复选框 │
│ └── DatePicker 日期选择 │
│ │
│ 导航组件 │
│ ├── ActionBar 操作栏 │
│ ├── TabView 标签页 │
│ └── BottomNavigation 底部导航 │
│ │
│ 数据组件 │
│ ├── ListView 列表视图 │
│ ├── Repeater 重复器 │
│ └── WebView 网页视图 │
│ │
└─────────────────────────────────────────────────────────────┘
基础组件 #
Label #
文本显示组件:
xml
<Label text="Hello World"
textWrap="true"
textAlignment="center" />
常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
| text | string | 文本内容 |
| textWrap | boolean | 是否换行 |
| textAlignment | string | 对齐方式: left/center/right |
| fontSize | number | 字体大小 |
| fontWeight | string | 字体粗细: normal/bold |
| color | Color | 文本颜色 |
| maxLines | number | 最大行数 |
富文本
xml
<Label textWrap="true">
<FormattedString>
<Span text="Hello " fontSize="16" color="#333" />
<Span text="World" fontSize="20" fontWeight="bold" color="#3498db" />
<Span text="!" fontSize="16" color="#333" />
</FormattedString>
</Label>
Button #
按钮组件:
xml
<Button text="Click Me"
tap="onTap"
class="btn btn-primary" />
常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
| text | string | 按钮文本 |
| tap | function | 点击事件 |
| isEnabled | boolean | 是否可用 |
| borderRadius | number | 圆角半径 |
| borderWidth | number | 边框宽度 |
按钮样式
css
.btn {
padding: 12 24;
border-radius: 8;
font-size: 16;
min-width: 100;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-secondary {
background-color: #95a5a6;
color: white;
}
.btn-danger {
background-color: #e74c3c;
color: white;
}
.btn-outline {
background-color: transparent;
border-color: #3498db;
border-width: 2;
color: #3498db;
}
Image #
图片组件:
xml
<!-- 本地图片 -->
<Image src="~/assets/images/logo.png"
stretch="aspectFit"
width="100"
height="100" />
<!-- 网络图片 -->
<Image src="https://example.com/image.jpg"
stretch="aspectFill"
loadMode="async" />
<!-- 图标字体 -->
<Image src="font://"
class="fas"
fontSize="32" />
stretch 属性
| 值 | 说明 |
|---|---|
| none | 原始大小 |
| aspectFit | 保持比例适应 |
| aspectFill | 保持比例填充 |
| fill | 拉伸填充 |
图片加载事件
xml
<Image src="{{ imageUrl }}"
loaded="onImageLoaded"
loadError="onImageError" />
typescript
export function onImageLoaded(args: EventData) {
console.log('Image loaded');
}
export function onImageError(args: EventData) {
console.log('Image load failed');
}
HtmlView #
显示 HTML 内容:
xml
<HtmlView html="<h1>Title</h1><p>Paragraph</p>" />
表单组件 #
TextField #
单行文本输入:
xml
<TextField hint="Enter your name"
text="{{ name }}"
keyboardType="name"
returnKeyType="done"
autocorrect="false"
autocapitalizationType="words" />
常用属性
| 属性 | 类型 | 说明 |
|---|---|---|
| text | string | 输入内容 |
| hint | string | 占位文本 |
| keyboardType | string | 键盘类型 |
| returnKeyType | string | 返回键类型 |
| maxLength | number | 最大长度 |
| secure | boolean | 密码输入 |
| editable | boolean | 是否可编辑 |
键盘类型
| 值 | 说明 |
|---|---|
| text | 默认文本 |
| number | 数字 |
| phone | 电话 |
| 邮箱 | |
| url | 网址 |
| name | 姓名 |
| password | 密码 |
事件处理
xml
<TextField text="{{ email }}"
textChange="onTextChange"
returnPress="onReturnPress"
focus="onFocus"
blur="onBlur" />
TextView #
多行文本输入:
xml
<TextView hint="Enter your message"
text="{{ message }}"
editable="true"
textWrap="true"
maxLength="500" />
Switch #
开关组件:
xml
<GridLayout columns="*, auto" rows="auto">
<Label text="Enable notifications" col="0" />
<Switch checked="{{ isEnabled }}"
checkedChange="onCheckedChange"
col="1" />
</GridLayout>
typescript
export function onCheckedChange(args) {
const switchControl = args.object as Switch;
console.log('Checked:', switchControl.checked);
}
Slider #
滑块组件:
xml
<StackLayout>
<Label text="Volume: {{ volume }}" />
<Slider minValue="0"
maxValue="100"
value="{{ volume }}"
valueChange="onVolumeChange" />
</StackLayout>
CheckBox #
复选框(需要插件):
bash
npm install @nativescript/checkbox
xml
<CheckBox checked="{{ isChecked }}"
text="Remember me"
checkedChange="onCheckedChange" />
DatePicker #
日期选择器:
xml
<DatePicker year="2024"
month="1"
day="1"
date="{{ selectedDate }}"
dateChange="onDateChange" />
TimePicker #
时间选择器:
xml
<TimePicker hour="12"
minute="0"
time="{{ selectedTime }}"
timeChange="onTimeChange" />
Picker #
下拉选择器:
xml
<Picker items="{{ items }}"
selectedIndex="{{ selectedIndex }}"
selectedValue="{{ selectedValue }}"
selectedIndexChanged="onSelectedIndexChanged" />
typescript
export class ViewModel extends Observable {
items = ['Option 1', 'Option 2', 'Option 3'];
selectedIndex = 0;
selectedValue = 'Option 1';
}
导航组件 #
ActionBar #
顶部操作栏:
xml
<Page>
<ActionBar title="My App">
<!-- 导航按钮 -->
<NavigationButton icon="res://back"
tap="onBack"
android.visibility="collapsed" />
<!-- 操作按钮 -->
<ActionItem text="Edit"
icon="res://edit"
tap="onEdit"
android.position="actionBar"
ios.position="right" />
<!-- 溢出菜单 -->
<ActionItem text="Settings"
tap="onSettings"
android.position="popup" />
</ActionBar>
<GridLayout>
<!-- 页面内容 -->
</GridLayout>
</Page>
自定义 ActionBar
xml
<ActionBar class="action-bar">
<GridLayout columns="auto, *, auto">
<Button text="Menu" col="0" tap="onMenu" />
<Label text="Custom Title" col="1" class="title" />
<Button text="Search" col="2" tap="onSearch" />
</GridLayout>
</ActionBar>
TabView #
标签页导航:
xml
<TabView selectedIndex="{{ selectedIndex }}"
tabTextColor="#999"
selectedTabTextColor="#3498db"
tabBackgroundColor="#f5f5f5">
<TabView.items>
<TabViewItem title="Home" iconSource="res://home">
<TabViewItem.view>
<GridLayout>
<Label text="Home Page" />
</GridLayout>
</TabViewItem.view>
</TabViewItem>
<TabViewItem title="Search" iconSource="res://search">
<TabViewItem.view>
<GridLayout>
<Label text="Search Page" />
</GridLayout>
</TabViewItem.view>
</TabViewItem>
<TabViewItem title="Profile" iconSource="res://user">
<TabViewItem.view>
<GridLayout>
<Label text="Profile Page" />
</GridLayout>
</TabViewItem.view>
</TabViewItem>
</TabView.items>
</TabView>
BottomNavigation #
底部导航(Material Design):
xml
<BottomNavigation>
<BottomNavigation.items>
<TabStripItem title="Home">
<TabStripItem.iconSource>
<FontIconSource icon="home" fontSize="24" />
</TabStripItem.iconSource>
</TabStripItem>
<TabStripItem title="Search">
<TabStripItem.iconSource>
<FontIconSource icon="search" fontSize="24" />
</TabStripItem.iconSource>
</TabStripItem>
<TabStripItem title="Profile">
<TabStripItem.iconSource>
<FontIconSource icon="user" fontSize="24" />
</TabStripItem.iconSource>
</TabStripItem>
</BottomNavigation.items>
<BottomNavigation.content>
<GridLayout>
<Label text="{{ currentTab }}" />
</GridLayout>
</BottomNavigation.content>
</BottomNavigation>
数据组件 #
ListView #
高性能列表视图:
xml
<ListView items="{{ items }}"
itemTap="onItemTap"
loadMoreItems="onLoadMore"
separatorColor="#e0e0e0">
<ListView.itemTemplate>
<GridLayout columns="auto, *, auto" rows="auto, auto" class="item">
<Image src="{{ avatar }}"
row="0" col="0" rowSpan="2"
width="50" height="50"
borderRadius="25" />
<Label text="{{ name }}"
row="0" col="1" class="name" />
<Label text="{{ message }}"
row="1" col="1" class="message" textWrap="true" />
<Label text="{{ time }}"
row="0" col="2" class="time" />
</GridLayout>
</ListView.itemTemplate>
</ListView>
无限滚动
typescript
export class ViewModel extends Observable {
items = new ObservableArray();
page = 1;
onLoadMore() {
this.page++;
this.loadItems();
}
async loadItems() {
const newItems = await fetchItems(this.page);
this.items.push(...newItems);
}
}
下拉刷新
xml
<ListView items="{{ items }}"
refresh="{{ isRefreshing }}"
refreshList="onRefresh">
<!-- ... -->
</ListView>
typescript
export function onRefresh(args) {
const listView = args.object as ListView;
// 刷新数据
refreshData().then(() => {
listView.refreshing = false;
});
}
Repeater #
简单重复器:
xml
<Repeater items="{{ items }}">
<Repeater.itemTemplate>
<Label text="{{ title }}" class="item" />
</Repeater.itemTemplate>
</Repeater>
WebView #
网页视图:
xml
<WebView src="https://example.com"
loaded="onLoaded"
loadStarted="onLoadStarted"
loadFinished="onLoadFinished" />
typescript
export function onLoadFinished(args) {
const webView = args.object as WebView;
console.log('URL:', webView.url);
}
对话框组件 #
Alert #
typescript
import { Dialogs } from '@nativescript/core';
Dialogs.alert({
title: 'Alert',
message: 'This is an alert',
okButtonText: 'OK'
});
Confirm #
typescript
const result = await Dialogs.confirm({
title: 'Confirm',
message: 'Are you sure?',
okButtonText: 'Yes',
cancelButtonText: 'No'
});
if (result) {
console.log('Confirmed');
}
Prompt #
typescript
const result = await Dialogs.prompt({
title: 'Input',
message: 'Enter your name:',
okButtonText: 'OK',
cancelButtonText: 'Cancel',
inputType: 'text'
});
if (result.result) {
console.log('Input:', result.text);
}
Action #
typescript
const result = await Dialogs.action({
title: 'Choose',
message: 'Select an option:',
cancelButtonText: 'Cancel',
actions: ['Option 1', 'Option 2', 'Option 3']
});
console.log('Selected:', result);
最佳实践 #
组件复用 #
xml
<!-- components/user-card.xml -->
<GridLayout columns="auto, *" class="user-card">
<Image src="{{ avatar }}" col="0" width="50" height="50" />
<StackLayout col="1">
<Label text="{{ name }}" class="name" />
<Label text="{{ email }}" class="email" />
</StackLayout>
</GridLayout>
xml
<!-- 使用组件 -->
<user-card user="{{ currentUser }}" />
性能优化 #
typescript
// 使用 itemLoading 优化 ListView
export function onItemLoading(args: ItemEventData) {
const view = args.view;
const item = args.bindingContext;
// 复用视图,避免重复创建
if (!view) {
view = createItemView();
}
// 更新数据
updateView(view, item);
}
下一步 #
现在你已经了解了 UI 组件,接下来学习 布局系统,掌握如何组织界面布局!
最后更新:2026-03-29