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

元素

Text

Text 用來繪製段落。它是顯示字串的元素:View 的字串子節點不會繪製任何內容。段落由原生排版(iOS 與 Mac Catalyst 使用 CoreText,Android 使用 StaticLayout),並在 engine thread 上測量,因此主執行緒只負責繪製。Rust 使用 text(content) 建立,TSX 則使用 nsh 的 <Text>。

el-text.tsx
el-text.tsxLive
import { mount, signal, Text, View } from "nsh";
import { button, buttonText, focusRing, ui } from "./palette";

const LONG =
  "Text is typeset natively (CoreText on iOS, StaticLayout on Android) and measured on the engine thread, so the main thread only draws it.";

function App() {
  const tapped = signal("nothing yet");
  const lines = signal(2);
  const link = () => ({ color: ui().accent, textDecorationLine: "underline" }) as const;
  return (
    <View style={{ flex: 1, backgroundColor: ui().page }}>
      <View style={{ flex: 1, width: "100%", maxWidth: 560, alignSelf: "center", padding: 20, paddingTop: 24, gap: 14 }}>
        <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>text</Text>

        <Text style={{ fontSize: 16, lineHeight: 24, color: ui().text }}>
          {"Nested text elements are spans: "}
          <Text style={{ fontWeight: "700" }}>bold</Text>
          {", "}
          <Text style={{ fontStyle: "italic", color: ui().violet }}>italic</Text>
          {", and "}
          <Text style={link()} onPress={() => tapped.set("the first link")} hoverStyle={{ opacity: 0.88 }} focusStyle={focusRing()} activeStyle={{ opacity: 0.7 }}>
            links
          </Text>
          {" that take presses of "}
          <Text style={link()} onPress={() => tapped.set("the second link")} hoverStyle={{ opacity: 0.88 }} focusStyle={focusRing()} activeStyle={{ opacity: 0.7 }}>
            their own
          </Text>
          .
        </Text>
        <Text style={{ fontSize: 13, color: ui().muted }}>Pressed: {tapped()}</Text>

        <View style={{ height: 1, backgroundColor: ui().line }} />

        <Text style={{ numberOfLines: lines(), fontSize: 15, color: ui().text }}>
          {LONG}
        </Text>
        <View style={{ flexDirection: "row", gap: 8 }}>
          <View
            onPress={() => lines.set(lines() === 2 ? 0 : 2)}
            {...button()}
          >
            <Text style={buttonText()}>{lines() === 2 ? "Show all" : "numberOfLines 2"}</Text>
          </View>
        </View>

        <Text style={{ numberOfLines: 1, textOverflow: "ellipsis-middle", fontSize: 15, color: ui().muted }}>
          {LONG}
        </Text>

        <Text style={{ fontSize: 17, color: ui().text }}>
          <Text style={{ textDecorationLine: "line-through", textDecorationColor: ui().down }}>struck</Text>{" "}
          <Text style={{ textTransform: "uppercase", letterSpacing: 2 }}>spaced</Text>{" "}
          <Text style={{ fontVariant: ["tabular-nums"] }}>0123456789</Text>
        </Text>

        <Text
          style={{
            fontSize: 28,
            fontWeight: "800",
            color: ui().onAccent,
            backgroundColor: "#c8f26d",
            paddingHorizontal: 10,
            borderRadius: 8,
            textShadow: "2px 2px 4px rgba(255,255,255,0.55)",
          }}
        >
          Text shadow
        </Text>
      </View>
    </View>
  );
}

mount(App);
use nsh_core::prelude::*;

text("Read the ")
    .text_base()
    .line_height(px(24.0))
    .text_color(rgb(0x1f2328))
    .child(text("terms").font_weight(FontWeight::BOLD))
    .child(" before you continue.")

text(..) 接受字串、SharedString、signal 或 closure(text(move || format!("{} items", count.get()))): 使用 closure 或 signal 時,只要其讀取的內容改變,文字就會原地更新。

#Spans

Text 內的 Text 是同一個段落的 span,而不是獨立的 view。Span 會繼承段落的文字樣式(color、fontSize、fontWeight 等),並覆寫自己設定的部分。整個區塊是一個原生 view。在 Rust 中,text(..) 會先顯示其內容,再顯示其子節點:.child(text(..)) 新增一個 span,.child("…") 新增一段純文字。

text("")
    .text_size(px(17.0))
    .child(text("Light ").font_weight(FontWeight::LIGHT))
    .child(text("Bold ").font_weight(FontWeight::BOLD))
    .child(text("Italic").italic().text_color(rgb(0x8250df)))

#Span 背景

設有背景的 span 會在其字元後方填滿字元所在的方框:高度等同其字型(基線上方的 ascent 與下方的 descent,不受行高影響);當 span 換行時,每一行中屬於它的部分各自是一個方框,並套用該 span 的圓角半徑。內層的 span 會共用其背景,除非自行設定背景。這只影響繪製:文字的排版與沒有背景時完全相同,可選取的文字在複製時,也會把該 span 的字元與其他內容一併複製。行內程式碼就是一種 span:

text("Run ")
    .text_size(px(15.0))
    .line_height(px(22.0))
    .select_text()
    .child(
        text(" cargo test ")
            .font_family("ui-monospace")
            .text_size(px(13.0))
            .bg(rgb(0xeff1f3))
            .rounded(px(4.0)),
    )
    .child(" before you commit.")

Tailwind class 的運作方式相同:<Text class="bg-gray-100 rounded">。Padding 與邊框屬於方框樣式,不會影響 span;若要讓方框兩側留出空間,可在 span 內加入空格。

#連結

設有按壓處理函式的 span 就是連結:按壓其字元時由該 span 接收,按壓其他位置則由段落接收。

text("By continuing you agree to the ")
    .text_size(px(15.0))
    .on_press(move || log("the sentence"))
    .child(
        text("Terms of service")
            .text_color(rgb(0x0969da))
            .underline()
            .on_press(move || open("terms")),
    )
    .child(".")

#行內 View 與圖片

Text 內的任何其他元素(view、圖片,即使位於 span 內)都是行內附件:它以自身尺寸排版,位於基線上(或依其 verticalAlign 樣式決定的位置:"text-top"、"text-bottom"、"middle",與 CSS 相同;該行會隨之增高以容納它),並與文字一起換行。它可自行接收按壓事件。

text("Made with ")
    .text_base()
    .line_height(px(26.0))
    .child(image(LOGO).size(px(18.0)).rounded(px(4.0)))
    .child(" NSH. Status ")
    .child(
        view()
            .bg(rgb(0x1a7f37))
            .rounded(px(9.0))
            .px(px(7.0))
            .h(px(18.0))
            .justify_center()
            .child(text("LIVE").text_color(white()).text_size(px(11.0)).font_weight(FontWeight::BOLD)),
    )

被 numberOfLines 或省略號截斷的附件會被隱藏。複製可選取的文字時,不會包含附件。

#空白字元

JSX 文字遵循 React 的規則:除文字的頭尾外,每行都會被修剪,只含空白的行會被丟棄,其餘各行以單一空格連接。同一行內的空格會保留({a} {b} 會保留其空格)。當 span 需要前置或尾隨空格時,請寫成 {" "} 或使用字串運算式。Rust 字串則依原樣繪製。

#Props

除了通用 props(其中 style 的型別為 TextStyle)之外:

PropRust型別預設值說明
numberOfLines (style).number_of_lines(n)、.truncate()number0最多顯示的行數;0 表示全部顯示。
textOverflow (style).text_overflow(EllipsizeMode::Middle)"ellipsis" | "clip" | "ellipsis-start" | "ellipsis-middle""ellipsis"當文字超出 numberOfLines 時的截斷方式:在結尾、開頭或中間加上省略號,或直接裁切。
allowFontScaling.allow_font_scaling(..)booleantrue跟隨系統的文字大小設定。
maxFontSizeMultiplier.max_font_size_multiplier(..)number0系統縮放比例的上限;0 表示不設上限。
adjustsFontSizeToFit.shrink_to_fit(min_scale)booleanfalse縮小字型,直到文字符合其行數(numberOfLines)與方框。
minimumFontScale.shrink_to_fit(min_scale)numberadjustsFontSizeToFit 最多縮小的幅度(0..1;至少為 0.1)。
userSelect (style).select_text()、.select_none()"auto" | "none" | "text""auto""text":使用者可以選取文字範圍:長按會選取一個單字,拖曳控制點可延伸選取範圍,編輯選單可複製所選內容。"auto" 採用文字周圍 view 的設定;若都未設定,文字不可選取,但桌面瀏覽器除外:在桌面瀏覽器中,除非文字位於互動式元素內,否則預設可選取;"none" 則讓文字在桌面瀏覽器中同樣不可選取。
selectionColor (style).selection_color(..)ColorValue平台的強調色選取範圍的醒目標示與控制點。
onTextLayout(e: { lines: TextLayoutLine[] }) => void每次排版後已排版的各行。

Tailwind class select-text、select-none 與 select-auto 會設定 userSelect,line-clamp-* 會設定 numberOfLines,text-ellipsis 與 text-clip 會設定 textOverflow,而 truncate 表示單行並以省略號截斷。選取範圍的醒目標示與控制點使用樣式鍵 selectionColor(selection:text-*),預設為平台的強調色。

#文字樣式

TextStyle 在 view 樣式之外新增:color、fontSize、fontWeight("normal"、"bold" 或 100–900)、fontStyle、fontFamily、lineHeight、letterSpacing、textAlign、textAlignVertical、textTransform、textDecorationLine、textDecorationColor、textDecorationStyle、textDecorationThickness、textUnderlineOffset、textShadow(CSS 的 text-shadow)、fontVariant、writingDirection、numberOfLines 與 textOverflow。在 Rust 中,它們是 Styled 的方法:.text_color(..)、.text_size(..)(或 .text_sm()、.text_lg() 等)、.font_weight(FontWeight::SEMIBOLD)、.italic()、.font_family(..)、.line_height(..)、.letter_spacing(..)、.text_center()、.uppercase()、.underline()、.line_through()、.tabular_nums()、.number_of_lines(..) 及其餘方法。請參閱樣式參考與 text。

字型家族可以是系統字型家族,或應用程式內的字型檔:

// assets/fonts/Inter-Regular.ttf, shipped by the registry (`registry.assets(assets!())`).
const INTER: FontAsset = asset!("fonts/Inter-Regular.ttf");

text("Inter").font_family(INTER.family).text_size(px(17.0))

字型會在第一個影格之前註冊。請參閱字型。

#事件

事件RustPayload時機
onTextLayout{ lines: TextLayoutLine[] }每次段落排版之後。
onPress、onLongPress 等.on_press、.on_long_press 等PressEvent(Rust 中沒有)與 view 相同;在 span 上則是按壓其字元時觸發。
onLayout.on_layout{ bounds }區塊的 frame 改變時。

TextLayoutLine 是 { text, x, y, width, height, ascender, descender, capHeight, xHeight },位於文字的內容方框中:

const lines = signal(0);
<Text onTextLayout={(e) => lines.set(e.lines.length)}>{article}</Text>;

Rust 在排版之前,會使用 measure_text(..) 測量字串。

#Ref 方法

沒有專屬的方法。

#平台說明

  • 選取範圍僅限於同一個文字區塊內(一個 Text 及其 span)。iOS 會顯示其編輯選單(拷貝、全選、查詢、翻譯、分享),Android 則顯示浮動工具列(拷貝、全選、分享)。
  • 實線單線以外的文字裝飾(double、dotted、dashed、wavy)在 iOS 上由 NSH 繪製。
  • iOS 的文字會繪製實色背景與外陰影。
  • nsh 的 fontScale()(Rust 中為 font_scale())會回報目前的系統文字縮放比例。