Em Flet, tanto ListView quanto Column organizam itens na vertical. A diferença está na intenção e no comportamento: ListView é pensado para listas longas, dinâmicas e roláveis (ex.: tarefas, mensagens), enquanto Column é ideal para poucos elementos estáticos (ex.: um formulário curto).
| Recurso | ListView | Column |
|---|---|---|
| Rolagem | Nativa por padrão (lista já é rolável) | Opcional (scroll=...) |
| Uso típico | Muitos itens, conteúdo que cresce em tempo real | Layout de poucas seções estáticas |
| Propriedades úteis | expand, spacing, padding, auto_scroll
|
spacing, horizontal_alignment, scroll |
| Performance | Mais apropriado para muitas linhas e adição/remoção frequentes | Com scroll funciona, mas pode exigir mais cuidado com muitos itens |
| Casos de uso | To-do, feed, chat, listagens CRUD | Formulários, painéis, blocos de conteúdo |
ListViewauto_scroll=True para “grudar” no fim (padrão chat/log).Column resolve melhorpage.update() muitas vezes: acumule mudanças (adicionar vários
itens) e atualize uma única vez.expand=True funcionar, o pai precisa permitir
expansão (ex.: um Column pai ocupando área disponível).import flet as ft
def main(page: ft.Page):
page.title = "Semana 5 - ListView básico"
lv = ft.ListView(expand=True, spacing=8, padding=12, auto_scroll=False)
for i in range(1, 21):
lv.controls.append(
ft.Container(
ft.Text(f"Item {i}"),
padding=10, bgcolor="#FAFAFA",
border=ft.border.all(1, "#E5E7EB"), border_radius=8
)
)
page.add(lv)
ft.app(target=main)
Notas: spacing controla o espaço entre itens; padding é o respiro
interno do ListView;
cada item aqui é um Container estilizado como “card”.
auto_scroll=Trueimport flet as ft
def main(page: ft.Page):
page.title = "ListView - chat/log"
entrada = ft.TextField(label="Mensagem", expand=True)
lista = ft.ListView(expand=True, spacing=6, padding=12, auto_scroll=True)
def enviar(e):
if not entrada.value.strip():
entrada.error_text = "Digite algo..."
page.update(); return
# adiciona mensagem ao final e "cola" a rolagem
lista.controls.append(ft.Text(entrada.value.strip()))
entrada.value, entrada.error_text = "", None
page.update()
page.add(lista, ft.Row([entrada, ft.FilledButton("Enviar", on_click=enviar)]))
ft.app(target=main)
Por quê ListView aqui? O conteúdo cresce no fim da lista; auto_scroll mantém a
visualização no último item,
replicando o comportamento de chats e logs em tempo real.
Column com rolagem bastaimport flet as ft
def main(page: ft.Page):
page.title = "Column com rolagem"
col = ft.Column(
controls=[
ft.Text("Seção 1", weight=ft.FontWeight.BOLD),
ft.Text("Conteúdo curto..."),
ft.Divider(),
ft.Text("Seção 2", weight=ft.FontWeight.BOLD),
ft.Text("Mais conteúdo curto..."),
ft.Divider(),
ft.Text("Seção 3", weight=ft.FontWeight.BOLD),
ft.Text("Mais conteúdo curto..."),
],
scroll=ft.ScrollMode.AUTO, # rola apenas se necessário
spacing=10
)
page.add(col)
ft.app(target=main)
Por quê Column aqui? São poucos blocos, praticamente estáticos; a rolagem é só um “backup” se a janela ficar pequena.
🔗 Documentação: ListView · Column · Container · Text · Border · Scroll / ScrollMode
Vamos criar uma lista dinâmica onde o usuário digita um texto e adiciona um item ao
ListView. Isso cobre as operações de Criação e Leitura do
CRUD.
Abaixo estão boas práticas de UX/estado para evitar problemas comuns.
on_submit).auto_scroll=True se quiser “colar” no último
item.page.update() apenas no final.
import flet as ft
def main(page: ft.Page):
page.title = "Semana 5 - Adicionar itens"
# Campo de entrada e ListView (auto_scroll = True para colar no final)
entrada = ft.TextField(
label="Novo item",
hint_text="Digite e clique em Adicionar",
expand=True,
prefix_icon=ft.icons.NOTE_ADD,
autofocus=True
)
lista = ft.ListView(expand=True, spacing=8, padding=12, auto_scroll=True)
# Botão começa desabilitado; será habilitado ao digitar algo
btn_add = ft.FilledButton(text="Adicionar", disabled=True)
# Conjunto opcional para não permitir textos repetidos (case-insensitive)
vistos = set()
def validar(e=None):
texto = (entrada.value or "").strip()
entrada.error_text = None
btn_add.disabled = (texto == "")
page.update()
entrada.on_change = validar # valida a cada digitação
def adicionar(e):
texto = (entrada.value or "").strip()
if not texto:
entrada.error_text = "Digite algo para adicionar à lista."
page.update()
return
# Checagem opcional: duplicados
chave = texto.lower()
if chave in vistos:
entrada.error_text = "Esse item já foi adicionado."
page.update()
return
# Cria um "card" simples para o item
item = ft.Container(
content=ft.Row(
[ft.Icon(ft.icons.CIRCLE_OUTLINED, size=18), ft.Text(texto, expand=True)]
),
padding=10, bgcolor="#FAFAFA",
border=ft.border.all(1, "#E5E7EB"), border_radius=8
)
lista.controls.append(item)
vistos.add(chave)
# Limpa o input e volta a desabilitar o botão
entrada.value = ""
btn_add.disabled = True
entrada.error_text = None
# Uma única atualização ao final
page.update()
# Clique no botão adiciona
btn_add.on_click = adicionar
page.add(
ft.Row([entrada, btn_add]),
lista
)
ft.app(target=main)
import flet as ft
def main(page: ft.Page):
page.title = "Adicionar com Enter"
lista = ft.ListView(expand=True, spacing=6, padding=12, auto_scroll=True)
entrada = ft.TextField(
label="Novo item",
hint_text="Digite e pressione Enter",
expand=True,
prefix_icon=ft.icons.NOTE_ADD
)
def adicionar(e=None):
texto = (entrada.value or "").strip()
if not texto:
entrada.error_text = "Campo vazio."
page.update(); return
lista.controls.append(
ft.Container(
ft.Text(texto), padding=10, bgcolor="#FAFAFA",
border=ft.border.all(1, "#E5E7EB"), border_radius=8
)
)
entrada.value, entrada.error_text = "", None
page.update()
entrada.on_submit = adicionar # Enter aciona a mesma função
page.add(lista, ft.Row([entrada, ft.FilledButton("Adicionar", on_click=adicionar)]))
ft.app(target=main)
import flet as ft
def main(page: ft.Page):
page.title = "Inserção em lote"
lista = ft.ListView(expand=True, spacing=6, padding=12)
def adicionar_muitos(e):
# adiciona 100 itens e atualiza somente no final
base = len(lista.controls) + 1
for i in range(base, base + 100):
lista.controls.append(
ft.Container(ft.Text(f"Item {i}"), padding=10, bgcolor="#FAFAFA",
border=ft.border.all(1, "#E5E7EB"), border_radius=8)
)
page.update()
page.add(
ft.Row([ft.FilledButton("Adicionar 100 itens", on_click=adicionar_muitos)]),
lista
)
ft.app(target=main)
🔗 Documentação: TextField · ListView · Row · FilledButton · Icon · Container · SnackBar
Agora vamos completar o nosso CRUD! Além de adicionar e listar, o usuário poderá editar e remover itens da lista.
Para editar, vamos abrir uma AlertDialog com um campo de texto preenchido com o valor atual.
Para excluir, basta clicar no ícone da lixeira. Também adicionamos a opção de riscar itens com
Checkbox.
import flet as ft
def main(page: ft.Page):
page.title = "Semana 5 - Editar/Remover itens"
entrada = ft.TextField(label="Novo item", expand=True)
lista = ft.ListView(expand=True, spacing=8, padding=12)
editor_input = ft.TextField(label="Editar item")
editor_dialog = ft.AlertDialog(
modal=True,
title=ft.Text("Editar item"),
content=editor_input,
actions=[],
actions_alignment=ft.MainAxisAlignment.END
)
current_label: ft.Text | None = None
def abrir_editor(label: ft.Text):
def handler(e):
nonlocal current_label
current_label = label
editor_input.value = label.value
page.dialog = editor_dialog
editor_dialog.actions = [
ft.TextButton("Cancelar", on_click=lambda e: fechar_editor(False)),
ft.ElevatedButton("Salvar", on_click=lambda e: fechar_editor(True)),
]
editor_dialog.open = True
page.update()
return handler
def fechar_editor(confirmar: bool):
nonlocal current_label
if confirmar and current_label and editor_input.value.strip():
current_label.value = editor_input.value.strip()
editor_dialog.open = False
page.update()
def make_item(texto: str) -> ft.Control:
label = ft.Text(texto, expand=True)
check = ft.Checkbox(value=False)
def toggle(e):
label.color = "grey" if check.value else None
label.decoration = ft.TextDecoration.LINE_THROUGH if check.value else None
page.update()
check.on_change = toggle
def remover(e):
lista.controls.remove(card)
page.update()
btn_edit = ft.IconButton(icon=ft.icons.EDIT, tooltip="Editar", on_click=abrir_editor(label))
btn_del = ft.IconButton(icon=ft.icons.DELETE_OUTLINE, tooltip="Remover", on_click=remover)
row = ft.Row([check, label, btn_edit, btn_del], alignment=ft.MainAxisAlignment.SPACE_BETWEEN)
card = ft.Container(content=row, padding=10, bgcolor="#FAFAFA",
border=ft.border.all(1, "#E5E7EB"), border_radius=8)
return card
def adicionar(e):
if not entrada.value.strip():
entrada.error_text = "Digite algo..."
page.update()
return
lista.controls.append(make_item(entrada.value.strip()))
entrada.value, entrada.error_text = "", None
page.update()
page.add(ft.Row([entrada, ft.FilledButton("Adicionar", on_click=adicionar)]), lista)
ft.app(target=main)
🧠 Exemplo do mundo real: Imagine um aplicativo de tarefas. Agora o usuário pode editar um compromisso que foi digitado errado ou remover uma tarefa que já não é mais necessária. Além disso, marcar a tarefa como "feita" riscando o texto ajuda a visualizar o progresso do dia.
🔗 Documentação: Checkbox · Text · IconButton · AlertDialog · ListView · Row · Container
À medida que o código do nosso app cresce, ele pode começar a ficar bagunçado e repetitivo. Para resolver isso, usamos a técnica de componentização: criar funções que montam partes da interface, chamadas de funções fábrica ou "factory functions".
🎨 Analogia: imagine que você está montando uma pizzaria. Ao invés de preparar cada pizza manualmente, você cria uma receita padronizada que, ao ser chamada, monta a pizza do mesmo jeito. No nosso app, cada tarefa será como uma "pizza" montada por uma função.
Vamos transformar a criação de uma tarefa em uma função reutilizável:
import flet as ft
def criar_tarefa(texto):
return ft.Container(
content=ft.Row([
ft.Checkbox(),
ft.Text(texto, expand=True),
ft.IconButton(icon=ft.icons.DELETE, icon_color="red")
]),
padding=10,
bgcolor="#F8FAFC",
border=ft.border.all(1, "#CBD5E1"),
border_radius=8
)
def main(page: ft.Page):
entrada = ft.TextField(label="Nova tarefa", expand=True)
lista = ft.ListView(expand=True, spacing=10, padding=10)
def adicionar(e):
if entrada.value.strip():
lista.controls.append(criar_tarefa(entrada.value))
entrada.value = ""
page.update()
page.add(
ft.Row([entrada, ft.FilledButton("Adicionar", on_click=adicionar)]),
lista
)
ft.app(target=main)
Com isso, você separa a lógica de criação dos componentes da lógica do fluxo do app, deixando o código mais organizado, legível e fácil de manter.
🔗 Documentação: Checkbox · IconButton · Container
Quando nossos componentes precisam ter estado interno (como armazenar se uma tarefa foi
concluída), usamos classes baseadas em ft.UserControl. Isso permite encapsular a
lógica de cada item da interface.
🧠 Por que usar classes?
Quando uma tarefa precisa lembrar se está marcada ou não, ou responder a eventos de forma isolada, uma
função simples não é suficiente. A classe resolve isso mantendo o estado e comportamento juntos.
🧱 Analogia: Se a função fábrica monta uma pizza na hora, a classe seria como criar um robô pizzaiolo, que sabe montar, modificar e até jogar fora a própria pizza. Ele lembra o que fez e reage ao cliente.
Veja um exemplo de componente "Tarefa" como classe:
import flet as ft
class Tarefa(ft.UserControl):
def __init__(self, texto):
super().__init__()
self.texto = texto
def build(self):
self.checkbox = ft.Checkbox(on_change=self.marcar)
self.titulo = ft.Text(self.texto, expand=True)
self.botao_excluir = ft.IconButton(
icon=ft.icons.DELETE,
icon_color="red",
on_click=self.excluir
)
return ft.Container(
content=ft.Row([self.checkbox, self.titulo, self.botao_excluir]),
padding=10,
border=ft.border.all(1, "#E2E8F0"),
border_radius=8
)
def marcar(self, e):
if self.checkbox.value:
self.titulo.style = ft.TextStyle(decoration=ft.TextDecoration.LINE_THROUGH)
else:
self.titulo.style = ft.TextStyle(decoration=None)
self.update()
def excluir(self, e):
self.visible = False
self.update()
def main(page: ft.Page):
entrada = ft.TextField(label="Nova tarefa", expand=True)
lista = ft.ListView(expand=True, spacing=10, padding=10)
def adicionar(e):
if entrada.value.strip():
lista.controls.append(Tarefa(entrada.value))
entrada.value = ""
page.update()
page.add(
ft.Row([entrada, ft.FilledButton("Adicionar", on_click=adicionar)]),
lista
)
ft.app(target=main)
🔁 O componente "Tarefa" agora é reutilizável, com lógica própria de marcação e exclusão. Essa é uma abordagem profissional que imita o comportamento de frameworks modernos.
🔗 Documentação: UserControl · Checkbox · Text
À medida que a aplicação cresce, fica difícil manter tudo em um único arquivo ou misturar lógica e interface. Por isso, aprender a organizar seu código e controlar o estado da forma certa é essencial.
🧠 O que é "estado"?
É tudo aquilo que muda com o tempo e afeta a interface. Por exemplo: se um checkbox está marcado ou não, se
um item está visível, o conteúdo de um campo de texto, etc.
🧱 Analogia: Imagine uma cafeteria. O “estado” do pedido muda à medida que o cliente escolhe os itens. Se o cliente muda de ideia, o pedido também muda. O sistema precisa refletir essas mudanças sem bagunçar a lógica.
Com o tempo, é melhor dividir os componentes em arquivos diferentes. Por exemplo:
📁 meu_app/
│
├── main.py # ponto de entrada (page.add e lógica principal)
├── componentes/
│ ├── tarefa.py # componente de tarefa como classe (UserControl)
│ └── cabecalho.py # campo de entrada e botão de adicionar
└── utilidades.py # funções auxiliares (validação, temas etc.)
No main.py, você importa seus próprios componentes:
from componentes.tarefa import Tarefa
from componentes.cabecalho import Cabecalho
Sempre que possível, evite misturar a lógica (como o que acontece ao clicar) com a aparência (como o layout visual). Mantenha funções bem nomeadas e separadas.
# Exemplo ruim
botao = ft.ElevatedButton("Clique aqui", on_click=lambda e: print("Clicou"))
# Exemplo bom
def quando_clicar(e):
print("Clicou")
botao = ft.ElevatedButton("Clique aqui", on_click=quando_clicar)
self.update() quando algo muda.Exemplo de organização interna de um componente com estado:
class Contador(ft.UserControl):
def build(self):
self.valor = 0
self.label = ft.Text(str(self.valor), size=30)
self.botao = ft.FilledButton("Somar", on_click=self.somar)
return ft.Column([self.label, self.botao])
def somar(self, e):
self.valor += 1
self.label.value = str(self.valor)
self.update()
Crie um arquivo vazio chamado __init__.py na pasta componentes. Isso permite
importar os arquivos como um pacote. Exemplo:
from componentes import tarefa, cabecalho
🔗 Documentação: UserControl · TextField · ElevatedButton · Column
Vamos consolidar o conteúdo da semana com um exercício guiado. O objetivo é criar uma aplicação de lista de tarefas que permita:
A aplicação será dividida em componentes reutilizáveis, com boa separação de responsabilidades, usando
UserControl e funções organizadas em arquivos separados.
Recomendação de estrutura de pastas:
meu_app/
├── main.py
├── componentes/
│ ├── cabecalho.py # Campo de entrada e botão de adicionar
│ └── tarefa.py # Classe Tarefa (UserControl)
└── utilidades.py # Validação e funções auxiliares
✅ Siga os tópicos anteriores e desenvolva sua aplicação por partes, testando cada funcionalidade antes de avançar.
Dica: Use variáveis de instância para manter o estado e atualize sempre com
self.update() quando necessário.
Além disso, recomendo revisar os exemplos do próprio site oficial do Flet. Eles oferecem snippets prontos com preview e explicações úteis.
Objetivo: Desenvolver uma aplicação de lista de tarefas completa, com as seguintes funcionalidades:
Entrega:
Avaliação: