Indicadores & listas¶
Esta categoria reúne dois grupos de widgets de feedback e de dados. Os
indicadores — ProgressBar e Spinner — são folhas não interativas
(sem eventos) que comunicam progresso ou atividade. As listas virtualizadas
— LazyColumn, LazyRow, LazyGrid, SectionList e o wrapper
RefreshControl — declaram um item_count mais um item_builder, e só
materializam a janela visível de itens na IR. 🚀
O que você aprende aqui
- Como um
ProgressBaralterna entre determinado e indeterminado, e o que oSpinnersempre é. - Como o
color_schemeescolhe a família de cor do acento em cada indicador. - Como uma lista virtualiza:
item_count+item_builder+ a janela de tamanhoDEFAULT_WINDOW_SIZE. - Por que o primeiro mount já vem com conteúdo, e como a aplicação desliza a janela num scroll.
- Como funcionam
on_end_reached/end_reached_threshold(paginação), o pull-to-refresh e as seções doSectionList.
Indicadores de progresso¶
Os indicadores são widgets folha não interativos: não têm handlers de evento, só carregam props que o renderizador pinta contra o tema ativo. Use-os para sinalizar que algo está acontecendo — uma barra para progresso mensurável, um spinner para atividade de duração desconhecida.
ProgressBar¶
Uma barra de progresso horizontal. Ela mostra ou uma fração determinada em
[0.0, 1.0], ou uma barra indeterminada (em loop) quando a duração é
desconhecida:
from tempest_core import ProgressBar
# Determinada: 42% concluído.
carregando = ProgressBar(value=0.42)
# Indeterminada: trabalho de duração desconhecida (value é ignorado).
processando = ProgressBar(indeterminate=True)
O acento (o trecho preenchido da trilha) é pintado pela família de papéis do
color_scheme:
Props¶
| Prop | Tipo | Padrão | O que faz |
|---|---|---|---|
value |
float |
0.0 |
A fração concluída em [0.0, 1.0] (ignorada quando indeterminate está setado). |
indeterminate |
bool |
False |
Quando True, renderiza uma barra em loop sem valor fixo (duração desconhecida). |
color_scheme |
str |
"primary" |
A família de papéis M3 com que o renderizador pinta o acento da barra. |
value é validado no range [0.0, 1.0]
O campo value tem ge=0.0 e le=1.0 — passar algo fora desse intervalo é
erro de validação Pydantic na construção, não um clamp silencioso. Quando
indeterminate=True, o value é simplesmente ignorado pelo renderizador.
Determinado quando você sabe a fração; indeterminado quando não sabe
Prefira value= sempre que puder medir o progresso (download com tamanho
conhecido, passo N de M). Reserve indeterminate=True para espera opaca — é o
que evita uma barra que fica "presa" em 10% porque você não sabe o total.
Spinner¶
Um indicador de atividade circular — sempre indeterminado. Não tem value;
existe só para dizer "algo está rodando". O size é o diâmetro em pixels
lógicos, ou None para o padrão do renderizador:
from tempest_core import Spinner
# Diâmetro padrão do renderizador.
ocupado = Spinner()
# Spinner maior, na cor de erro.
recarregando = Spinner(size=48.0, color_scheme="error")
Props¶
| Prop | Tipo | Padrão | O que faz |
|---|---|---|---|
size |
float \| None |
None |
O diâmetro do indicador em pixels lógicos, ou None para o padrão do renderizador. |
color_scheme |
str |
"primary" |
A família de papéis M3 com que o renderizador pinta o acento do spinner. |
O Spinner não tem value nem indeterminate
Diferente do ProgressBar, o spinner é circular e sempre em loop — não
existe estado "determinado" para ele. Se você precisa mostrar uma fração,
use o ProgressBar.
Listas virtualizadas¶
As listas são as primitivas de container virtual do framework. Em vez de
declarar uma lista materializada de filhos, elas declaram um item_count mais um
item_builder(index) -> Widget. Só a janela visível de itens chega à IR: o
renderizador reporta o offset de scroll via ScrollEvent, a aplicação
recalcula a janela [start, end) e reconstrói, e o diff por chave (chave do item
= str(index)) transforma um deslize de janela numa sequência mínima de
remove/reorder/insert.
O item_builder é um callable Python que materializa o widget na mesma
thread do build — ele nunca cruza a fronteira nativa. O serializador o descarta;
o device recebe item_count mais os filhos da janela já materializados e renderiza
nativamente (Compose LazyColumn).
DEFAULT_WINDOW_SIZE¶
A constante que define quantos itens entram na janela visível inicial quando
uma lista não declara um window explícito:
DEFAULT_WINDOW_SIZE vale 20. É o valor padrão do campo window_size de
toda lista (e de cada SectionHeader). Ele mantém o primeiro mount barato — o
device renderiza esses 20 itens, não os item_count totais — enquanto ainda
mostra conteúdo imediatamente.
A janela de virtualização, em uma frase
A janela é window quando setada (a aplicação a desliza em resposta a um
ScrollEvent via App.slide_window), senão o padrão inicial
[0, min(window_size, item_count)]. É isso que faz o primeiro mount ser
não-vazio: build materializa window_size itens imediatamente, sem esperar
um evento de scroll. A virtualização é preservada — só a janela é construída,
nunca todos os item_count itens.
LazyColumn¶
Uma lista virtualizada vertical (Compose LazyColumn). Declara um item_count e
um item_builder em vez de filhos materializados; só a janela visível é
construída na IR:
from tempest_core import LazyColumn, Text
def build_item(index: int) -> Text:
return Text(content=f"Item {index}")
lista = LazyColumn(item_count=10_000, item_builder=build_item)
Esse LazyColumn com 10 mil itens materializa só os primeiros 20
(DEFAULT_WINDOW_SIZE) no primeiro mount. Ele emite ScrollEvent ao rolar,
RefreshEvent no pull-to-refresh e EndReachedEvent ao passar de
end_reached_threshold:
from tempest_core import LazyColumn, Text
async def carregar_mais(event) -> None: # (1)!
...
lista = LazyColumn(
item_count=10_000,
item_builder=lambda i: Text(content=f"Item {i}"),
end_reached_threshold=0.8,
on_end_reached=carregar_mais,
)
- O handler pode ser síncrono ou
async— o runtime agenda os awaitables no event loop. Ele recebe umEndReachedEvent(veja Referência da API).
Props¶
| Prop | Tipo | Padrão | O que faz |
|---|---|---|---|
item_count |
int |
(obrigatório) | O número total de itens na lista. |
item_builder |
ItemBuilder |
(obrigatório) | Factory que constrói o item no índice dado. Vive só no lado Python; nunca serializado. |
window_size |
int |
DEFAULT_WINDOW_SIZE (20) |
Quantos itens entram na janela inicial quando window está sem valor. |
window |
tuple[int, int] \| None |
None |
A janela visível [start, end) atual, ou None para o padrão inicial. A aplicação a desliza num scroll. |
end_reached_threshold |
float |
0.8 |
A fração 0..1 do scroll total em que on_end_reached dispara. |
refreshing |
bool |
False |
Se o spinner de pull-to-refresh está ativo. |
on_scroll |
ScrollHandler \| None |
None |
Handler opcional para eventos de scroll. |
on_refresh |
RefreshHandler \| None |
None |
Handler opcional para pull-to-refresh. |
on_end_reached |
EndReachedHandler \| None |
None |
Handler opcional disparado perto do fim da lista. |
Não materialize a lista inteira você mesmo
O ponto da virtualização é item_builder construir um item por índice, sob
demanda. Passar item_count gigantesco é ótimo — só a janela é construída. Mas
montar toda a lista de widgets antes e devolvê-los pelo builder joga fora a
virtualização e incha a IR.
A aplicação desliza a window, o widget não
O LazyColumn não se move sozinho. Quando o renderizador reporta um
ScrollEvent, a aplicação chama App.slide_window para calcular a nova
[start, end) e reconstruir com a window atualizada. O widget só descreve
qual janela materializar; o movimento vem de fora.
LazyRow¶
O análogo horizontal do LazyColumn (Compose LazyRow): contrato idêntico,
itens dispostos e rolados da esquerda para a direita.
from tempest_core import LazyRow, Text
carrossel = LazyRow(
item_count=500,
item_builder=lambda i: Text(content=f"Slide {i}"),
)
Props¶
| Prop | Tipo | Padrão | O que faz |
|---|---|---|---|
item_count |
int |
(obrigatório) | O número total de itens na lista. |
item_builder |
ItemBuilder |
(obrigatório) | Factory que constrói o item no índice dado. Vive só no lado Python; nunca serializado. |
window_size |
int |
DEFAULT_WINDOW_SIZE (20) |
Quantos itens entram na janela inicial quando window está sem valor. |
window |
tuple[int, int] \| None |
None |
A janela visível [start, end) atual, ou None para o padrão inicial. |
end_reached_threshold |
float |
0.8 |
A fração 0..1 do scroll total em que on_end_reached dispara. |
refreshing |
bool |
False |
Se o spinner de pull-to-refresh está ativo. |
on_scroll |
ScrollHandler \| None |
None |
Handler opcional para eventos de scroll. |
on_refresh |
RefreshHandler \| None |
None |
Handler opcional para pull-to-refresh. |
on_end_reached |
EndReachedHandler \| None |
None |
Handler opcional disparado perto do fim da lista. |
Mesma API, eixo diferente
Se você já sabe usar LazyColumn, já sabe usar LazyRow — os campos e
eventos são exatamente os mesmos. A única diferença é a orientação do scroll.
LazyGrid¶
Uma grade virtualizada (Compose LazyVerticalGrid). Dispõe os itens
virtualizados num número fixo de columns, rolando verticalmente. Não tem
pull-to-refresh — envolva com um RefreshControl se precisar:
from tempest_core import LazyGrid, Text
galeria = LazyGrid(
item_count=1_000,
item_builder=lambda i: Text(content=f"Foto {i}"),
columns=3,
)
Props¶
| Prop | Tipo | Padrão | O que faz |
|---|---|---|---|
item_count |
int |
(obrigatório) | O número total de itens na grade. |
item_builder |
ItemBuilder |
(obrigatório) | Factory que constrói o item no índice dado. Vive só no lado Python; nunca serializado. |
columns |
int |
2 |
O número de colunas da grade. |
window_size |
int |
DEFAULT_WINDOW_SIZE (20) |
Quantos itens entram na janela inicial quando window está sem valor. |
window |
tuple[int, int] \| None |
None |
A janela visível [start, end) atual, ou None para o padrão inicial. |
end_reached_threshold |
float |
0.8 |
A fração 0..1 do scroll total em que on_end_reached dispara. |
on_scroll |
ScrollHandler \| None |
None |
Handler opcional para eventos de scroll. |
on_end_reached |
EndReachedHandler \| None |
None |
Handler opcional disparado perto do fim da grade. |
A grade não tem on_refresh nem refreshing
Diferente de LazyColumn / LazyRow, o LazyGrid não expõe pull-to-refresh.
Para o gesto de puxar-para-atualizar numa grade, envolva-a num RefreshControl
(veja abaixo).
SectionHeader¶
Uma seção de um SectionList: um cabeçalho mais itens virtualizados. Não é um
widget — é um value object congelado (frozen=True) que descreve como
construir o cabeçalho fixo de uma seção e seus itens. Cada seção tem sua própria
janela de virtualização:
from tempest_core import SectionHeader, Text
secao_a = SectionHeader(
title="A",
item_count=200,
item_builder=lambda i: Text(content=f"A-{i}"),
header_builder=lambda: Text(content="Seção A"),
)
Props¶
| Prop | Tipo | Padrão | O que faz |
|---|---|---|---|
title |
str |
(obrigatório) | Um rótulo estável para a seção (usado como chave e no cabeçalho). |
item_count |
int |
(obrigatório) | O número de itens nesta seção. |
item_builder |
ItemBuilder |
(obrigatório) | Factory que constrói o item num índice local à seção. |
header_builder |
HeaderBuilder |
(obrigatório) | Factory que constrói o cabeçalho fixo desta seção. |
window_size |
int |
DEFAULT_WINDOW_SIZE (20) |
Quantos itens entram na janela inicial desta seção quando window está sem valor. |
window |
tuple[int, int] \| None |
None |
A janela visível [start, end) atual desta seção, ou None para o padrão inicial. |
A seção é congelada; a aplicação a substitui via model_copy
Como SectionHeader é frozen=True, deslizar sua janela não muta a seção — a
aplicação substitui a seção (frozen) por uma cópia com a nova window via
model_copy. Cada item materializado é keyed "sec:<title>:<index>" e o
cabeçalho "sec:<title>:header", então toda child do SectionList tem chave
globalmente única para o diff por chave.
SectionList¶
Uma lista virtualizada seccionada com cabeçalhos fixos (sticky). Cada
SectionHeader declara seu cabeçalho mais seus próprios itens virtualizados. O
renderizador fixa os cabeçalhos (Compose stickyHeader; o simulador Qt fixa um
label acima da área de scroll):
from tempest_core import SectionHeader, SectionList, Text
def make_section(letra: str) -> SectionHeader:
return SectionHeader(
title=letra,
item_count=100,
item_builder=lambda i: Text(content=f"{letra}-{i}"),
header_builder=lambda: Text(content=f"Seção {letra}"),
)
contatos = SectionList(
sections=[make_section("A"), make_section("B"), make_section("C")],
)
Props¶
| Prop | Tipo | Padrão | O que faz |
|---|---|---|---|
sections |
list[SectionHeader] |
[] |
As seções ordenadas a renderizar. |
end_reached_threshold |
float |
0.8 |
A fração 0..1 do scroll total em que on_end_reached dispara. |
on_scroll |
ScrollHandler \| None |
None |
Handler opcional para eventos de scroll. |
on_end_reached |
EndReachedHandler \| None |
None |
Handler opcional disparado perto do fim da lista. |
Cada seção virtualiza a sua própria janela
Um SectionList não tem uma janela única — cada SectionHeader carrega seu
próprio window_size / window. Ao construir, o SectionList achata, em
ordem, o cabeçalho mais os itens da janela de cada seção, tudo keyed para o diff
por chave do reconciliador.
sections cai para [], nunca None
O campo sections usa default_factory para uma lista vazia — um SectionList
sem seções é um estado válido (lista vazia), não um erro.
RefreshControl¶
Um wrapper de pull-to-refresh autônomo (Compose PullToRefreshBox),
desacoplado de uma lista virtualizada. Envolva-o em torno de qualquer conteúdo
rolável — inclusive um LazyGrid, que não tem refresh próprio. O conteúdo é
fornecido pelo renderizador; o widget carrega só o contrato de refresh:
from tempest_core import RefreshControl
async def recarregar(event) -> None: ...
controle = RefreshControl(refreshing=False, on_refresh=recarregar)
Props¶
| Prop | Tipo | Padrão | O que faz |
|---|---|---|---|
refreshing |
bool |
False |
Se o spinner de pull-to-refresh está ativo. |
on_refresh |
RefreshHandler \| None |
None |
Handler opcional para pull-to-refresh. |
Você controla o refreshing
O spinner não some sozinho: no seu on_refresh, dispare o recarregamento e,
quando terminar, reconstrua com refreshing=False. Setar refreshing=True
enquanto os dados chegam mantém o indicador girando; voltar para False o
esconde.
Recapitulando¶
- Indicadores são folhas não interativas:
ProgressBaralterna determinado (valueem[0.0, 1.0]) e indeterminado;Spinneré circular e sempre indeterminado. Ocolor_schemepinta o acento. - Virtualização: as listas declaram
item_count+item_buildere só materializam a janela visível; oitem_buildernunca cruza a fronteira nativa. DEFAULT_WINDOW_SIZEé20— owindow_sizepadrão que dá conteúdo ao primeiro mount sem construir todos ositem_countitens.- A aplicação desliza a
windownumScrollEvent(viaApp.slide_window); o widget só descreve qual janela materializar. LazyColumn/LazyRowtêm pull-to-refresh (refreshing/on_refresh);LazyGridnão — envolva numRefreshControl.- Paginação:
on_end_reacheddispara emend_reached_threshold(padrão0.8) do scroll total. SectionListachataSectionHeaders congelados, cada um com sua própria janela e cabeçalho fixo; a aplicação substitui a seção viamodel_copypara deslizar.