Diese Seite wurde maschinell übersetzt und kann Fehler enthalten. Das englische Original lesen

Dokumentation

Scrollen

Die Scroll View (scroll() in Rust, <Scroll> in TSX) ist die plattformeigene Scroll View (UIScrollView unter iOS, eine ScrollView unter Android): Flings, Bounces, der Scroll-Indikator und Pull-to-Refresh sind nativ, ebenso das Snapping. Dein Code erfährt über Events vom Scrollen und kann den Offset an einen animierten Wert binden, für Effekte, die dem Finger Frame für Frame folgen.

guide-scrolling.tsx
guide-scrolling.tsxLive
import { mount, Scroll, type ScrollRef, signal, Text, View } from "nsh";
import { createAnimatedValue } from "nsh";
import { button, buttonText, desktop, size, tint, ui, wide } from "./palette";

// Tints of the info, violet, danger, warn and up colours under the text colour.
const hues = ["info", "violet", "down", "warn", "up", "faint"] as const;
const hue = (i: number) => tint(ui()[hues[i % hues.length]], 0.28);

/** Pages as wide as the scroll view, with a page indicator. */
function Pager() {
  const width = signal(340);
  const page = signal(0);
  return (
    <View style={{ gap: 8 }}>
      <Scroll
        horizontal
        pagingEnabled
        scrollbar={false}
        style={{ height: 110, borderRadius: 12 }}
        onLayout={({ bounds }) => width.set(bounds.size.width)}
        onMomentumEnd={({ offset }) => page.set(Math.round(offset.x / width()))}
        onScrollEnd={({ offset }) => page.set(Math.round(offset.x / width()))}
      >
        <View style={{ flexDirection: "row" }}>
          <Each of={[0, 1, 2, 3]}>
            {(i) => (
              <View style={{ width: width(), height: 110, backgroundColor: hue(i()), alignItems: "center", justifyContent: "center" }}>
                <Text style={{ color: ui().text, fontSize: 24, fontWeight: "700" }}>Page {i() + 1}</Text>
              </View>
            )}
          </Each>
        </View>
      </Scroll>
      <View style={{ flexDirection: "row", justifyContent: "center", gap: 6 }}>
        <Each of={[0, 1, 2, 3]}>
          {(i) => <View style={{ width: 8, height: 8, borderRadius: 4, backgroundColor: page() === i() ? ui().accent : ui().border }} />}
        </Each>
      </View>
    </View>
  );
}

/** Cards that snap: one stop every card plus gap. */
function Cards() {
  return (
    <Scroll horizontal scrollbar={false} snapToInterval={152} decelerationRate="fast" style={{ height: 90 }}>
      <View style={{ flexDirection: "row", gap: 12, paddingHorizontal: 4 }}>
        <Each of={Array.from({ length: 12 }, (_, i) => i)}>
          {(i) => (
            <View
              style={{
                width: 140,
                height: 90,
                borderRadius: 14,
                backgroundColor: hue(i()),
                boxShadow: "inset 0 1px 0 rgba(255,255,255,0.08)",
                alignItems: "center",
                justifyContent: "center",
              }}
            >
              <Text style={{ color: ui().text, fontWeight: "700" }}>Card {i() + 1}</Text>
            </View>
          )}
        </Each>
      </View>
    </Scroll>
  );
}

function App() {
  // The vertical offset, written natively in the frame it changes.
  const offset = createAnimatedValue(0);
  const y = signal(0);
  const refreshing = signal(false);
  const refreshed = signal(0);
  let scroll!: ScrollRef;
  const refresh = () => {
    refreshing.set(true);
    setTimeout(() => {
      refreshed.update((n) => n + 1);
      refreshing.set(false);
    }, 1200);
  };
  return (
    <View style={{ flex: 1, backgroundColor: ui().page, paddingTop: 24 }}>
      {/* A bar that fades in and shrinks its title as the page scrolls: no JS per frame. */}
      <View
        style={{
          height: 48,
          paddingHorizontal: 16,
          justifyContent: "center",
          backgroundColor: offset.interpolate({ inputRange: [0, 80], outputRange: [ui().page, ui().card.backgroundColor] }),
          borderBottomWidth: 1,
          borderBottomColor: ui().line,
        }}
      >
        <Text
          style={{
            fontSize: 22,
            fontWeight: "700",
            color: ui().text,
            transformOrigin: "left center",
            scale: offset.interpolate({ inputRange: [0, 80], outputRange: [1, 0.8] }),
          }}
        >
          Scrolling
        </Text>
      </View>
      <Scroll
        ref={scroll}
        style={{ flex: 1 }}
        scrollValue={offset}
        scrollEventThrottle={100}
        onScroll={(e) => y.set(Math.round(e.offset.y))}
        refreshing={refreshing()}
        onRefresh={refresh}
        keyboardDismissMode="on-drag"
      >
        <View style={{ padding: 16, gap: 18, maxWidth: wide() ? 720 : undefined }}>
          <Text style={{ color: ui().muted }}>
            {desktop ? `Scroll with the wheel or the trackpad. onScroll: y = ${y()}` : `Pull down to refresh (${refreshed()}×). onScroll: y = ${y()}`}
          </Text>
          <Text style={{ fontWeight: "600", color: ui().text }}>pagingEnabled</Text>
          <Pager />
          <Text style={{ fontWeight: "600", color: ui().text }}>snapToInterval</Text>
          <Cards />
          <View style={{ gap: desktop ? 8 : 18 }}>
            <Each of={Array.from({ length: 24 }, (_, i) => i + 1)}>
              {(n) => (
                <View
                  style={
                    desktop
                      ? { ...ui().row, height: size.row, justifyContent: "center", paddingHorizontal: 12, borderRadius: 8 }
                      : { ...ui().row, padding: 14, borderRadius: 12 }
                  }
                >
                  <Text style={{ color: ui().text, fontSize: desktop ? size.label : undefined }}>Row {n()}</Text>
                </View>
              )}
            </Each>
          </View>
          <View
            onPress={() => scroll.scrollTo({ y: 0 })}
            {...button("primary")}
          >
            <Text style={buttonText("primary")}>Back to the top (ref.scrollTo)</Text>
          </View>
        </View>
      </Scroll>
    </View>
  );
}

mount(App);

Verwende eine Scroll View für Inhalte bekannter, mäßiger Größe: eine Einstellungsseite, ein Formular, einen Artikel, eine Reihe von Chips. Für lange oder unbegrenzte Daten nimm eine Liste, die Zeilen wiederverwendet und dieselben Scrolling-Props akzeptiert.

#Eine Scroll View

Eine Scroll View ist der Viewport; ihre Kinder sind der Inhalt. Der Viewport braucht eine Größe von seinem Parent (meist flex: 1, .flex_1()), und der Inhalt wird in der Breite (vertikal) bzw. Höhe (horizontal) des Viewports angeordnet und ist so lang, wie er sein muss.

#[component]
pub fn App() -> impl IntoElement {
    scroll().flex_1().bg(rgb(0xf6f8fa)).child(
        view().p_4().gap_3().children((1..=50).map(|n| {
            view().p_4().rounded(px(10.0)).bg(white()).child(text(format!("Row {n}")))
        })),
    )
}

Die obige Child View ist der Content Container der Scroll View; in TSX erzeugt contentStyle (oder contentClass) einen solchen, indem es die Kinder in eine View mit diesem Style packt, die mit ihnen scrollt. flexGrow: 1 (.flex_grow(1.0)) macht den Inhalt mindestens so hoch wie den Viewport, sodass kurzer Inhalt zentriert werden kann und langer trotzdem scrollt:

scroll().flex_1().child(
    view().flex_grow(1.0).justify_center().p_4().child(
        when(move || messages.with(|m| !m.is_empty()), move || {
            each(messages, |m| m.id, |m, _| text(move || m.with(|m| m.text.clone())))
        })
        .otherwise(|| text("No messages yet")),
    ),
)

Eine horizontale Scroll View nimmt horizontal (.horizontal()) und eine Zeile als Kind:

scroll().horizontal().scrollbar(false).child(
    view().flex_row().gap_2().px_4().children(["Today", "This week", "This month", "This year"].map(|label| {
        text(label).px(px(14.0)).py_2().rounded(px(16.0)).overflow_hidden().bg(white())
    })),
)

Bei Rechts-nach-links-Layout (siehe Gerät und Umgebung) beginnt eine horizontale Scroll View an ihrem rechten Ende und behält ihren Abstand zu diesem.

Finger ziehen Scroll Views. Auf dem Desktop (macOS, Windows, Linux) und im Browser scrollt die Maus wie in Desktop-Apps: mit dem Mausrad (Shift + Mausrad scrollt horizontal), dem Touchpad, der Tastatur und dem Scroll-Indikator, der erscheint, solange die Maus über der View steht, und sich wie eine Scrollleiste ziehen lässt; Maus-Drags gehen an die Pans, Responder und die Textauswahl der App. Touchscreens an Desktops ziehen Scroll Views wie Finger. Horizontale Views behalten dort ihren Indikator auch mit scrollbar={false}, denn damit bewegt man sie mit einer Maus ohne horizontales Rad.

#Verhaltens-Props

Prop Rust Typ Standard Beschreibung
horizontal .horizontal() boolean false Scrollt seitwärts.
scrollable .scrollable(..) boolean true Der Nutzer kann scrollen. Scrollen per Code funktioniert weiterhin.
bounces .bounces(..) boolean true Overscroll über die Ränder hinaus.
scrollbar .scrollbar(..) boolean true Der Scroll-Indikator wird angezeigt. Auf dem Desktop und in Desktop-Browsern behält ihn eine horizontale View: Er ist die Scrollleiste der Maus.
contentInset .content_inset(EdgeInsets { .. }) Partial<Edges> Platz um den Inhalt, in den hineingescrollt werden kann.
contentOffset .content_offset(point(..)) { x?, y? } Wo sie beginnt.
decelerationRate .deceleration(rate) "normal" | "fast" | number der Plattform Wie schnell ein Fling langsamer wird: "normal", "fast" oder die pro Millisekunde beibehaltene Geschwindigkeit (0.998 ist normal, 0.99 schnell; 0 ist der Standard der Plattform).

#Paging und Snapping

Das Snapping wird nativ beim Loslassen des Fingers entschieden, anhand des Offsets beim Loslassen, des projizierten Gleitwegs und der Geschwindigkeit; die Scroll View gleitet dann zum gewählten Haltepunkt, ganz ohne dein Zutun.

Prop Rust Typ Beschreibung
pagingEnabled .paging(true) boolean Haltepunkte bei Vielfachen der Viewport-Größe.
snapToInterval .snap_interval(px(..)) number Haltepunkte bei Vielfachen dieser Länge (0: aus).
snapToOffsets .snap_offsets(vec![..]) number[] Haltepunkte bei diesen Offsets.
snapToAlignment .snap_alignment(SnapAlignment::Center) "start" | "center" | "end" Mit snapToInterval: Welcher Punkt eines Elements am Viewport ausgerichtet wird.
disableIntervalMomentum .disable_interval_momentum(true) boolean Ein Fling bewegt höchstens um einen Haltepunkt weiter.

Ein Pager: Seiten so breit wie die Scroll View und ein Indikator, der aktualisiert wird, wenn das Gleiten endet.

const COLORS: [u32; 4] = [0x0969da, 0x8250df, 0xbf3989, 0x1a7f37];

pub fn pager() -> impl IntoElement {
    let width = signal(0.0_f32);
    let page = signal(0_usize);
    let settle = move |e: &Point<Points>| {
        let width = width.get_untracked();
        if width > 0.0 {
            page.set((e.x.0 / width).round() as usize);
        }
    };
    let pages = COLORS.iter().enumerate().map(|(i, &color)| {
        view()
            .w(move || px(width.get()))
            .h(px(160.0))
            .bg(rgb(color))
            .items_center()
            .justify_center()
            .child(text(format!("Page {}", i + 1)).text_color(white()).text_2xl().font_weight(FontWeight::BOLD))
    });
    let dots = (0..COLORS.len()).map(|i| {
        view()
            .size_2()
            .rounded(px(4.0))
            .bg(move || if page.get() == i { rgb(0x0969da) } else { rgb(0xd0d7de) })
    });
    view()
        .gap_2()
        .p_4()
        .child(
            scroll()
                .horizontal()
                .paging(true)
                .scrollbar(false)
                .h(px(160.0))
                .rounded(px(12.0))
                .on_layout(move |frame: &Bounds<Points>| width.set(frame.size.width.0))
                .on_scroll_end(settle)
                .child(view().flex_row().children(pages)),
        )
        .child(view().flex_row().justify_center().gap(px(6.0)).children(dots))
}

Ein Karussell aus Karten, die zur Mitte hin einrasten, eine Karte pro Fling:

scroll()
    .horizontal()
    .snap_interval(px(232.0))          // card width (220) + gap (12)
    .snap_alignment(SnapAlignment::Center)
    .deceleration(0.99)
    .disable_interval_momentum(true)
    .scrollbar(false)
    .child(view().flex_row().gap_3().px_4().children(cards))

Listen akzeptieren dieselben Snapping-Props, weshalb sie für ein langes Karussell die bessere Wahl sind.

#Scroll-Events

Prop Rust Payload Wann
onScroll .on_scroll(..) ScrollEvent Der Offset hat sich geändert. Höchstens einmal pro Frame; scrollEventThrottle vergrößert den Abstand weiter.
scrollEventThrottle .event_throttle(ms) number Minimale Millisekunden zwischen onScroll-Events.
onScrollDragStart .on_scroll_drag_start(..) { offset } Der Nutzer hat begonnen zu ziehen.
onScrollDragEnd .on_scroll_drag_end(..) { offset } Der Nutzer hat den Finger gehoben.
onMomentumStart .on_momentum_start(..) { offset } Das Gleiten nach einem Fling hat begonnen.
onMomentumEnd .on_momentum_end(..) { offset } Das Gleiten ist zu Ende (auch wenn ein neuer Drag es abfängt).
onScrollEnd .on_scroll_end(..) { offset } Ein Nutzer-Scroll ist zur Ruhe gekommen: nach dem Gleiten oder am Ende eines Drags ohne Gleiten.

ScrollEvent enthält den Offset (offset, ein Point; in Rust ebenso), die Größe des Inhalts, gezoomt (contentSize, ein Size; content_size), den Viewport (size) und zoomScale (zoom_scale). Die übrigen Events enthalten den Offset: { offset } mit einem Point in TSX, einem Point<Points> in Rust.

let at_bottom = signal(false);

scroll()
    .flex_1()
    .event_throttle(100.0)
    .on_scroll(move |e: &ScrollEvent| {
        at_bottom.set(e.offset.y + e.size.height >= e.content_size.height - px(20.0))
    })
    .child(content)

#Scroll-gekoppelte Effekte: scrollValue

scrollValue (.scroll_value(&value)) nimmt einen animierten Wert und schreibt den Scroll-Offset (entlang der Hauptachse) nativ in ihn, im selben Frame, in dem sich der Inhalt bewegt. Binde diesen Wert oder daraus mit interpolate, diffClamp (diff_clamp) oder Arithmetik abgeleitete Knoten an Styles.

#[component]
pub fn App() -> impl IntoElement {
    let offset = animated_value(0.0);
    view()
        .flex_1()
        .child(
            view()
                .h(px(56.0))
                .justify_center()
                .px_4()
                // Transparent at the top, white after 80 points.
                .bind_background(&offset.interpolate_colors(&[0.0, 80.0], &[rgba(0xffffff00), rgba(0xffffffff)]))
                .child(
                    text("Inbox")
                        .text_size(px(22.0))
                        .font_weight(FontWeight::BOLD)
                        .origin_left()
                        .bind_scale(&offset.interpolate(&[0.0, 80.0], &[1.0, 0.8])),
                ),
        )
        .child(scroll().flex_1().scroll_value(&offset).child(view().p_4().gap_2()))
}

Ein Header, der beim Hochscrollen des Inhalts wegrutscht und sofort zurückkommt, sobald nach unten gescrollt wird, ist diffClamp des Offsets:

let offset = animated_value(0.0);
let header = offset.diff_clamp(0.0, 56.0);

view()
    .flex_1()
    .child(scroll().flex_1().scroll_value(&offset).child(view().pt(px(56.0))))
    .child(view().absolute().top_0().left_0().right_0().h(px(56.0)).bind_translate_y(&header.mul(-1.0)))

Interpolationen klemmen standardmäßig außerhalb ihres Eingabebereichs, sodass das Ziehen über den oberen Rand hinaus (ein negativer Offset) den Effekt an seinem Ende hält; übergib extrapolate: "extend" (interpolate_with(.., Interpolation::extrapolate(Extrapolate::Extend)) in Rust) für einen dehnbaren Header, der beim Ziehen wächst.

#Pull-to-Refresh

refreshing und onRefresh (.refreshing(..) und .on_refresh(..)) fügen das Pull-to-Refresh-Steuerelement der Plattform hinzu. Das Steuerelement wird angezeigt, solange refreshing true ist; setze es in onRefresh und setze es zurück, wenn die Arbeit erledigt ist.

let refreshing = signal(false);
let reload = move || {
    refreshing.set(true);
    spawn(async move {
        if let Ok(response) = fetch("https://api.example.com/items").await {
            items.set(parse_items(&response.text()));
        }
        refreshing.set(false);
    });
};

scroll().flex_1().refreshing(refreshing).on_refresh(reload).child(content)

Unter iOS ist das Steuerelement UIRefreshControl; im Mac-Idiom von Mac Catalyst zeigt NSH einen eigenen Spinner mit demselben Verhalten. Listen akzeptieren dieselben Props. Auf dem Desktop und im Browser ist das Ziehen eine Geste des Touchscreens (oder eines macOS-Trackpads, das über den oberen Rand hinauszieht), da die Maus ohne Ziehen scrollt; biete dort einen anderen Weg zum Aktualisieren an, etwa eine Schaltfläche oder ein Tastaturkürzel.

#Ein eigener Indikator

Das Aussehen des Ziehens bestimmt die App. refreshIndicator={{ height }} besagt, dass die App einen eigenen Indikator zeichnet, height gibt seine Höhe in Punkten an: Der Spinner der Plattform wird ausgeblendet, ein Zug löst die Aktualisierung aus, sobald er height erreicht, und während der Aktualisierung bleiben height über dem Inhalt für den Indikator frei. pullValue speist einen animierten Wert damit, wie weit der Inhalt über den oberen Rand hinausgezogen ist: 0 in Ruhe, wachsend mit dem Zug, und height während der Aktualisierung. Zeichne den Indikator über den oberen Rand der Scroll View und interpoliere seine Styles aus dem Wert: Sie folgen dem Finger im UI-Thread, ohne JS pro Frame.

let pull = animated_value(0.0);

view()
    .flex_1()
    .child(
        scroll()
            .flex_1()
            .refreshing(refreshing)
            .on_refresh(reload)
            .pull_value(&pull)
            .refresh_indicator(Some(px(72.0)))
            .child(content),
    )
    .child(
        view()
            .pointer_events(PointerEvents::None)
            .absolute()
            .top_0()
            .left_0()
            .right_0()
            .h(px(72.0))
            .items_center()
            .justify_center()
            .bind_opacity(&pull.interpolate(&[0.0, 36.0], &[0.0, 1.0]))
            .bind_rotate(&pull.interpolate(&[36.0, 72.0], &[0.0, 180.0]))
            .child(arrow()),
    )

Halte height innerhalb der halben Viewport-Höhe: Über dem oberen Rand setzt der Inhalt dem Finger umso mehr Widerstand entgegen, je weiter er geht, und eine zu große Höhe wird nie erreicht. Unter iOS ersetzt ein eigener Indikator der App UIRefreshControl durch den Zug, den NSH selbst erkennt, wie im Mac-Idiom.

#Zoomen

Eine Scroll View (Listen behalten die Skalierung 1) zoomt, wenn ihre maximale Zoomstufe über der minimalen liegt: Pinchen skaliert den Inhalt, und im vergrößerten Zustand scrollt er in beide Richtungen.

Prop Rust Typ Standard Beschreibung
minimumZoomScale .zoom_range(min, max) number 1 Der kleinste Zoom.
maximumZoomScale .zoom_range(min, max) number 1 Der größte Zoom; liegt er über dem Minimum, zoomt Pinchen.
zoomScale .zoom(scale) number Zoomt auf diese Stufe, wenn sich der Prop ändert; der Zoom des Nutzers bleibt sonst erhalten.
bouncesZoom .bounces_zoom(..) boolean true Pinchen darf über die Grenzen hinausgehen und federt zurück.
zoomable .zoomable(..) boolean true Der Nutzer kann per Pinch zoomen.
centerContent .center_content(..) boolean false Inhalt, der kleiner als der Viewport ist (herausgezoomt), wird zentriert.

Das Scroll-Event meldet die Zoomstufe und die gezoomte Inhaltsgröße. Per Code zoomt zoom_to(scale, center, animated) / zoomTo({ scale, x, y, animated }) so, dass der Inhaltspunkt (in ungezoomten Punkten) in der Mitte des Viewports landet; Doppeltippen zum Zoomen ist ein Press-Handler, der es aufruft:

pub fn photo() -> impl IntoElement {
    let photo = element_ref();
    let scale = signal(1.0_f32);
    scroll()
        .flex_1()
        .bg(black())
        .zoom_range(1.0, 4.0)
        .center_content(true)
        .on_scroll(move |e: &ScrollEvent| scale.set(e.zoom_scale))
        .element_ref(&photo)
        .child(
            image("https://picsum.photos/id/1015/1200/800")
                .w(px(390.0))
                .h(px(260.0))
                .on_press(move || photo.zoom_to(if scale.get_untracked() > 1.5 { 1.0 } else { 3.0 }, None, true)),
        )
}

Text und Canvases innerhalb einer gezoomten Scroll View werden nach dem Zoomen in der neuen Auflösung neu gezeichnet, sodass sie unter iOS scharf bleiben. Unter Android zoomt NSH mit einer eigenen Scroll View, die um den Fokuspunkt des Pinch herum skaliert, mit beliebig vielen Fingern schwenkt, flingt und an den Rändern und Zoomgrenzen wie ein Gummiband nachgibt. Android beginnt einen Pinch, sobald die Finger etwa 27 mm auseinander sind, wie es der Gestendetektor des Systems tut.

#Scrollen per Code

Gib der Scroll View eine Ref (element_ref() und .element_ref(&scroller) in Rust, eine ref in TSX) und rufe ihre Methoden auf. Befehle laufen nach den übrigen Änderungen desselben Updates, sodass das Scrollen zu gerade hinzugefügtem Inhalt funktioniert.

Rust TSX Beschreibung
scroll_to(point, animated) scrollTo({ x?, y?, animated? }) Scrollt zu einem Offset. In TSX standardmäßig animiert.
zoom_to(scale, center, animated) zoomTo({ scale, x?, y?, animated? }) Zoombare Scroll Views: zoomt auf scale (an die Grenzen geklemmt), um den Inhaltspunkt oder die Mitte.
let scroller = element_ref();

scroll().flex_1().element_ref(&scroller).child(content)
view().on_press(move || scroller.scroll_to(point(px(0.0), px(0.0)), true))

Listen ergänzen scrollToIndex, scrollToOffset und scrollToEnd; siehe Listen.

#Scroll Views und die Tastatur

NSH hält ein fokussiertes Eingabefeld von selbst sichtbar: Wenn die Tastatur erscheint, erhält die Scroll View um das Eingabefeld einen unteren Inset in Höhe der Tastatur und scrollt das Eingabefeld in den sichtbaren Bereich. Das geschieht nativ, ohne Code in deiner App. Verwende env(keyboard-inset-height) als unteres Padding (Inhalte außerhalb von Scroll Views) für Inhalte außerhalb von Scroll Views, etwa eine am unteren Rand fixierte Nachrichtenleiste.

Zwei Props bestimmen, wie Scrollen und Taps mit der Tastatur zusammenspielen:

Prop Rust Typ Standard Beschreibung
keyboardDismissMode .keyboard_dismiss_mode(KeyboardDismissMode::OnDrag) "none" | "on-drag" | "interactive" "none" Ob das Ziehen des Inhalts die Tastatur ausblendet. "interactive" lässt die Tastatur unter iOS dem Finger nach unten folgen; unter Android verhält es sich wie "on-drag".
keyboardShouldPersistTaps .keyboard_taps(KeyboardTaps::Handled) "never" | "always" | "handled" "never" Was ein Tap bewirkt, während die Tastatur sichtbar ist.

keyboardShouldPersistTaps:

  • "never": Ein Tap außerhalb des fokussierten Eingabefelds blendet nur die Tastatur aus, und Schaltflächen erhalten den nächsten Tap. Der Nutzer tippt einmal zum Schließen, ein zweites Mal zum Drücken.
  • "handled": Ein Tap auf etwas, das ihn verarbeitet (eine Schaltfläche), geht durch und die Tastatur bleibt; ein Tap auf leere Fläche blendet sie aus. Meist die richtige Wahl für Formulare.
  • "always": Die Tastatur bleibt bei jedem Tap sichtbar.

Beides wird nativ entschieden, wenn der Tap oder Drag geschieht, ohne auf deinen Code zu warten. Um die Tastatur per Code auszublenden, rufe dismiss_keyboard() in Rust oder Keyboard.dismiss() aus nsh in TSX auf.

#Verschachtelte Scroll Views

Eine horizontale Scroll View in einer vertikalen funktioniert wie erwartet: Jede übernimmt die Drags entlang ihrer eigenen Achse. Zwei Scroll Views auf derselben Achse lassen sich ebenfalls verschachteln (die innere scrollt zuerst). Responder, die den Finger brauchen, etwa ein Zeichen-Canvas in einer Scroll View, halten ihn von den umgebenden Scroll Views fern: .keep_touches(true) in Rust, true, von onResponderGrant zurückgegeben, in TSX; siehe Touch und Gesten.

#Siehe auch

  • Scroll: die Element-Referenz mit allen Props und Events.
  • Listen: virtualisierte Listen mit denselben Scrolling-Props.
  • Animation: animierte Werte, interpolate und diffClamp für scroll-gekoppelte Effekte.
  • Formulare und die Tastatur: Eingabefelder, Fokus und die Tastatur.