本頁由機器翻譯,可能有錯誤。 閱讀英文原文

元素

元素

Native Shell 以一小組元素構建每個畫面:View、Text 和 Image 等基本元素,以及 Button、Dialog 和 NavStack 等基礎元件。每個基本元素都是原生視圖(在 iOS 和 Mac Catalyst 上是 UIView 子類別,在 Android 上是 View,在桌面平台上是 AppKit、GTK 或 Win32 視圖),由 NSH runtime 建立並排版。應用程式顯示的一切都由它們組成:應用程式的按鈕、卡片、列和畫面都是它自己的元件,以這些元素繪製並由應用程式設定樣式,所以應用程式的外觀完全由自己決定。

這些頁面上的每個範例都提供 Rust 和 TSX 兩個版本:選擇一個分頁,頁面上所有範例都會跟着切換。

use nsh_core::prelude::*;

fn app() -> impl IntoElement {
    let count = signal(0_u32);
    view()
        .flex_1()
        .items_center()
        .justify_center()
        .gap_3()
        .child(text(move || format!("Tapped {} times", count.get())).text_2xl())
        .child(
            view()
                .rounded_lg()
                .bg(rgb(0x2563eb))
                .px_4()
                .py_2()
                .active(|s| s.opacity(0.6))
                .on_press(move || count.update(|n| *n += 1))
                .child(text("Tap").text_color(white())),
        )
}

在 Rust 中,基本元素是 nsh_core::prelude 的函式(view()、text(..)、image(..)),基礎元件來自 nsh_base crate。樣式是有型別的方法(.flex_1()、.gap_3()、.px(px(20.0))、.bg(..)),狀態樣式接受一個閉包(.active(|s| s.opacity(0.6))),監聽器是接收其事件的閉包(.on_press(move || ..)、.on_change(move |text: &str| ..)),而 .child(..) 用來加入子元素。一段 UI 就是一個回傳 impl IntoElement 的普通函式;它只會執行一次,而在閉包(text(move || ..)、.bg(move || ..))中讀取的 signal 會更新它們所綁定的內容。參閱以 Rust 構建。

在 TSX 中,每個標籤都使用 PascalCase,並指向作用域內的一個綁定:基本元素來自 nsh,基礎元件來自 nsh/base。只有 <If> 和 <Each> 是全域的,無需匯入。小寫標籤(例如 <view>)會造成建置錯誤,因為 TypeScript 會按每個名稱一個全域型別來檢查它,而 List 和 Each 等泛型元件將無法推斷其項目型別。編譯器透過匯入來識別這些元素,並就地建立原生視圖,因此匯入它們不會增加任何執行時成本。參閱 JSX 與元件。

import { Canvas, Image, List, Overlay, Scroll, Shader, Text, Video, View, WebView } from "nsh";
import { Button, Dialog, Menu, NavPage, NavStack, Popover, Sheet, Tooltip, Input } from "nsh/base";

#基本元素

TSX 中從 nsh 匯入,Rust 中從 nsh_core::prelude 匯入。

元素 Rust 原生視圖(iOS / Android) 頁面
<View> view() UIView / ViewGroup;配合 selectable 時,其中的文字會一併選取 View、文字選取
<Text> text(content) CoreText 視圖 / StaticLayout 視圖 Text
<Image> image(src) 帶有圖像圖層的視圖 / 圖像視圖 Image
<Scroll> scroll() UIScrollView / ScrollView Scroll
<List> virtual_list(count, |at| row) UIScrollView / 回收儲存格的 ScrollView List
<Canvas> canvas(painter) 重播 2D 繪圖的視圖 Canvas
<Shader> shader(program) 由 WGSL 程式在 GPU 上於其子元素下方繪製其範圍的視圖 Shader
<WebView> web_view(url)、web_view_html(html, base) WKWebView / android.webkit.WebView WebView
<Video> video(url) AVPlayerLayer / TextureView 上的 MediaPlayer Video
<Overlay> overlay() 位於視窗(或其所在對話框)最上層、相對錨點定位的視圖 Overlay
<If>、<Each> when(..)、each(..)、dynamic(..) 控制流程:選擇要顯示的元素 控制流程
<InputAccessoryView>(nsh/mobile) input_accessory_view(id) iOS 上位於鍵盤上方的工具列 InputAccessoryView

內距(padding)和外距(margin)可使用 CSS 的 env() 讓內容避開系統 UI:env(safe-area-inset-top) 及其他邊緣(安全區域)和 env(keyboard-inset-height)(鍵盤)。

#基礎元件

TSX 中從 nsh/base 匯入,Rust 中來自 nsh_base crate。它們負責行為、狀態、焦點和無障礙功能,外觀則由應用程式決定。

元件 Rust 用途 頁面
AlertDialog nsh_base::alert_dialog() 系統的警示框和動作表 AlertDialog
<Accordion> 及其組成部分 nsh_base::accordion()、accordion_item()、… 在標題下展開和收合的層疊區段,可一次展開一個或多個 Accordion
<Avatar>、<AvatarGroup> nsh_base::avatar()、avatar_group() 一個人的圖片,或在載入中或載入失敗時的後備內容,以及只顯示最前幾個的一排頭像 Avatar
<Button> nsh_base::button() 帶有各種狀態和角色的可按壓控制項 Button
<Carousel> 及其組成部分 nsh_base::carousel()、carousel_content()、… 一次顯示一張的投影片,支援原生滑動、上一張 / 下一張、分頁指示、按鍵操作和無縫循環 Carousel
<Checkbox> nsh_base::checkbox() 帶有指示符號、可處於已勾選、未勾選或不確定狀態的控制項 Checkbox
<Collapsible> nsh_base::collapsible() 內容可展開和收合的區域 Collapsible
<Command> nsh_base::command() 命令面板:搜尋欄加上以原生方式篩選的命令、虛擬化列表,以及 Cmd+K 面板的按鍵操作 Command
<DataTable> nsh_base::data_table() 列和欄數目不限的表格,可選取、可排序,欄可調整大小和移動 DataTable
<Dialog> nsh_base::dialog() 將其子元素顯示於所有其他內容之上 Dialog
<DiffView> nsh_base::diff_view() 兩段文字之間或補丁(patch)的差異,以並排或統一方式顯示 DiffView
<DockArea> nsh_base::dock_area() 由使用者自行排列分割區、分頁群組和邊緣停靠區的工作區 Dock
<Editor> nsh_base::editor() 支援語法突顯、多個插入點、摺疊和搜尋的程式碼編輯器 Editor
<HoverCard> nsh_base::hover_card() 指標停留在觸發元素上時,預覽其指向的內容 HoverCard
<Input> nsh_base::input() 原生文字輸入(UITextField 或 UITextView / EditText) Input
<Link> nsh_base::link() 可按壓並開啟頁面、網站或應用程式路由的目標 Link
<Menu>、<ContextMenu>、<MenuItem>、<MenuSeparator>、<Submenu> nsh_base::menu()、… 選單:桌面平台上帶有層疊子選單的彈出選單,手機上則為底部面板 Menu
<NavStack>、<NavPage> nsh_base::nav_stack()、nsh_base::nav_page() 原生導覽:帶有平台標題列和返回手勢的頁面堆疊 NavStack
<NumberInput> nsh_base::number_input() 在欄位中輸入或以按鈕和按鍵逐步調整的數字 NumberInput
<OtpInput> nsh_base::otp_input() 固定位數的一次性代碼,可輸入、貼上或自動填入各個儲存格 OtpInput
<Pagination> nsh_base::pagination() 一組內容的頁碼:上一頁、目前頁附近的頁碼、省略號和下一頁 Pagination
<Tree> nsh_base::tree() 項目可包含項目,可展開和收合,並可選取其中一項:已虛擬化,佔鍵盤焦點的一個停駐點 Tree
<Plot>、<Line>、<Area>、<Bar>、<Grid>、<Pie>、<Arc>(nsh/base/plot) nsh_base::plot::{plot, line, area, bar, grid, pie, arc} 圖表的框架及其標記,根據應用程式的資料列以原生方式繪製 Plot
<Popover> nsh_base::popover() 錨定於觸發元素、顯示於所有內容之上的內容 Popover
<Progress> nsh_base::progress() 進度條:顯示任務完成了多少,或表示任務正在進行 Progress
<Radio>、<RadioGroup> nsh_base::radio()、radio_group() 供使用者在一組選項中選擇其一的單個選項,以及該組選項 Radio
<ResizablePanelGroup>、<ResizablePanel> nsh_base::h_resizable()、resizable_panel() 並排或上下堆疊的面板,帶有可調整其大小的拖曳把手 Resizable
<Scrollbar> nsh_base::scrollbar() 為捲動視圖或列表提供應用程式自有外觀的捲軸 Scrollbar
<Select> nsh_base::select() 從項目列表中選擇一個值,支援鍵盤、搜尋和分組 Select
<Sheet> nsh_base::sheet() 位於螢幕邊緣、置於背景遮罩之上的面板 Sheet
<Slider> nsh_base::slider() 沿軌道選取的一個值,或兩個值組成的範圍 Slider
<Switch> nsh_base::switch() 帶有軌道和滑塊的開 / 關控制項 Switch
<Table> 及其組成部分 nsh_base::table()、table_row()、… 由應用程式自有的列和儲存格組成的表格,帶有表格的角色 Table
<Tab>、<Tabs> nsh_base::tab()、tabs() 分頁列表中的單個分頁,以及該列表 Tabs
<TextView> nsh_base::text_view() 以文字和視圖顯示 Markdown 或 HTML 文件 TextView
<Toast>、<ToastStack> nsh_base::toast()、toast_stack() 一則 toast 的語意根節點;從邊緣起以圖層方式排列的多則 toast Toast
<Toggle>、<ToggleGroup> nsh_base::toggle()、toggle_group() 可開啟或關閉的按鈕,以及由這些按鈕組成的工具列 Toggle
<Toolbar>、<ToolbarGroup>、<ToolbarSeparator> nsh_base::toolbar()、toolbar_group()、toolbar_separator() 由按鈕、切換按鈕和連結組成的列,佔鍵盤焦點的一個停駐點 Toolbar
<Tooltip> nsh_base::tooltip() 在懸停或手指按住時,顯示於觸發元素旁的提示 Tooltip

#通用屬性

每個基本元素都接受以下屬性(TSX 中為 CommonProps 型別;Rust 中為 Styled、Interactive 和 Accessible trait)。各元素頁面只列出其專屬屬性。Rust 欄列出對應的建構方法。

#樣式

屬性 Rust 類型 說明
style 有型別的 Styled 方法;.inline_style(style) StyleProp<ViewStyle>(TextStyle 用於 <Text> 和 <Input>,ImageStyle 用於 <Image>) 內嵌樣式,或由左至右合併的樣式陣列(會略過假值項目;陣列可巢狀,所以 style={[base, props.style]} 可接受本身也是陣列的 props.style)。優先於 class。參閱樣式參考。
class 有型別的 Styled 方法(.flex_row().gap_2().p_4()) string Tailwind class,以原生方式解析("flex-row gap-2 p-4 dark:bg-zinc-900")。後面的 class 優先於前面的。參閱 Tailwind。
classList .style_when(when, |s| ..) Record<string, boolean | null | undefined> 在其值為真值時套用的 class({ "opacity-50": busy() })。
disabled .disabled(..) boolean 忽略按壓(也沒有按壓回饋),套用 disabled: 樣式,停用輸入框等控制項,並讓螢幕閱讀器讀出它已停用(accessibilityState.disabled)。

#按壓與回饋

按壓處理在原生層執行:平台負責識別按壓並套用應用程式的按壓樣式,應用程式的程式碼事後才會得知。只有最內層的可按壓視圖會作出回應;在捲動視圖內,按下(press-in)會等待約 130 毫秒,讓捲動可以先開始。

屬性 Rust 類型 預設值 說明
onPress .on_press(..) (e: PressEvent) => void 在視圖內結束的一次點按。
onLongPress .on_long_press(..) (e: PressEvent) => void 手指按住達 delayLongPress。
onPressDown .on_press_down(..) (e: PressEvent) => void 手指按在視圖上。
onPressUp .on_press_up(..) (e: PressEvent) => void 手指抬起或離開。
delayLongPress .long_press_delay(ms) number 500 按壓變為長按前的毫秒數。
hitSlop .hit_slop(EdgeInsets::all(px(8.0))) number | Partial<Edges> 將觸控區域延伸至邊界之外,單位為 point。

PressEvent 是 { position, pagePosition, modifiers, clickCount }:指標在視圖內及其根視圖內的位置(Point),按壓開始時按住的修飾鍵,以及連續點擊次數(雙擊為 2)。若平台沒有回報指標,則為視圖的原點、沒有按鍵,點擊次數為一。

樣式鍵 pointerEvents(Rust 中為 .pointer_events(PointerEvents::BoxNone),class 為 pointer-events-*)決定視圖本身及其內部視圖接收哪些觸控:"none" 兩者皆不接收(內部設定 "auto" 的視圖會重新接收),"box-none" 只有其內部的視圖接收,"box-only" 只有視圖本身接收(觸控與手勢)。

按壓回饋由應用程式決定:activeStyle(或 active: class,Rust 中為 .active(..))在視圖被按壓期間以原生方式套用,無需與應用程式的程式碼往返溝通(見下文)。

#狀態樣式

當視圖處於某個狀態時套用的樣式,以原生方式疊加在其自有樣式之上。每一項都是 Tailwind variant 的物件形式。後面的狀態優先(selected、checked 和 indeterminate,其次是 hover、focus、active);disabledStyle 優先於全部,而在視圖停用時,其他狀態都不會套用。每一項都接受該元素自己的樣式:<Text> 上為 TextStyle,<Input> 上為 InputStyle,<Image> 上為 ImageStyle,其他元素為 ViewStyle。

屬性 Rust Class variant 套用時機
activeStyle .active(|s| ..) active: 被按壓時。
hoverStyle .hover(|s| ..) hover: 指標位於視圖上方時。
focusStyle .focus(|s| ..) focus: 視圖擁有焦點時。
focusWithinStyle .focus_within(|s| ..) focus-within: 視圖或其內部的視圖擁有焦點時(Overlay 的內容視為位於其所在的視圖之內)。
disabledStyle .disabled_style(|s| ..) disabled: 當 disabled 時。
checkedStyle .checked(|s| ..)、.checked_style(style) checked: 當 accessibilityState.checked 為 true 時(核取方塊、開關、單選按鈕;"mixed" 不算已勾選)。
indeterminateStyle .indeterminate(|s| ..)、.indeterminate_style(style) indeterminate: 當 accessibilityState.checked 為 "mixed" 時(既非已勾選也非未勾選的核取方塊)。
selectedStyle .selected(|s| ..) selected: 當 accessibilityState.selected 為 true 時(已選取的 Button、分頁)。
expandedStyle .expanded(|s| ..)、.expanded_style(style) aria-expanded: 當 accessibilityState.expanded 為 true 時(Popover 或 Menu 的觸發元素在其彈出內容開啟期間)。
pressedStyle .aria_pressed(|s| ..)、.pressed_style(style) aria-pressed: 當 accessibilityState.pressed 為 true 時(已開啟的切換按鈕;"mixed" 不算已按下)。並非 activeStyle,即正在進行中的按壓。
view()
    .rounded_lg()
    .bg(rgb(0x2563eb))
    .px_4()
    .py_2()
    .active(|s| s.opacity(0.6))
    .hover(|s| s.bg(rgb(0x1d4ed8)))
    .on_press(save)
    .child(text("Save").text_color(white()))

#懸停

App 可以偵測滑鼠移入視圖、在其上移動及移出:支援 macOS、Windows、Linux、GPUI 後端和桌面瀏覽器;在 iOS 上支援 iPad 的指標或 Mac 的滑鼠;在 Android 上支援滑鼠或觸控筆。懸停樣式(hover:、.hover(..))會在相同的平台上為被懸停的視圖設定樣式。

屬性 Rust 類型 說明
onMouseEnter .on_mouse_enter(..) (e: MouseEvent) => void 滑鼠移入了該視圖或其內的視圖(對應 DOM 的 mouseenter)。
onMouseMove .on_mouse_move(..) (e: MouseEvent) => void 滑鼠在該視圖(或其內的視圖)上移動,不論有否按下按鈕;或者在沒有按下任何按鈕、滑鼠停留其上時,按下或放開了修飾鍵(對應 DOM 的 mousemove)。
onMouseLeave .on_mouse_leave(..) (e: MouseEvent) => void 滑鼠離開了該視圖及其內的視圖,或離開了視窗;e 是它最後所在的位置(對應 DOM 的 mouseleave)。

與 CSS 的 :hover 和 DOM 的 mouseenter 一樣,只要滑鼠在視圖上,或在其內任何內容上,該視圖即算被懸停,因此滑鼠下方那個視圖的外層視圖同樣會收到 onMouseEnter(由外至內;onMouseLeave 則由內至外)。這些事件在事後才觸發,觸控時永不觸發。若滑鼠下方的視圖被移除,該視圖會收到 onMouseLeave,在 Linux 和 Windows 上要待下次滑鼠移動時才收到。

MouseEvent 說明滑鼠的位置,與 GPUI 的 MouseMoveEvent 相同:position 是在該視圖內的位置(以點為單位的 Point,已還原其變換);pagePosition 是在其所屬根視圖內的位置,與觸控事件提供的一致;modifiers 是按住的修飾鍵(control、alt、shift、platform:在 Apple 平台上是 Command,在其他平台上是 Windows 鍵);pressedButton 是按住的按鈕("left"、"right"、"middle"、"back" 或 "forward",即 GPUI 的 MouseButton),或 null;在 Rust 中則為 position、page_position、modifiers 和 pressed_button。在桌面平台和網頁上,拖曳同時也是一次觸控(onTouchMove,即 responder),其移動會傳到 onMouseMove,並設定了 pressedButton:只需跟隨懸停的回饋會略過這些事件;在 iOS 和 Android 上,懸停回報的是沒有按下任何按鈕的指標。移動事件每幀最多一次,只取最新的一次,並且一定在緊隨其後的 onMouseLeave 之前送達,因此處理函式可以在每次移動時重繪:

// A crosshair over a chart.
let at = signal(None::<Points>);

view()
    .h(px(200.0))
    .on_mouse_move(move |e: &MouseEvent| at.set(Some(e.position.x)))
    .on_mouse_leave(move |_: &MouseEvent| at.set(None))
    .child(when(move || at.get().is_some(), move || {
        view()
            .absolute()
            .left(move || at.get().unwrap_or_default())
            .top_0()
            .bottom_0()
            .w(px(1.0))
            .bg(rgb(0x888888))
    }))

#工具提示

tooltip 可為任何元素加上提示。在桌面平台(macOS、Windows、Linux、GPUI 後端和桌面瀏覽器)上,指標在元素上停留半秒後,提示會顯示在元素上方,直至指標離開或按下該元素為止。手機和平板電腦不會顯示提示。提示不會接管指標或按下操作,因此元素的運作與沒有提示時完全一樣。

屬性 Rust 類型 說明
tooltip .tooltip(..) JSX.Element 提示,一個由 app 設定樣式的元素:Tooltip 的 nsh/base 本身就是工具提示,並自帶位置、延遲和事件;其他任何元素則按原樣顯示。
view()
    .on_press(save)
    .tooltip(
        tooltip()
            .rounded_md()
            .bg(rgb(0x111827))
            .px_2()
            .py_1()
            .child(text("Save the document").text_color(white())),
    )
    .child(text("💾"))

Tooltip 自行設定外觀、位置、延遲和事件,就像 GPUI 的 .tooltip(..) 所接受的那樣。工具提示很多的 app,會自備一個小元件,把文字包在它的 Tooltip 中。

#情境選單

onContextMenu 會告知視圖,其上按下了次要按鈕:右鍵按一下、macOS 上的 Control-按一下、觸控板上的雙指按一下、iPad 指標的次要按一下;在瀏覽器中還包括鍵盤的選單鍵。在此打開 app 自己的選單;在 TSX 中,Menu.popup 則顯示系統的選單:

// The app's own menu, placed where the click was.
let menu_at = signal(None::<Point<Points>>);

view()
    .on_context_menu(move |e: &ContextMenuEvent| menu_at.set(Some(e.page_position)))
    .child(text(row.name.clone()))
屬性 Rust 類型 說明
onContextMenu .on_context_menu(..) (e: ContextMenuEvent) => void 在該視圖或其內的視圖上按下了次要按鈕。

ContextMenuEvent 包含視圖內的 position、其根視圖內的 pagePosition(Menu.popup 用它來放置選單:Menu.popup(items, e.pagePosition))以及 modifiers;在 Rust 中則為 position、page_position 和 modifiers。由指標下方的視圖開始向外,最近的、有監聽的視圖會收到事件。若有文字輸入框,或帶編輯選單的可選取文字(Windows、GPUI 後端、瀏覽器)比該視圖更近,則會改為顯示它自己的選單。平台在按鈕按下的一刻,根據已掛載的內容作出決定,所以處理函式是事後才獲通知,無需取消任何東西。disabled 視圖會把點按交給其外層最近的已啟用監聽者。在觸控螢幕上,長按會打開選單:請用同一個函式處理 onLongPress。

#鍵盤焦點

在 macOS 和網頁上,Tab 鍵會按樹狀順序在各個停駐點之間移動鍵盤焦點:輸入框、接受按下操作的視圖,以及每個 focusable 視圖。焦點樣式(focus:、.focus(..))會為取得焦點的視圖設定樣式;若視圖接受按下操作,按 Return 或空白鍵即可按下它。點按會按下視圖但不會令它取得焦點,因此焦點樣式只會在鍵盤焦點下顯示。

焦點和按下操作是分開的。在取得焦點時接收按鍵的視圖(圖表、滑桿、以方向鍵移動選擇項的列表)會設定 focusable,並在取得焦點時綁定其按鍵(onFocus / onBlur,配合 keymap);按下它時,操作仍會傳到其外層可按下的視圖,例如會打開頁面的卡片。

ref 的 focus() 會像 Tab 鍵一樣把鍵盤焦點移到某個停駐點(其焦點樣式會顯示,onFocus 也會觸發),例如在彈出視窗關閉後把焦點交還給觸發它的元素;blur() 則會移除焦點。

外層視圖可在焦點位於自身或其內任何視圖時,用 focus-within:(focusWithinStyle、.focus_within(..))設定自己的樣式:例如包住正在編輯的輸入框的欄位外框,或其中一個按鈕取得焦點時的工具列。Overlay 的內容算作該疊加層所在視圖之內,不論它顯示在何處,因此彈出視窗算是包含其觸發元素的視圖的一部分。在 Rust 中,元件可透過其 ref 的 on_focus_in(..) 和 on_focus_out(..) 得知焦點進入和離開某個元素:在元素內部的視圖之間移動則兩者皆不算。

屬性 Rust 類型 預設值 說明
focusable .focusable(..) boolean 接受按下操作的視圖為 true 視圖是否為 Tab 的停駐點。true 令任何視圖都成為停駐點,不論它是接受按下操作,還是把按下操作留給外層視圖;false 則讓可按下的視圖只供指標操作(例如關閉彈出框的背景遮罩、以方向鍵在其間移動的列)。
onFocus .on_focus(..) () => void Tab(或 focus())令視圖取得了鍵盤焦點。
onBlur .on_blur(..) () => void 鍵盤焦點離開了視圖。
view()
    .rounded_lg()
    .p_3()
    .focus(|s| s.bg(rgb(0xe0f2fe)))
    .on_press(open)
    .child(text("Open"))

// An overlay's backdrop: a click closes it, Tab goes past it to the content.
view().absolute().inset_0().focusable(false).on_press(close)

// A chart in a card: Tab stops at the chart, a click opens the card's page.
view().on_press(open_page).child(
    view()
        .h(px(160.0))
        .focusable(true)
        .focus(|s| s.bg(rgb(0xe0f2fe)))
        .on_focus(move || focused.set(true))
        .on_blur(move || focused.set(false)),
)

#版面配置與識別

屬性 Rust 類型 說明
onLayout .on_layout(|frame: &Bounds<Points>| ..) (e: { bounds: Bounds }) => void bounds 是視圖相對於其父視圖的框架,{ origin, size },在每次令其改變的版面配置之後提供。
ref .element_ref(&handle) ViewRef | (node: ViewRef) => void 元素的 ref(見 ref 方法):ViewRef,或建基於其上的元素自身類型。ref={el} 指派變數,ref={fn} 呼叫函式。
nativeID .native_id(id) string 為視圖命名以供其他元素參照,例如輸入框所參照的 InputAccessoryView。
testID .test_id(id) string 供 UI 測試使用的識別碼(在 Android 上為視圖的 resource id)。
key .key(key) string 元素在其同層元素中的身分。

#以手勢驅動動畫值

這些屬性把手勢綁定到原生動畫值,動畫值在 UI 執行緒上移動,app 的程式碼無需逐幀處理。參閱觸控與手勢。

屬性 Rust 類型 說明
pan .pan_values(Some(&x), Some(&y)) { x?: AnimatedValue; y?: AnimatedValue } 拖曳會驅動這些值(單位為點)。
onPanStart .on_pan_start(..) (e: PanEvent) => void 拖曳開始。
onPanEnd .on_pan_end(..) (e: PanEvent) => void 拖曳結束,並附帶放開時的速度(例如用來啟動 decay)。
pinch .pinch_value(&scale) { scale?: AnimatedValue } 雙指捏合會驅動 scale。
onPinchStart / onPinchEnd .on_pinch_start(..) / .on_pinch_end(..) (e: { scale: number; velocity: number }) => void 捏合開始 / 結束。
rotation .rotation_value(&angle) { angle?: AnimatedValue } 雙指旋轉會驅動 angle(單位為度)。
onRotationStart / onRotationEnd .on_rotation_start(..) / .on_rotation_end(..) (e: { rotation: number; velocity: number }) => void 旋轉開始 / 結束。

PanEvent 是 { cancelled, translation, velocity, value? }(在 Rust 中為 cancelled、translation、velocity_x、velocity_y、value_x、value_y):包括系統是否取消了拖曳、自開始以來的位移、以每秒點數計的速度(各個 Point),以及當下已綁定的值,即 { x?, y? },對應 pan 所綁定的軸(拖曳可以在動畫進行中抓住某個值)。

#原始觸控與 responder 系統

上述屬性未涵蓋的手勢,可使用每個元素都支援的觸控和 responder 屬性:onTouchStart、onTouchMove、onTouchEnd、onTouchCancel(及其 …Capture 形式)、onStartShouldSetResponder、onMoveShouldSetResponder(及 …Capture)、onResponderGrant、onResponderReject、onResponderStart、onResponderMove、onResponderEnd、onResponderRelease、onResponderTerminate 和 onResponderTerminationRequest。每個處理函式都會收到一個 GestureResponderEvent ({ identifier, locationX, locationY, pageX, pageY, target, timestamp, touches, changedTouches, nativeEvent, stopPropagation() }); the …ShouldSetResponder handlers also get a gestureState (dx, dy, vx, vy, …)。 在 onResponderGrant 中回傳 true 可防止原生捲動視圖接管觸控。panActivation ({ activeOffsetX, activeOffsetY, failOffsetX, failOffsetY })會令視圖只在平台啟動其平移手勢之後才成為 responder,因此捲動視圖內的拖曳會把另一個軸留給捲動視圖 (觸控與手勢)。 PanResponder 中的 PanResponder 建基於這些屬性。在 Rust 中,.on_touch(..) 會接收每一次觸控 (TouchEvent),.claim_touches(..) 決定視圖何時接管觸控,.on_responder_grant(..)、 .on_responder_move(..)、.on_responder_release(..) 及其他則跟隨 responder。

#無障礙功能

屬性 Rust 類型 說明
accessible .accessible(..) boolean 該視圖對輔助技術而言是單一元素;其子視圖不會被個別公開。
accessibilityLabel .label(..) string 螢幕閱讀器朗讀的內容。沒有標籤的視圖會朗讀其文字。
accessibilityHint .hint(..) string 啟用該元素會發生的操作。
role .role(AccessibilityRole::Button) AccessibilityRole 元素的類別("button"、"header"、"link"、"switch"……)。
accessibilityState .accessibility_state(..) { disabled?, selected?, checked?, busy?, expanded?, pressed?, current? } 輔助技術回報的狀態(checked 和 pressed 可以是 "mixed";current 是 true 或 "page"、"step"、"location"、"date"、"time")。
accessibilityLevel .accessibility_level(..) number 標題的級別(1 為最高),樹狀項目的深度(1 為最頂層)。
accessibilityValue .accessibility_value(..) { min?, max?, now?, text? } 範圍型或文字型元素的值。
accessibilityElementsHidden boolean 對輔助技術隱藏該視圖及其子樹。
importantForAccessibility "auto" | "yes" | "no" | "no-hide-descendants" 視圖是否被公開。
accessibilityActions { name: string; label?: string }[] 提供給輔助技術的自訂動作。
onAccessibilityAction .on_accessibility_action(..) (e: { actionName: string }) => void 用戶觸發了 accessibilityActions 之一。

參閱無障礙功能。

#平台專屬屬性

只存在於單一平台的屬性,在 TSX 中放在以該平台命名的物件內,因此共用屬性在各處都保持一致。只有當前平台的物件會被傳送,其餘的會被忽略。Rust builder 則以方法接受這些屬性,並只在相應平台上生效。

屬性 說明
ios={{…}} iOS 和 Mac Catalyst。<Input>、Dialog 和 WebView 會加入各自的鍵。
android={{…}} Android。每個元素都支援 ripple、elevation 和 accessibilityLiveRegion(見下文)。
desktop={{…}} 桌面平台和桌面瀏覽器;在行動平台上會被忽略。每個元素都支援 windowDrag(見下文)。
web={{…}} 瀏覽器;在其他平台上會被忽略。
android 鍵 Rust 類型 說明
ripple .ripple(Ripple { .. }) { color?, borderless?, radius?, foreground? } 按下時的 material 漣漪效果。
elevation .elevation(px(2.0)) number material 陰影(單位為點),形狀取自視圖。
accessibilityLiveRegion "none" | "polite" | "assertive" 視圖的變更如何向 TalkBack 播報。
desktop 鍵 Rust 類型 說明
windowDrag .window_drag(..) boolean 按下該視圖會拖動視窗,效果如同其標題列;false 令可拖動視圖的某部分仍可點按。參閱自訂標題列。
use nsh_core::Ripple;

view()
    .rounded_lg()
    .bg(white())
    .p_4()
    .elevation(px(2.0))
    .ripple(Ripple { color: Some(rgba(0x00000022).into()), ..Ripple::default() })
    .on_press(save)
    .child(text("Save"))

參閱平台專屬程式碼。

#Ref 方法

在 TSX 中,ref 會接收元素的 ref,其類型就是該元素本身的類型:ViewRef(focus、blur、measure)用於 View、Text、Image、Canvas、Overlay 和 Shader;其餘元素則使用以 ViewRef 為基礎、並附有該元素方法的類型:InputRef(Input)、ScrollRef(Scroll)、ListRef(List)、WebViewRef(WebView)、VideoRef(Video)和 NativeComponentRef(原生元件)。在 Rust 中,element_ref() 會建立一個 ElementRef 控制代碼,由 .element_ref(&handle) 附加。這些方法會向原生視圖發送指令,並與同一輪中先前所作的變更按順序執行。各元素頁面會說明哪些方法適用於該元素。ref 變數應以其元素本身的類型標註(let field!: InputRef 用於 <Input>):類型為 ViewRef 的變數,並不適用於 ref 帶有自身方法的元素。

方法 Rust Ref 說明
focus() / blur() focus() / blur() ViewRef 令 <Input> 取得焦點(顯示鍵盤),或令接受按壓的視圖取得/取消鍵盤焦點。
measure() measure() ViewRef 視圖的位置。
setSelection(start, end?) set_selection(start, end) InputRef 以 UTF-16 單位選取 start..end;沒有 end 時則設定插入點。
scrollTo({ x?, y?, animated? }) scroll_to(point, animated) ScrollRef、ListRef 捲動至指定偏移量。
zoomTo({ scale, x?, y?, animated? }) zoom_to(scale, center, animated) ScrollRef(可縮放的 <Scroll>) 以內容中某一點為中心縮放至 scale。
scrollToOffset({ offset, animated? }) scroll_to_offset(offset, animated) ListRef 沿列表的軸向捲動至指定偏移量。
scrollToIndex({ index, animated?, viewPosition?, viewOffset? }) scroll_to_index(index, position, offset, animated) ListRef 捲動至項目可見。
scrollToEnd({ animated? }) scroll_to_end(animated) ScrollRef、ListRef 捲動至內容末端。
reload()、goBack()、goForward()、stopLoading()、injectJavaScript(script)、postMessage(data) reload()、go_back()、go_forward()、stop_loading()、inject_java_script(..)、post_message(..) WebViewRef 控制頁面。
seek(seconds) seek(seconds) VideoRef 跳至指定時間。
command(name, args?) command(name, args) NativeComponentRef 呼叫已註冊原生元件的指令(nativeComponent)。
let field = element_ref();

view()
    .on_press(move || field.focus())
    .child(input().element_ref(&field).placeholder("Tap the row to type"))

#為何處理函式不會阻塞 UI

主執行緒從不等待應用程式的程式碼。當系統需要即時得到答覆時(是否允許此文字變更、返回按鈕是否可以彈出、此 sheet 是否可以關閉),NSH 會根據事先設定的 prop(maxLength、popOnBack、closeOnSwipe……)在原生層直接回應,事件其後才會傳到應用程式。這就是此類行為屬於 prop,而不是處理函式回傳值的原因。