
Controles de Layout
Card, Tabs, SafeArea, Divider, VerticalDivider, ExpansionPanelList são outros controles que facilitam a divisão, exibição ou ocultação de conteúdo dentro de uma página de aplicativo.
Seguem-se alguns exemplos de códigos usando esses controles.

Exemplo de uso de Card
Leia sobre a descrição do controle, suas propriedades e evento em Controles de Layout: Card.
O código abaixo exibe a construção de uma página com 3 cards.
import flet as ft
def main(page):
page.title = "Exemplo usando \"card\""
listitulo = ft.ListTile(leading=ft.Icon(ft.icons.ACCESS_ALARM, size=50, color="#D3143E"),
title=ft.Text("Compromisso: 28 de junho de 2024"),
subtitle=ft.Text("Aniversário de seu filho. Não esqueça o presente!"),)
def apagar(e):
listitulo.title= ft.Text("")
listitulo.subtitle=ft.Text("* apagado", color="#aaaaaa")
cartao.color="WHITE"
page.update()
def proximo(e):
listitulo.title=ft.Text("Compromisso: 01 de agosto de 2024")
listitulo.subtitle=ft.Text("Viagem para São Paulo", color="#000000")
cartao.color="#CBEBA8"
page.update()
bt1 = ft.TextButton("Apagar", on_click=apagar)
bt2 = ft.TextButton("Próximo compromisso", on_click=proximo)
linha = ft.Row([bt1, bt2], alignment=ft.MainAxisAlignment.END)
coluna = ft.Column([listitulo, linha,])
contem = ft.Container(coluna, width=500, padding=20,)
cartao = ft.Card(contem, color="YELLOW", shadow_color="white", elevation=8.0)
page.add(cartao)
ft.app(target=main)
Na figura 1 o resultado desse código, quando executado.

Tabs
Leia sobre a descrição do controle, suas propriedades e evento em Controle Tabs.
O controle Tabs, usualmente chamado de guias ou abas em português, é usado para separar conteúdo de categorias diferentes, facilitando a navegação do usuário. Ele simula a marcação de bookmarks em um livro físico, para indicar setores de conteúdos diversos, sendo um padrão de desenho com o qual os usuários estão acostumados, no desktop ou web.

Exemplo de uso de Tabs
O código abaixo exibe a construção de uma página o uso de Tabs.
import flet as ft
def main(page: ft.Page):
page.bgcolor=ft.colors.BROWN_200
ct_interno=ft.Container(content=ft.Text("Conteúdo do Tab 1 dentro de um Card",
size=20, weight=ft.FontWeight.BOLD), padding=45)
card = ft.Card(content= ct_interno, elevation=20, expand=1)
ct_tab1 = ft.Container(content=card, bgcolor="#BAE3ED", alignment=ft.alignment.center)
tab1 = ft.Tab(text="Tab 1", content=ct_tab1)
ct_tab2 = ft.Container(content=ft.Text("Conteúdo de texto do Tab 2", size=30),
bgcolor="#BBF3EF",
margin = ft.margin.all(10.0),
padding=ft.padding.all(1.0),
alignment=ft.alignment.top_right,
border=ft.border.all(2, "#55555"),
border_radius=20)
tab2 = ft.Tab(text="Tab 2", icon=ft.icons.SEARCH, content=ct_tab2)
ct_tab3 = ft.Container(content=ft.Text("Conteúdo de texto do Tab 3", size=30),
bgcolor="#FCF5CC", alignment=ft.alignment.bottom_left)
tab3 = ft.Tab(text="Tab 3", icon=ft.icons.SETTINGS, content=ct_tab3)
tabs = ft.Tabs(tabs=[tab1, tab2, tab3],
selected_index=0,
divider_color="#ABCAD0",
label_color="WHITE",
unselected_label_color="BLACK",
overlay_color = "YELLOW",
indicator_padding=10,
indicator_color="RED")
page.add(ft.Container(tabs, bgcolor=ft.colors.BROWN_200, expand=1))
ft.app(target=main)
O resultado desse código está mostrado na figura 2.

O primeiro tab exibe um card com texto interno, centralizado. O segundo tab tem um container formatado com bordas arredondadas e texto no alto, à direita. O terceiro tab tem texto em baixo, à esquerda.
Observe que no código acima, e na maioria dos blocos de código nessas notas, os controles são montados por partes, separando controles que serão depois inseridos dentro de outros. Para montar o tab1, criamos ct_interno, um container com um texto e o inserimos dentro de card, que é inserido em ct_tab1 e finalmente em tab1. Isso é feito por motívo didático, para que o código fique mais fácil de ser entendido, e não tem efeito no resultado no momento da execução. Esse estilo é uma preferência do programador. Uma forma alternativa para a construção do mesmo controle pode ser vista abaixo.
tab1 = ft.Tab(
text="Tab 1",
content=ft.Container(
content=ft.Card(
content=ft.Container(
content=ft.Text("Conteúdo do Tab 1 dentro de um Card",
size=20, weight=ft.FontWeight.BOLD), padding=45
),
elevation=20, expand=1
),
bgcolor="#BAE3ED",
alignment=ft.alignment.center
)
)
De qualquer forma é importante estabelecer um critério de indentação e se manter coerente com ele.

SafeArea
SafeArea insere conteúdo com preenchimento (padding, as distâncias entre as margens internas do container com as bordas externas do controle) de modo a impedir que os elementos de interface do sistema operacional se sobreponham às do aplicativo. Leia sobre a descrição do controle, suas propriedades em Controles de Layout: SafeAreaCard.
Um exemplo é mostrado no código abaixo.
import flet as ft
class Contador:
contagem = 0
def main(page: ft.Page):
def add_click(e):
cliques.contagem += 1
contador.value = f"Contador: {cliques.contagem}"
contador.update()
page.horizontal_alignment=ft.CrossAxisAlignment.CENTER
page.vertical_alignment=ft.MainAxisAlignment.CENTER
page.floating_action_button = ft.FloatingActionButton(icon=ft.icons.ADD, on_click=add_click)
cliques = Contador()
contador = ft.Text("Contador: 0", size=30, color="WHITE", weight=ft.FontWeight.BOLD)
page.add(
ft.SafeArea(
expand=False,
content=ft.Container(
content=contador,
padding=35, width=300, height=190,
alignment=ft.alignment.center,
bgcolor="BLUE",
gradient=ft.LinearGradient(colors=[ft.colors.BLACK, ft.colors.BLUE_900]),
border=ft.border.all(3, ft.colors.BLUE_100),
border_radius=ft.border_radius.all(10)
)
)
)
ft.app(main)
A execução desse código gera um aplicativo mostrado na figura 3.

Observe que, para manter um contador global foi criada a classe e propriedade State.counter. O objeto cliques = Contador() tem, portanto a propriedade cliques.contagem, que é incrementada a cada clique no botão floating_action_button.
Divider e VerticalDivider
São controles usados para desenhar uma linha divisória, horizontal e vertical, respectivamente. Leia sobre a descrição dos controles e suas propriedades em Controle Divider e VerticalDivider.

import flet as ft
def main(page: ft.Page):
ct1 = ft.Container(bgcolor="#797EF6", expand=True)
ct2 = ft.Container(bgcolor="#4ADEDE", expand=True)
divisorVertical = ft.VerticalDivider(width=50, thickness=10, color="#960cc9")
ct3 = ft.Container(bgcolor="#1AA7EC", expand=True)
ct4 = ft.Container(bgcolor="#1E2F97", expand=True)
divisorHorizontal= ft.Divider(height=50, thickness=40, color="#F6E750")
page.add(
ft.Row([ct1, divisorVertical, ct2], spacing=0, expand=True),
ft.Column([ct3, divisorHorizontal, ct4], spacing=0, expand=True)
)
ft.app(target=main)
O código executado é renderizado como a figura 4.

A única diferença entre propriedades de Divider e VerticalDivider são as propriedades Divider.height e VerticalDivider.width.
ExpansionPanel e ExpansionPanelList
ExpansionPanelList é o controle usado para criar painéis expansíveis, que podem ser contraídos até um mínimo, expandidos para exibir conteúdo, ou removidos da página. ExpansionPanelsList recebe uma lista de controles ExpansionPanel. Leia sobre a descrição dos controles e suas propriedades em Controle ExpansionPanelList e ExpansionPanel.

import flet as ft
class Painel(ft.ExpansionPanel):
def __init__(self, cabecalho, titulo, texto, cor, botao=None, indice=0):
super().__init__()
self.can_tap_header=True
self.bgcolor=cor
self.header=ft.Text(" " + cabecalho, size=22, weight=ft.FontWeight.BOLD)
self.titulo=ft.Text(titulo, size=18)
self.texto=ft.Text(texto, size=17)
self.conteudo=ft.ListTile(
title=self.titulo,
subtitle=self.texto,
trailing=botao
)
self.content=self.conteudo
self.indice = indice
def main(page: ft.Page):
cor = [ft.colors.GREEN_100, ft.colors.BLUE_100, ft.colors.RED_100,
ft.colors.WHITE10, ft.colors.BLUE_500, ft.colors.WHITE70]
def pegar_painel(index):
for pn in panel.controls:
if index == pn.indice: return pn
return None
def mudou(e: ft.ControlEvent):
if int(e.data)==4: return
atividade.conteudo.subtitle.value += f"\nO painel {e.data} foi modificado"
page.update()
def apagar(e: ft.ControlEvent):
pn = pegar_painel(e.control.data)
if pn:
panel.controls.remove(pn)
atividade.conteudo.subtitle.value += f"\nFoi apagado o Painel {e.control.data}"
page.update()
panel = ft.ExpansionPanelList(
elevation=8,
spacing=6,
expand_icon_color=ft.colors.AMBER_100,
divider_color=ft.colors.CYAN_ACCENT_100,
on_change=mudou
)
panel.controls.append(
Painel("Atividades Online", "Lista das ações realizadas no aplicativo.", "",
cor[4], None,0
)
)
panel.controls.append(
Painel(
"Painel 1", "Conteúdo do painel 1", "Clique no ícone para apagar o painel 1.", cor[0],
ft.ElevatedButton("Apagar", icon=ft.icons.DELETE, width=150, height=30, data=1,
on_click=apagar), 1
)
)
panel.controls.append(
Painel(
"Painel 2", "Conteúdo do painel 2", "Clique no ícone para apagar o painel 2.", cor[1],
ft.ElevatedButton("Apagar", icon=ft.icons.DELETE, width=150, height=30, data=2,
on_click=apagar), 2
)
)
panel.controls.append(
Painel(
"Painel 3", "Conteúdo do painel 3", "Clique no ícone para apagar o painel 3.", cor[2],
ft.ElevatedButton("Apagar", icon=ft.icons.DELETE, width=150, height=30, data=3,
on_click=apagar), 3
)
)
atividade = Painel("Resumo de Atividades", "", "Conteúdo de texto da informação de uso", cor[5])
panel.controls.append(atividade)
page.add(panel)
ft.app(target=main)
O resultado pode ser visto na figura 5.

Como são gerados no código vários painéis flet.ExpansionPanel é interessante criar a classe Class Painel que herda de ExpansionPanel, personalizando na inicialização cada caso a ser inserido. O último dos painéis exibidos, atividade é usado para armazenar e exibir as ações feitas no aplicativo, no campo atividade.conteudo.subtitle.value. Essas ações são executadas nos eventos ExpansionPanelList.on_change e pelo clique nos botões ElevatedButton colocados em cada painel, exceto o último, e pela ação de apagar um painel. Os painéis são identificados pela propriedade painel.indice, usada para encontrar e deletar um deles.

Muito bom esse site. Já está nos meus favoritos!
Obrigado André. Me ajude a divulgar!
Gostei, muito meus parabéns meu amigo….
Obrigado pela força, Neusa.
Muito, bem explicado precisamos de mais como vocês muito obrigado.
Obrigado, Marcio. Visite-nos sempre!