Esta página foi traduzida automaticamente e pode conter erros. Ler o original em inglês

Elementos

Scroll

Scroll é uma scroll view nativa (UIScrollView no iOS e no Mac Catalyst, um ScrollView no Android). Ela dispõe todos os seus filhos como conteúdo e os rola de forma nativa: rolagem, arremesso (fling), efeito elástico e encaixe (snapping) rodam na plataforma, na taxa de atualização da tela. Em Rust, crie uma com scroll(); em TSX, com <Scroll> de nsh.

el-scroll.tsx
el-scroll.tsxLive
import { mount, Scroll, type ScrollRef, signal, Text, View } from "nsh";
import { button, buttonText, desktop, Inspector, Panel, size, ui, wide } from "./palette";

// The page colors, read when they render: they follow the theme.
const colors = (): string[] => [ui().accent, ui().info, ui().violet, ui().down, ui().warn, ui().up];

function App() {
  const page = signal(0);
  // A page is as wide as the scroll view: 390 on a phone, wider on a desktop.
  const width = signal(390);
  const y = signal(0);
  let scroller!: ScrollRef;
  const rows = Array.from({ length: 40 }, (_, i) => i + 1);

  /** Pages as wide as the scroll view; `inset`: each page's margin. */
  const pager = (inset: number, style: object) => (
    <Scroll
      horizontal
      pagingEnabled
      scrollbar={false}
      style={style}
      onLayout={({ bounds }) => width.set(bounds.size.width)}
      onMomentumEnd={({ offset }) => page.set(Math.round(offset.x / width()))}
    >
      <Each of={colors()}>
        {(color, index) => (
          <View style={{ width: width(), padding: inset }}>
            <View style={{ flex: 1, borderRadius: 16, backgroundColor: color(), alignItems: "center", justifyContent: "center" }}>
              <Text style={{ color: ui().page, fontSize: 32, fontWeight: "700" }}>{index() + 1}</Text>
            </View>
          </View>
        )}
      </Each>
    </Scroll>
  );
  const toTop = (compact: boolean) => (
    <View
      onPress={() => scroller.scrollTo({ y: 0, animated: true })}
      {...button("secondary", { size: compact ? "md" : "sm" })}
    >
      <Text style={buttonText()}>scrollTo top</Text>
    </View>
  );

  return (
    <View style={{ flex: 1, backgroundColor: ui().page, paddingTop: 24, gap: 12 }}>
      {() =>
        wide() ? (
          <View style={{ flex: 1, paddingHorizontal: 20, paddingBottom: 20, gap: 14 }}>
            <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>scroll</Text>
            <Text style={{ color: ui().muted }}>
              {desktop ? "Shift+wheel or swipe the trackpad across the pages; the wheel scrolls the rows" : "Swipe the pages and the rows"}
            </Text>
            <View style={{ flex: 1, flexDirection: "row", gap: 16 }}>
              <View style={{ flex: 1, minWidth: 0, gap: 12 }}>
                {pager(0, { height: 150, flexGrow: 0, borderRadius: 16 })}
                {/* The scroll view fills its region, so its scrollbar sits at the region's edge. */}
                <View style={{ flex: 1, borderRadius: 16, borderWidth: 1, borderColor: ui().line, overflow: "hidden" }}>
                  <Scroll ref={scroller} style={{ flex: 1 }} scrollEventThrottle={16} onScroll={(e) => y.set(e.offset.y)}>
                    <View style={{ padding: 12, gap: 6 }}>
                      <Each of={rows}>
                        {(n) => (
                          <View style={{ ...ui().row, borderRadius: 8, height: size.row, paddingHorizontal: 12, justifyContent: "center" }}>
                            <Text style={{ color: ui().text, fontSize: size.label }}>Row {n()}</Text>
                          </View>
                        )}
                      </Each>
                    </View>
                  </Scroll>
                </View>
              </View>
              <Inspector>
                <Panel title="Horizontal, pagingEnabled · onMomentumEnd">
                  <Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>
                    page {page() + 1} of {colors().length}
                  </Text>
                </Panel>
                <Panel title="Vertical · onScroll">
                  <Text style={{ fontSize: 13, color: ui().text, fontVariant: ["tabular-nums"] }}>y = {Math.round(y())}</Text>
                </Panel>
                {toTop(true)}
              </Inspector>
            </View>
          </View>
        ) : (
          <>
            <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text, paddingHorizontal: 20 }}>scroll</Text>

            <Text style={{ color: ui().muted, paddingHorizontal: 20 }}>
              Horizontal, pagingEnabled · page {page() + 1} · {desktop ? "Shift+wheel or swipe the trackpad" : "swipe"}
            </Text>
            {pager(12, { height: 140, flexGrow: 0 })}

            <View style={{ width: "100%", maxWidth: 640, alignSelf: "center", flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingHorizontal: 20 }}>
              <Text style={{ color: ui().muted }}>Vertical · y = {Math.round(y())}</Text>
              {toTop(false)}
            </View>
            <Scroll ref={scroller} style={{ flex: 1 }} scrollEventThrottle={16} onScroll={(e) => y.set(e.offset.y)}>
              <View style={{ width: "100%", maxWidth: 640, alignSelf: "center", paddingHorizontal: 20, paddingBottom: 20, gap: 8 }}>
                <Each of={rows}>
                  {(n) => (
                    <View style={{ ...ui().row, borderRadius: 12, padding: 14 }}>
                      <Text style={{ color: ui().text }}>Row {n()}</Text>
                    </View>
                  )}
                </Each>
              </View>
            </Scroll>
          </>
        )
      }
    </View>
  );
}

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

scroll().flex_1().child(
    view().p_4().gap_2().child(each(
        rows,
        |row: &Row| row.id,
        |row, _index| text(move || row.get().title),
    )),
)

Uma scroll view cria todos os filhos, o que serve bem para páginas de tamanho moderado. Para dados longos ou ilimitados, use uma lista: ela cria as linhas próximas da área visível e as recicla.

Uma scroll view precisa de um tamanho limitado ao longo do seu eixo: dê a ela flex: 1 (.flex_1()) ou uma altura dentro de um contêiner que tenha uma. O conteúdo é disposto com a largura da scroll view (altura, quando horizontal) e tão alto (largo) quanto precisar.

#Props

Além das props comuns (com style tipado como ViewStyle). Em Rust, as props de rolagem são métodos da trait Scrollable, que as listas compartilham.

#Rolagem

Prop Rust Tipo Padrão Descrição
horizontal .horizontal() boolean false Rola na horizontal.
scrollable .scrollable(..) boolean true O usuário pode rolá-la.
scrollbar .scrollbar(..) boolean | ScrollbarTheme true O indicador de rolagem aparece: o da plataforma ou a barra integrada do app, quando ele define um tema; um objeto é um tema próprio da view. No desktop e em navegadores desktop, uma view horizontal o mantém: é a barra de rolagem do mouse.
bounces .bounces(..) boolean true Rolagem além das bordas (overscroll).
pagingEnabled .paging(..) boolean false Para em múltiplos da área visível.
snapToInterval .snap_interval(px(..)) number 0 Para em múltiplos deste comprimento (0: desligado).
snapToOffsets .snap_offsets(..) number[] Para nestes deslocamentos.
snapToAlignment .snap_alignment(SnapAlignment::Center) "start" | "center" | "end" "start" Qual ponto de um item se alinha com a área visível com snapToInterval.
disableIntervalMomentum .disable_interval_momentum(..) boolean false Um arremesso (fling) avança no máximo um ponto de encaixe.
decelerationRate .deceleration(rate) "normal" | "fast" | number plataforma Com que rapidez um arremesso desacelera: a velocidade mantida por milissegundo (normal 0,998, fast 0,99).
contentInset .content_inset(EdgeInsets { .. }) Partial<Edges> Espaço ao redor do conteúdo.
contentOffset .content_offset(point(..)) { x?, y? } Onde ela começa.
contentStyle os próprios estilos da view de conteúdo ViewStyle O estilo de uma view ao redor dos filhos que rola junto com eles; flexGrow: 1 faz com que ela tenha pelo menos a altura da área visível (um estado vazio centralizado).
contentClass os próprios estilos da view de conteúdo string O mesmo que classes, por exemplo "grow justify-center p-4".
keyboardDismissMode .keyboard_dismiss_mode(KeyboardDismissMode::OnDrag) "none" | "on-drag" | "interactive" "none" Se arrastar dispensa o teclado.
keyboardShouldPersistTaps .keyboard_taps(KeyboardTaps::Handled) "never" | "always" | "handled" | boolean "never" Se um toque dispensa o teclado: never (o toque apenas o dispensa), always (ele permanece aberto), handled (ele permanece aberto quando algo trata o toque).
scrollValue .scroll_value(&value) AnimatedValue Um valor animado alimentado com o deslocamento da rolagem (eixo principal), para animações vinculadas à rolagem.
scrollEventThrottle .event_throttle(ms) number Mínimo de milissegundos entre eventos onScroll (no máximo um por quadro de qualquer forma).

#Puxar para atualizar

Prop Rust Tipo Descrição
refreshing .refreshing(..) boolean O indicador de atualização aparece.
onRefresh .on_refresh(..) () => void O usuário puxou para atualizar: comece a carregar e defina refreshing até terminar.
refreshIndicator .refresh_indicator(Some(height)) { height: number } O app desenha seu próprio indicador, com height pontos de altura: o spinner da plataforma fica oculto, um puxão atualiza quando chega a height, e, durante a atualização, height é mantido acima do conteúdo. Uma altura igual ou menor que 0 significa nenhum.
pullValue .pull_value(&value) AnimatedValue Um valor animado alimentado com o quanto a view é puxada além do topo (0 em repouso, crescendo com o puxão; o espaço mantido acima do conteúdo durante a atualização: height com refreshIndicator), para o indicador do app.
let refreshing = signal(false);

scroll()
    .flex_1()
    .refreshing(refreshing)
    .on_refresh(move || {
        refreshing.set(true);
        spawn(async move {
            load().await;
            refreshing.set(false);
        });
    })
    .child(content())

#Zoom

Definir maximumZoomScale acima de minimumZoomScale (.zoom_range(min, max) em Rust) torna o conteúdo ampliável com um gesto de pinça; ampliado, ele rola nos dois sentidos. As listas mantêm sua escala.

Prop Rust Tipo Padrão Descrição
minimumZoomScale .zoom_range(min, max) number 1 O menor zoom.
maximumZoomScale .zoom_range(min, max) number 1 O maior zoom.
zoomScale .zoom(..) number Aplica zoom a esta escala quando ela muda; caso contrário, o zoom do usuário é mantido.
bouncesZoom .bounces_zoom(..) boolean true A pinça pode ultrapassar os limites e volta com efeito elástico.
zoomable .zoomable(..) boolean true O usuário pode usar a pinça para dar zoom.
centerContent .center_content(..) boolean false Conteúdo menor que a área visível (reduzido) é centralizado.
let photo = element_ref();

scroll()
    .element_ref(&photo)
    .zoom_range(1.0, 4.0)
    .center_content(true)
    .flex_1()
    .bg(black())
    .child(
        image(PICTURE)
            .w(px(390.0))
            .h(px(520.0))
            .on_press(move || photo.zoom_to(1.0, None, true)),
    )

#Eventos

Evento Rust Payload Quando
onScroll .on_scroll ScrollEvent O deslocamento mudou (no máximo uma vez por quadro e não mais que o intervalo de scrollEventThrottle).
onScrollDragStart / onScrollDragEnd .on_scroll_drag_start / .on_scroll_drag_end { offset } (Point<Points>) O usuário começou / parou de arrastar.
onMomentumStart / onMomentumEnd .on_momentum_start / .on_momentum_end { offset } Um deslizamento após o arrasto começou / parou. Um deslizamento interrompido por um novo arrasto ainda termina com onMomentumEnd.
onScrollEnd .on_scroll_end { offset } A rolagem chegou ao repouso.
onRefresh .on_refresh nenhum Puxado para atualizar.

ScrollEvent é { offset: Point; contentSize: Size; size: Size; zoomScale } (em Rust offset, content_size, size, zoom_scale): o deslocamento, o tamanho do conteúdo (com zoom), o tamanho da área visível e a escala de zoom (1, a menos que a view tenha zoom).

#Métodos de ref

Método Rust Descrição
scrollTo({ x?, y?, animated? }) scroll_to(point(x, y), animated) Rola até um deslocamento.
zoomTo({ scale, x?, y?, animated? }) zoom_to(scale, Some(point(x, y)), animated) Aplica zoom a scale (limitado aos limites) com o ponto do conteúdo (x, y, pontos sem zoom) no meio da área visível, ou ao redor do meio. Por exemplo, um toque duplo que amplia o ponto tocado.
let scroller = element_ref();
scroll().element_ref(&scroller).child(content());

scroller.scroll_to(point(px(0.0), px(0.0)), true);

#Teclado

Campos de entrada dentro de uma scroll view são mantidos fora da área do teclado de forma nativa: a scroll view recebe um inset inferior e rola até o campo em foco por conta própria. Para conteúdo fora de scroll views, use env(keyboard-inset-height) em um padding ou margin inferior. Veja formulários e o teclado.

#Da direita para a esquerda

Scroll views horizontais começam na extremidade direita em layouts da direita para a esquerda e mantêm a distância dela quando o conteúdo muda; seus filhos são espelhados.

#Notas por plataforma

  • Android: o puxar para atualizar é desenhado pelo NSH (o overscroll continua nativo), contentInset é aplicado como padding, e decelerationRate molda os deslizamentos de encaixe.
  • O Android aplica zoom com a scroll view própria do NSH, que escala o conteúdo a partir do canto superior esquerdo e rola nos dois sentidos. As pinças começam depois do vão mínimo do Android entre os dedos (cerca de 27 mm).
  • No Mac Catalyst (idioma Mac), o NSH detecta o puxão e mostra seu spinner.
  • O iOS redesenha textos e canvases na resolução ampliada quando um zoom termina.