Flet: AppBar e BottomAppBar

Controle Appbar

O controle AppBar insere uma barra de aplicativos, às vezes chamada de barra de ação, na parte superior da página do Flet. Essa barra pode conter botões, menus dropdown e outros widgets que acionam ações desejados pelo usuário. A barra de aplicativos tem uma estrutura visual e elementos interativos familiares para os usuários.

Exemplo de Uso da Appbar

Um controle AppBar é aplicado à propriedade da página, page.appbar = app_barra. O código a seguir mostra um pequeno aplicativo que faz uso da Appbar.

import flet as ft

def main(page: ft.Page):
    page.bgcolor="#72BFDE"
    page.horizontal_alignment=ft.CrossAxisAlignment.CENTER

    def clicou_botao(e):
        texto.value += f"\nO ícone \"{e.control.icon}\" foi clicado"
        page.update()

    def clicou_check(e):
        e.control.checked = not e.control.checked
        insere = f"\nO {e.control.text} {'' if e.control.checked else 'não'} está selecionado!"
        texto.value += insere
        page.update()

    class Pop(ft.PopupMenuItem):
        def __init__(self, texto):
            super().__init__()
            self.text=texto
            self.checked=False
            self.on_click=clicou_check

    app_barra = ft.AppBar(
        leading=ft.IconButton(ft.icons.SOUTH_AMERICA, icon_color="white", on_click=clicou_botao),
        leading_width=40,
        title=ft.Text("AppBar: Barra de Aplicativo", color="white"),
        center_title=False,
        bgcolor="#8FD0DA",
        actions=[
            ft.IconButton(ft.icons.PODCASTS, icon_color="white", on_click=clicou_botao),
            ft.IconButton(ft.icons.CAR_REPAIR, icon_color="white", on_click=clicou_botao),
            ft.PopupMenuButton(
                items=[Pop("Item 1"), Pop("Item 2"), Pop(""), Pop("Item 3")],
                bgcolor="#A88413", icon_color="#ffffff"
            ),
        ],
    )

    titulo = ft.Text("Clique nos ícones ou menu dropdown", size=15, color="#333333")
    texto = ft.Text("", size=20, color=ft.colors.BLACK)

    page.appbar = app_barra
    page.add(
        ft.Text(
            "Lista de ações executadas:",
            size=30,
            color=ft.colors.BLACK
        ),
        ft.Container(
            content=ft.Column([titulo, texto]),
            padding=20,
            width=500, height=500,
            bgcolor=ft.colors.BLUE_100,
            border=ft.border.all(1, "#aaaaff")
        )
    )

ft.app(target=main)

A variável app_barra contém uma AppBar com ícones e um menu dropdown. Um clique em cada ícone ativa a função clicou_botao(e) que acrescenta uma linha de texto à variável texto que é um controle dentro do container na página.

Os cliques ativam as funções clicou_botao(e) e clicou_check(e) que recebem os objetos de eventos e (da classe ControlEvent). Esse objeto possui a propriedade .control que, por sua vez possui um dicionárioiconbutton, no caso do IconButton e popupmenuitem, no caso do PopupMenuButton. Os dois dicionários estão listados abaixo (para algum momento da execução):

  • iconbutton = {‘icon’: ‘podcasts’, ‘iconcolor’: ‘white’, ‘n’: ‘action’}
  • popupmenuitem = {‘text’: ‘Item 1’, ‘checked’: False}

Em execução o aplicativo é renderizado de acordo com a figura 1. A figura 1(a) mostra o estado inicial, com o PopupMenuButton clicado. A figura 1(b) representa o estado após um clique nos três ícones, seguidos de 2 cliques no item 1 do menu.

Figura 1: AppBar

Propriedades de AppBar

As seguintes propriedades estão definidas para o controle AppBar:

Propriedade Descrição
actions uma lista de controles a exibir na linha, depois do título.
Normalmente são IconButtons mas pode ser usado um PopupMenuButton.
Se AppBar.adaptive=True em dispositivo iOS ou macOS, apenas o primeiro elemento da lista será exibido.
adaptive booleano, default=False. Se True, o controle será adaptável à plataforma de destino. No iOS e macOS, uma barra CupertinoAppBar é criada, com funcionalidade e aparência esperadas no iOS. Nas demais plataformas, um Material AppBar é criado.
automatically_imply_leading booleano, válida apenas se leading = null. Se False aloca o espaço inicial para o título. Se True tenta deduzir automaticamente será o controle na primeira posição.
bgcolor cor de fundo da AppBar. A cor do tema atual é o default.
center_title booleano, default=False. Se o título deve ser centralizado.
clip_behavior O comprtamento de recorte (clipping) do conteúdo. O valor é do tipo ClipBehavior.
color definição de cor para os controles de texto e ícones na barra. A cor padrão é definida pelo tema atual.
elevation elevação da barra de aplicativos. Observação: o efeito só é visível quando o design Material 2 é usado (Theme.use_material3=False). O valor é do tipo OptionalNumber com default 4.
elevation_on_scroll elevação usada se houver algum controle rolado sob a barra. O valor é do tipo OptionalNumber.
exclude_header_semantics booleano, default=False. Se o título deve envolver um controle Semantics.
force_material_transparency booleano. Torna a barra transparente (em vez do padrão Material). Também remove a exibição visual de bgcolor e elevation (e outras características).
is_secondary booleano, default=False. Se a barra não é exibida na parte superior da tela.
leading controle exibido antes do título na barra. Normalmente é um Icon ou um IconButton. O valor é do tipo Control.
leading_width largura do controle leading. O valor é do tipo OptionalNumber com default 56,0.
shadow_color cor da sombra abaixo da barra, só visível se a elevation > 0.
shape A forma da barra e de sua sombra. O valor é do tipo OutlinedBorder.
surface_tint_color cor da sobreposição entre tom da superfície e da barra para indicar elevação. Default: nenhuma sobreposição é aplicada.
title controle principal exibido na barra, normalmente, um controle de texto. Se AppBar.adaptive=True no iOS ou macOS, o controle é centralizado automaticamente. O valor é do tipo Control.
title_spacing Espaçamento do título no eixo horizontal, aplicado mesmo se não não existirem controles leading ou action.
Se title_spacing = 0.0 o título ocupa todo o espaço disponível. O valor é do tipo OptionalNumber.
title_text_style estilo usado nos controles de texto do título. O valor é do tipo TextStyle.
toolbar_height altura do componente toolbar na AppBar. O valor é do tipo OptionalNumber, com default 56.0.
toolbar_opacity opacidade da barra, variando de 0.0 (transparente) a 1.0 (totalmente opaco). O valor é do tipo OptionalNumber com default 1.0.
toolbar_text_style estilo usado nos controles de texto leading ou action, exceto no título. O valor é do tipo TextStyle.


Notas †:Semantics Um controle que leva uma anotação descrendo o significado do widget. Usado para acessibilidade, search engines e ferramentas de análise semântica.

Controle BottomAppBar

O controle BottomAppBar é similar ao AppBar mas disposto no fundo da página do aplicativo.

Propriedades de BottomAppBar

As seguintes propriedades estão definidas para o controle BottomAppBar:

Propriedade Descrição
bgcolor cor de fundo para o BottomAppBar. A cor default é definida pelo tema atual.
clip_behavior O conteúdo será recortado (ou não) de acordo com esta opção. O valor é do tipo ClipBehavior e o default é ClipBehavior.NONE.
content Um controle filho contido pelo BottomAppBar.
elevation controla a elevação (e portanto a sombra abaixo do BottomAppBar). O default é 4.
height altura do BottomAppBar. O default é 80,0 no material 3.
notch_margin margem entre o FloatingActionButton e o entalhe (notch) do BottomAppBar. Só visível se shape=None.
padding margens internas na decoração (fundo, borda). É uma instância da classe Padding, e o default é padding.symmetric(vertical=12.0, horizontal=16.0).
shadow_color cor da sombra abaixo da BottomAppBar.
shape entalhe (notch) para o botão de ação flutuante. O valor é do tipo NotchShape.
surface_tint_color cor usada como sobreposição de bgcolor para indicar elevação. Se for None, nenhuma sobreposição será aplicada. Caso contrário, a cor será composta em cima de bgcolor com opacidade relacionada à elevação e a cor de fundo da BottomAppBar. O default é None.

Nota: †: Notch ou entalhe é o recorte usado para acomodar componentes na parte superior frontal na tela dos smartphones.

Exemplo de Uso da BottomAppbar

Um controle BottomAppBar é aplicado à propriedade da página, page.bottom_appbar = barra_fundo. O código a seguir mostra um pequeno aplicativo que faz uso da BottomAppbar.

import flet as ft

def main(page: ft.Page):
    def clicou(e):
        texto.visible = not texto.visible
        page.update()

    page.horizontal_alignment = page.vertical_alignment = "center"
    page.floating_action_button = ft.FloatingActionButton(icon=ft.icons.ADD, on_click=clicou)
    page.floating_action_button_location = ft.FloatingActionButtonLocation.CENTER_DOCKED

    page.bottom_appbar = ft.BottomAppBar(
        bgcolor=ft.colors.BLUE,
        shape=ft.NotchShape.CIRCULAR,
        content=ft.Row(
            controls=[
                ft.IconButton(icon=ft.icons.MENU, icon_color=ft.colors.WHITE),
                ft.Container(expand=True),
                ft.IconButton(icon=ft.icons.SEARCH, icon_color=ft.colors.WHITE),
                ft.IconButton(icon=ft.icons.FAVORITE, icon_color=ft.colors.WHITE),
            ]
        ),
    )

    texto = ft.Text(
        (
        'Lorem ipsum dolor sit amet, consectetur adipisicing elit,\n'
        'sed do eiusmod tempor incididunt ut labore et dolore magna\n'
        'aliqua. Ut enim ad minim veniam, quis nostrud exercitation\n'
        'ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\n'
        'Duis aute irure dolor in reprehenderit in voluptate velit\n'
        'esse cillum dolore eu fugiat nulla pariatur. Excepteur sint\n'
        'occaecat cupidatat non proident, sunt in culpa qui officia\n'
        'deserunt mollit anim id est laborum.'
        ),
        size=15, color=ft.colors.WHITE
    )

    page.add(texto)

ft.app(target=main)
Figura 2: BottomAppBar

A figura 2 mostra a execução do aplicativo. Cliques no botão FloatingActionButton (com o ícone +) ocultam / exibem a texto centralizado na página.

Versões Estilizadas para IOS

Dois controles análogos estão disponíveis com o estilo iOS:

Um exemplo breve de uso está descrito a seguir:

import flet as ft

def main(page: ft.Page):
    page.theme_mode = ft.ThemeMode.LIGHT
    page.horizontal_alignment = page.vertical_alignment = "center"
    page.bgcolor="#F4F0BD"
    icone = ["Bússula", "Carros", "Bandeira"]

    def clicou(e):
        texto2.value += f"\nClicou no ícone {icone[e.control.selected_index]}"
        page.update()

    page.appbar = ft.CupertinoAppBar(
        leading=ft.Icon(ft.icons.PALETTE),
        bgcolor="#976A4E",
        trailing=ft.Icon(ft.icons.WB_SUNNY_OUTLINED),
        middle=ft.Text("Exemplo de CupertinoAppBar e CupertinoNavigationBar")
    )

    page.navigation_bar = ft.CupertinoNavigationBar(
        bgcolor="#4E7B97",
        inactive_color=ft.colors.GREY,
        active_color=ft.colors.BLACK,
        on_change=clicou,
        destinations=[
            ft.NavigationBarDestination(icon=ft.icons.EXPLORE, label="Bússula"),
            ft.NavigationBarDestination(icon=ft.icons.COMMUTE, label="Carros"),
            ft.NavigationBarDestination(
                icon=ft.icons.BOOKMARK_BORDER,
                selected_icon=ft.icons.BOOKMARK,
                label="Bandeira",
            ),
        ]
    )
    texto1 = ft.Text("Exemplo de Uso dos controles:\n\nCupertinoAppBar e CupertinoNavigationBar", size=18)
    texto2 = ft.Text("", size=20)
    page.add(texto1, texto2)

ft.app(target=main)

O Material Design é desenvolvido pelo Google e pode ser usado para desenvolver aplicativos Android, iOS, web e desktop.O Cupertino é desenvolvido pela Apple. Ele é baseado nas Diretrizes de Interface Humana da Apple, que implementam a linguagem de design atual do iOS.

O Flutter e o Flet vem com bibliotecas de widgets Material e Cupertino para desenvolver um aplicativo que pareça e seja nativo para qualquer plataforma.

Figura 3: Controles estilizados Cupertino

A figura 3 mostra a execução do aplicativo após cliques nos botões na barra de fundo.

Flet: Controles personalizados

Personalizando Controles

O Flet inclui muitos controles prontos para uso, como já listamos nessas páginas. Mesmo assim é sempre possível que o desenvolvedor pense em um controle diferente, personalizado para fim específico. No Flet podemos partir dos controles embutidos e modificá-los, usando os conceitos de programação orientada a objetos do Python. Os controles definidos dessa forma podem ser armazenados e reutilizados posteriormente em outros projetos.

Controles estilizados

A personalização mais simples consiste em usar um controle existente e o estilizar, gerando nova aparência e acrescentando funcionalidades (métodos). Para fazer isso você cria uma nova classe em Python que herda do controle Flet que se deseja personalizar. No exemplo seguinte modificamos um botão ElevatedButton:

class NovoBotao(ft.ElevatedButton)
    def __init__(self, texto):
        super().__init__()
        self.bgcolor = ft.colors.ORANGE_300
        self.color = ft.colors.GREEN_800
        self.text = texto

O controle assim personalizado possui um construtor que define propriedades e eventos, além de passar dados, no caso através da variável texto. A chamada a super().__init__() no construtor dá o acesso às propriedades e métodos originais do controle Flet usado na herança. Uma vez criada essa classe esse controle pode ser usado no aplicativo:

import flet as ft

def main(page: ft.Page):
    page.add(NovoBotao(text="Sim"), NovoBotao(text="Não"))

ft.app(target=main)

Um exemplo de uso pode ser visto no código abaixo:


import flet as ft

def clicou_sim(e):
    e.control.text = "Clicou sim!"
    e.control.update()

def clicou_nao(e):
    e.control.text = "Clicou não!"
    e.control.update()

class NovoBotao(ft.ElevatedButton):
    def __init__(self, clicou, texto="Não"):
        super().__init__()
        self.bgcolor = ft.colors.BLUE_800
        self.color = ft.colors.YELLOW_100
        self.icon="chair_outlined"
        self.icon_color="GREEN_100"
        self.text = texto
        self.on_click = clicou

def main(page: ft.Page):
    page.bgcolor=ft.colors.BLUE_50

    page.add(
        NovoBotao(texto="Sim", clicou = clicou_sim),
        NovoBotao(clicou = clicou_nao)
    )

ft.app(target=main)
Figura 1

No exemplo acima vemos que a classe estende um ElevatedButton e seu construtor permite a alteração do texto e do nome da função executada sob o evento on_click. Essas funções devem estar definidas antes do ponto em que serão chamadas, e antes da definição da classe. Por default o texto no botão será “Não”. A execução do código gera a janela inicial com os botões ilustrados na figura 1 (a), que se alteram para 1 (b) quando o botão “Não” é clicado.

Controles Compostos

Alguns exemplos de widgets do Flet que são também containers (podem conter outros controles internamente, são: View, Page, Container, Column, Row e Stack.

Os controles personalizados compostos herdam de controles que podem ser também containers, como Page, Row ou Column, que por sua vez abrigam outros controles filhos. O controle criado dessa forma tem acesso às propriedades e métodos do container e também dos controles filhos.

O exemplo abaixo mostra um esqueleto de um aplicativo TODO simples, que define um controle personalizado que herda de ft.Row e acrescenta outros controles.

                                                                                   
import flet as ft

class Row_Tarefa(ft.Row):
    def __init__(self, texto):
        super().__init__()
        self.ver_texto = ft.Text(texto)
        self.editar_texto = ft.TextField(texto, visible=False)
        self.editar_botao = ft.IconButton(
            icon=ft.icons.EDIT,
            on_click=self.modificar
        )
        self.gravar_botao = ft.IconButton(
            icon=ft.icons.SAVE,
            on_click=self.modificar,
            visible=False
        )
        self.controls = [
            ft.Checkbox(),
            self.ver_texto,
            self.editar_texto,
            self.editar_botao,
            self.gravar_botao,
        ]

    def modificar(self, e):
        op = e.control.icon=="save"
        self.editar_botao.visible = op
        self.gravar_botao.visible = not op
        self.ver_texto.visible = op
        self.editar_texto.visible = not op
        if op:
            self.ver_texto.value = self.editar_texto.value
        self.update()

def main(page: ft.Page):

    page.add(
        Row_Tarefa(texto="Fazer Compras"),
        Row_Tarefa(texto="Terminar Código"),
        Row_Tarefa(texto="Enviar Projeto"),
    )

ft.app(target=main)

Figura 2

A execução desse código gera a janela mostrada na figura 2: (a) é a janela inicial; (b) o primeiro botão de editar foi clicado e o texto alterado; (c) a alteração foi transferida para o texto, com um clique no botão de gravar.

Observe que o controle herda de Row e acrescenta a ele os controles ft.Text, ft.TextField e dois controles ft.IconButton, que não existem no controle pai. Esses controle são inseridos nas propriedades personalizadas ver_texto, editar_texto, editar_botao e gravar_botao. Definidas essas propriedades elas são inseridas, juntas com um Checkbox, na lista de controles (controls) (que e nativa de Row). Três linhas com o objeto Row_Tarefa são inseridos na página.

Tanto o botão gravar_botao quanto editar_botao respondem ao evento on_click ativando a função modificar que recebe o parâmetro e que contém a propriedade e.control.icon=="save"/"edit". A função decide com ela quais controles devem ser exibidos ou ocultados, e altera a propriedade ver_texto.value quando o botão gravar foi clicado.

Nenhum evento foi definido associado aos botões Checkbox que, por isso, não executam nenhuma ação.

Métodos do ciclo de vida

Quando você cria controles personalizados, alguns métodos se tornam disponíveis para o gerenciamento do ciclo de vida do controle:

Método Efeito
build() chamado quando o controle está sendo criado e atribuído à página. Ele pode ser sobrescrito (override) para implementar uma lógica que não pode ser executada no construtor do controle porque necessita do acesso à self.page. Por exemplo: esse seria o local certo para escolher um ícone que depende qual é a plataforma em self.page.platform.
did_mount() chamado após a adição do controle à página e recebe um uid transitório. Por exemplo: o widget Wheather chama uma API (API Open Weather) a cada minuto para atualizar seus dados climáticos.
will_unmount() chamado antes que o controle seja removido da página. Pode ser usado para operação de limpeza.
before_update() chamado sempre que o controle está sendo atualizado. O método update() não deve ser chamado dentro de before_update(), ou um loop infinito seria gerado.

Controles isolados

Controles personalizados recebem automaticamente a propriedade is_isolated, que recebe um booleano com False. Se for ajustado is_isolated = True esse controle fica isolado do layout geral, o que significa que quando o método update() for chamado no controle pai, o próprio pai e seus filhos serão atualizados, exceto o controle isolado. Para atualizar controles isolados, seu próprio método self.update() deve ser chamado. É boa prática que, se self.update() for chamado em um controle personalizado, que ele seja isolado.

Por exemplo, nos códigos acima, o botão NovoBotao não precisa ser isolado, mas Row_Tarefa(ft.Row) deve ser:

class Row_Tarefa(ft.Row):
    def __init__(self, texto):
        super().__init__()
        self.isolated = True
        self.ver_texto = ft.Text(texto)
        self.editar_texto = ft.TextField(texto, visible=False)

DNF4: Gerenciador de Pacotes


DNF: Gerenciador de Pacotes de Software no Linux

O que é DNF

DNF é um gerenciador de pacotes de software, usado para instalar, atualizar e remover pacotes no Fedora e outras distribuições do Linux. DNF (Dandified Yum) foi proposto como substituto do YUM (Yellowdog Updater Modified), ambos usados em sistemas operacionais que utilizam pacotes RPM.

Um gerenciador de pacotes é um conjunto de aplicativos usados para automatizar o processo de instalação, atualização, configuração e remoção de programas de computador de uma maneira consistente. Ele manipula pacotes, que são distribuições de software e dados, contendo metadados como o nome do software, descrição de propósito, versão, fornecedor, checksum e uma lista de dependências necessárias. Na instalação, os metadados são armazenados em um banco de dados local. Gerenciadores de pacotes usam esse banco de dados de dependências para evitar incompatibilidades de software e pré-requisitos ausentes. Eles trabalham em conjunto com os repositórios de software, algumas vezes acessados pelas lojas de aplicativos. Diversas distribuições Linus usam DNF (ou YUM), entre elas: RedHat Enterprise Linux, Fedora, CentOS, Oracle Enterprise Linux, SUSE, OpenSUSE, Mageia e Turbolinux.

O DNF facilita a manutenção de pacotes, verificando automaticamente as dependências e determinando as ações necessárias para instalar os pacotes. Este método elimina a necessidade de instalar ou atualizar manualmente o pacote e suas dependências usando o comando rpm.

Uso do dnf

Nesse artigo usamos as seguintes convenções:
# linhas de comentários
$ linhas de código (input)
➡ linhas de saída (output)

O dnf pode ser usado para buscar, instalar, atualizar ou remover pacotes. Por exemplo:

# para pesquisar nos repositórios um tipo de pacote:
$ dnf search nome_do_pacote

# para instalar o pacote:
$ dnf install nome_do_pacote

# para remover um pacote:
$ dnf remove nome_do_pacote

# em muitos casos pode ser necessário rodar o comando como superuser
$ sudo dnf comando

Outros comandos do dnf de uso comum são:

Comando, aliás Descrição
autoremove remove dependências instaladas que não são mais necessárias para os programas instalados.
check-update verifica se existem atualizações, sem baixar nem instalar os pacotes.
downgrade, dg reverte para a versão anterior de um pacote.
download baixa um pacote, sem instalar.
info, if fornece informações básicas sobre o pacote, incluindo nome, versão, lançamento e descrição.
reinstall, rei reinstala o pacote que já está instalado.
remove remove (desinstala) o pacote do sistema.
upgrade, up verifica os repositórios em busca de pacotes mais recentes e os atualiza.
search procura pelo pacote nos repositórios registrados.
system upgrade atualiza o sistema operacional para a sua versão mais atualizada.

Alguns manuais encontrados online descrevem o comando update como sendo diferente de upgrade. Mas, de acordo com as páginas de man dnf, update é apenas um aliás, além de ser obsoleto (deprecated), o que significa que será eventualmente removido. Um aliás válido é dnf up.Você pode encontrar os demais parâmetros usados com DNF digitando dnf -help para uma lista de parâmetros ou dnf --help para a ajuda completa. A página de manual pode ser vista com man dnf.

Exemplos de Uso

Como exemplo, procuraremos pelo aplicativo focuswriter, um editor de texto distraction free. Ele é instalado e, em seguida, desinstalado. A pesquisa é feita nos repositórios cadastrados.

# pesquisando pelo aplicativo
$ dnf search focuswriter

➡ ========= Name Exactly Matched: focuswriter ========================
➡ focuswriter.x86_64 : A full screen, distraction-free writing program

# instalando o focuswriter (superuser é exigido)
$ sudo dnf install focuswriter
# aparece o aplicativo e uma lista de dependências (Is this ok? [y/n])

# desinstalando um aplicativo
$ sudo dnf remove focuswriter
# aparece o aplicativo e uma lista de dependências (Is this ok? [y/n])

Instalando Pacotes

O comando dnf [options] install <spec>... instala pacotes e verifica se todas as dependências estão satisfeitas (instaladas no sistema). <spec> pode se referir a pacotes, módulos ou grupos. Uma mensagem de erro será emitida se o pacote não estiver disponível nos repósitórios declarados. Se a versão do pacote for especificada ela será instalada, independentemente de qual versão já está instalada ou qual é a mais recente. A versão previamente instalada será removida.

Alguns exemplos podem ajudar a explicar o uso de install:

# instala o pacote vlc (vlc é o nome do pacote)
$ dnf install vlc

# instala o arquivo rpm tito-0.6.2-1.fc22.noarch.rpm na pasta ~/Downloads
$ dnf install ~/Downloads/tito-0.6.2-1.fc22.noarch.rpm

# instala o pacote do repositório, na versão especificada
# Se o pacote já instalado ele será atualizado ou downgraded (tentativamente)
$ dnf install tito-0.5.6-1.fc22

# instala a última versão do pacote. Se já instalado o pacote será atualizado ou downgraded (tentativamente)
# a instalação falhará se o pacote atualizado não puder ser instalado
$ dnf --best instala tito

# nesse caso vim não é o nome do pacote mas de um grupo
# dnf instalará o conjunto de pacote e dependências necessárias
$ dnf install vim

# instala o pacote diretamente da URL
$ dnf install https://fedoraproject.org//packages/nome_do_pacote.rpm

# instale todos os perfis do módulo 'docker' e seus RPMs
$ dnf install '@docker'

# instala o grupo 'Web Server'
$ dnf install '@Web Server'

# instala o pacote que fornece o arquivo /usr/bin/rpmsign.
$ dnf install /usr/bin/rpmsign

# instala o pacote tito sem dependências fracas (não exigidas para o
# funcionamento básico do pacote, como documentação, plugins, funções adicionais, etc).
$ dnf -y install tito --setopt=install_weak_deps=False

# instala todos os pacotes do "FEDORA-2018-b7b99fe852".
$ dnf install --advisory=FEDORA-2018-b7b99fe852 \*

Listando Pacotes

Podemos listar os pacotes instalados com dnf list installed:

$ dnf list installed
# uma lista de apps instalados pelo sistema ou pelo usuário,
# é exibida (com nome, versão e repositório de origem).

# dica: se você quer analisar a lista em um editor de texto faça:
$ dnf list installed > apps_instalados.txt
# um arquivo de nome apps_instalados.txt é gravado no diretório atual

# para contar quantos pacotes estão instalados
$ dnf list installed | wc -l

O “pipe” | envia a saida em lista para o comando seguinte. O comando wc do BASH faz uma contagem das linhas emitidas na saída de list.

Todos os pacotes instalados em sistemas que usam RPM deixam informações armazenadas em um banco de dados SQLite. Existem mais de uma forma de consultar esse banco de dados e encontrar detalhes sobre pacotes instalados, por exemplo usando o comando rpm. No Fedora esse banco de dados fica no diretório /var/lib/rpm/rpmdb.sqlite.

dnf list é uma das formas de acessar essa tabela. Outros comandos são possíveis:

Comando, aliás Descrição
dnf list [–all] Lista os pacotes presentes no RPMDB, nos repositórios registrados ou ambos.
dnf list –installed Lista pacotes instalados.
dnf list –available Lista pacotes disponíveis.
dnf list –extras Lista pacotes extras, i.e. pacotes instalados mas não disponíveis nos repositórios registrados.
dnf list –obsoletes Lista pacotes instalados obsoletos, i.e., com atualização disponíveis nos repositórios registrados.
dnf list –recent Lista pacotes adicionados recentemente nos repositórios registrados.
dnf list –upgrades Lista as atualizações disponíveis para pacotes instalados.
dnf list –autoremove Lista os pacotes que serão removidos através do comando dnf autoremove.

Todos os comandos acima admitem a inclusão de opções e discriminação de tipos de pacote, na forma:
dnf [option] comando <especifica-pacote>. Confira as opções disponíveis na páginas do manual usando man dnf.

Gerenciando Repositórios

Repositórios são servidores onde estão armazenados os pacotes de software das distribuições do Linux. As grandes distribuições mantém os seus repositórios próprios, que são acessados por meio dos comandos de gerenciamento de pacotes como dnf (do Fedora), apt (do Ubuntu) ou pacman (do Arch).

No Fedora os repositórios cadastrados podem ser encontrados no diretório etc/yum.repos.d/. Esses repositórios podem ser gerenciados com dnf:

# para ver uma lista de repositórios disponíneis e habilitados
$ dnf repolist

# para ver uma lista de todos os repositórios, habilitados ou não
$ dnf repolist all

Novos repositórios podem ser inseridos e habilitados simplesmente acrescentando-se um arquivo .repo na pasta /etc/yum.repos.d. A mesma operação pode ser feita com o comando dnf config-manager. Por exemplo, podemos inserir o repositório que contém o Sublime Text (um editor de código). Em seguido usamos cat para listar o conteúdo do arquivo inserido:

# inserindo o repositório para Sublime Text (stable, no Fedora)
$ sudo dnf config-manager --add-repo https://download.sublimetext.com/rpm/stable/x86_64/sublime-text.repo
➡ Adding repo from: https://download.sublimetext.com/rpm/stable/x86_64/sublime-text.repo

# o seguinte arquivo é inserido na pasta /etc/yum.repos.d/
$ cat /etc/yum.repos.d/sublime-text.repo
➡ [sublime-text]
➡ name=Sublime Text - x86_64 - Stable
➡ baseurl=https://download.sublimetext.com/rpm/stable/x86_64
➡ enabled=1
➡ gpgcheck=1
➡ gpgkey=https://download.sublimetext.com/sublimehq-rpm-pub.gpg

O arquivo *.repo tem a seguinte estrutura:

  • [ID do pacote]: id do repositório,
  • name: nome do repositório,
  • baseurl: URL do repositório,
  • enabled=1: 1 para habilitado, 0 para desabilitado,
  • gpgcheck=1: 1 para permitir verificação da assinatura gpg, 0 para impedir,
  • gpgkey: URL da chave GPG, usada para assinar os pacotes.

Feito isso o pacote para o Sublime Text pode ser instalado com sudo dnf install sublime-text.

Repositórios podem ser habilitados ou desabilitados com dnf --enablerepo. Essa operação pode ser útil, por exemplo quando um pacote está disponível em mais de um repositório mas desejamos instalar de um específico.

# para desabilitar um repositório
$ sudo dnf config-manager --set-disabled id_do_repositorio

# para habilitar um repositório
sudo dnf config-manager --set-enabled id_do_repositorio

Por exemplo, podemos encontrar o id do repositório Sublime Text (supondo que esteja instalado e habilitado) com dnf repolist --all, que é, como se vê abaixo, sublime-text. Em seguida vamos desabilitá-lo, e reabilitá-lo em seguida.

$ dnf repolist --all
➡ repo id repo name status
➡ docker-ce-nightly Docker CE Nightly - x86_64 disabled
➡ fedora Fedora 40 - x86_64 enabled
➡ google-chrome google-chrome enabled
➡ sublime-text Sublime Text - x86_64 enabled

# para desabilitar o repositório
$ sudo dnf config-manager --set-disabled sublime-text

# para habilitar o repositório
$ sudo dnf config-manager --set-enabled sublime-text

# para remover o repositório do sistema basta apagar o arquivo em /etc/yum.repos.d
$ sudo rm /etc/yum.repos.d/sublime-text.repo

Uma vez que o repositório tenha sido excluído o dnf não tentará instalar ou atualizar seus pacotes.

Opções

O comando dnf [options] install <spec>... pode receber parâmetros em [options]. Alguns opções são listadas abaixo. Veja a lista completa na documentação do dnf.

Opção, aliás Descrição
–assumeno Responder automaticamente “não” para todas as perguntas na execução de dnf.
-b, –best Tenta encontrar as melhores versões de pacotes disponíveis nas transações.
–bugfix Inclui pacotes que corrigem algum bug. Pode ser usado com install, repoquery, updateinfo, upgrade e offline-upgrade (dnf-plugins-core).
-C, –cacheonly Usa apenas o cache do sistema, não realizar nenhuma atualização, mesmo que o cache esteja expirado.
–color=<cor> Informa se cores são usadas na saída do terminal. Valores válidos: always, never e auto (default).
–comment=<comentário> Adiciona um comentário ao histórico de transações.
-c <arquivo de configuração>, –config=<arquivo de configuração> Indica a localização do arquivo de configuração.
–disable, –set-disable Desativa repositórios especificados. Deve ser usada com o comando config-manager (dnf-plugins-core).
–disableplugin=<nomes dos plug-ins> Desativa os plug-ins listados.
–disablerepo=<repoid> Desativa temporariamente os repositórios no comando dnf atual. Pode receber um ID, lista de IDs separados por vírgula ou um conjunto de IDs.
–downloaddir=<caminho>, –destdir=<caminho> Redireciona os pacotes baixados para o diretório fornecido. Deve ser usada em conjunto com a opção --downloadonly, com os comandos download, modulesync, reposync ou system-upgrade (dnf-plugins-core).
–downloadonly Baixa o conjunto de pacotes sem realizar nenhuma transação de instalar, atualizar ou apagar.
–enable, –set-enabled Habilita repositórios especificados. Deve ser usada junto com config-manager (dnf-plugins-core).
–enableplugin=<nomes dos plug-ins> Habilita os plug-ins especificados por nomes ou globs.
–enablerepo=<repoid> Habilita temporariamente repositórios. Aceita um ID, uma lista de IDs separados por vírgula ou um conjunto de IDs.
-x <especificação do arquivo de pacote>, –exclude=<especificação do arquivo de pacote> Exclui pacotes especificados por <package-file-spec>.
–forcearch=<arquitetura> Força o uso de uma arquitetura. Qualquer arquitetura pode ser especificada.
-h, –help, –help-cmd Mostra o texto da Ajuda.
–noautoremove Desabilita a remoção de dependências não usadas.
–nobest Ajusta como falsa a opção best para que as transações não sejam limitadas ao melhor candidato.
–nodocs Não instala a documentação.
–nogpgcheck Não faz o teste de assinaturas GPG dos pacotes, se permitido pela diretriz RPM.
–noplugins Disabilita todos os plugins.
-q, –quiet Se usada com comandos não interativos, exibe apenas conteúdo mais relevante. Suprime mensagens sobre o estado ou ações do DNF.
-R , –randomwait= Tempo máximo de espera.
–refresh Recarrega a metadata antes de executar o o comando.
–repofrompath ,<path/url> Especifica um repositório extra, indicado por <path/url>.
–repo=, –repoid= Habilita um repositório dado por id ou glob.
–security Inclui pacotes que fornecam ajustes de segurança. Pode ser usado com install, repoquery, updateinfo, upgrade e offline-upgrade (dnf-plugins-core).
–setopt== Sobrescreve uma opção de configuração definida no arquivo de configuração. A especificação de valor vazio (por ex., --setopt=tsflags=) reseta o opção.
–skip-broken Remove pacotes que estão causando problemas na transação. Permite que se execute uma ação mesmo que ela cause problemas de dependência.
–showduplicates Mostra pacotes duplicados nos repositórios. Aplicável aos comandos list e search.
-v, –verbose Operação exibindo todas as mensagens de debug.
–version Mostra a versão do DNF e termina.
-y, –assumeyes Responde automaticamente “yes” para todas as perguntas.

Histórico

O dnf armazena um histórico de transações realizadas, que permite a verificação de tudo o que foi feito. Isso é útil para identificar problemas e desfazer operações.

# para listar o histórico de transações usamos
$ dnf history

# ou
$ dnf history list

Esses comandos exibem uma tabela com quatro colunas: ID da transação, a linha de comando executada, data e hora, a ação executada e o número de pacotes alterados. Por default as transações mais recentes aparecem primeiro.

➡ ID  | Command line             | Date and time       | Action(s) | Altered
➡ --------------------------------------------------------------------------------
➡ 157 | upgrade                  | 2024-07-23 17:31    | Upgrade   | 16
➡ 156 | remove focuswriter       | 2024-07-23 11:58    | Removed   | 5
➡ 155 | install focuswriter      | 2024-07-23 11:55    | Install   | 5
➡ 154 | update                   | 2024-07-23 11:20    | Upgrade   | 1
➡ 153 | autoremove               | 2024-07-23 09:37    | Removed   | 1
➡ 152 | remove mpv*              | 2024-07-23 09:35    | Removed   | 75
➡ 151 | remove sublime-text      | 2024-07-23 09:33    | Removed   | 1

A coluna Actions pode listar as ações listadas (às vezes indicada por uma letra):

  • Install (I): pelo menos um pacote foi instalado,
  • Update (U): pelo menos um pacote foi atualizado,
  • Reinstall (R): pelo menos um pacote foi reinstalado,
  • Downgrade (D): pelo menos um pacote foi rebaixado para uma versão anterior,
  • Erase (E): pelo menos um pacote foi desinstalado ou removido do sistema,
  • Obsoleting (O): pelo menos um pacote foi marcado como obsoleto.

A coluna “Altered” lista quantas ações foram executadas na transação, e pode usar os seguintes símbolos:

  • >: O banco de dados RPM foi alterado após a transação,
  • <: O banco de dados RPM foi alterado antes da transação,
  • *: A transação foi abortada antes do término,
  • #: A transação foi completada mas com status não zero,
  • E: A transação foi completada com sucesso, mas emitiu mensagem de erro.

Alguns argumentos adicionais podem ser usados para se encontrar uma transação específica.

# listar a última transação
$ dnf history list last
➡ ID | Command line | Date and time | Action(s) | Altered
--------------------------------------------------------------------------------
➡ 157 | upgrade | 2024-07-23 17:31 | Upgrade | 16

# listar a n-ésima transação, sendo n=0 a última
$ dnf history list last-5
➡ ID | Command line | Date and time | Action(s) | Altered
➡ --------------------------------------------------------------------------------
➡ 152 | remove mpv* | 2024-07-23 09:35 | Removed | 75

# listar a transação por ID
$ dnf history list 155
➡ ID | Command line | Date and time | Action(s) | Altered
➡ --------------------------------------------------------------------------------
➡ 155 | install focuswriter | 2024-07-23 11:55 | Install | 5

# listar a transações dentro de uma faixa de IDs
$ dnf history list 101..104
➡ ID | Command line | Date and time | Action(s) | Altered
➡ --------------------------------------------------------------------------------
➡ 104 | install docker-ce | 2024-06-18 12:56 | Install | 9 <
➡ 103 | remove cosmic* | 2024-06-18 12:05 | Removed | 35 >>
➡ 102 | remove xournal* | 2024-06-18 11:30 | Removed | 5
➡ 101 | update | 2024-06-18 11:06 | Upgrade | 2

# listar a transações em ordem invertida:
$ dnf history list --reverse
# A saida é uma lista de transações iniciando pela mais antiga

# listar as transações de instalações executadas pelo usuário:
$ dnf history userinstalled

A saída do comando pode ser filtrada de alguns modos:

# dnf history list | grep nome_do_pacote
# por exemplo
$ dnf history list | grep sublime
➡ ID | Command line | Date and time | Action(s) | Altered
➡ --------------------------------------------------------------------------------
➡ 151 | remove sublime-text | 2024-07-23 09:33 | Removed | 1
➡ 120 | install sublime-text | 2024-07-02 14:58 | Install | 1 <

Obtendo informações mais detalhadas: para obter detalhes sobre uma transação específica passe a opção info.A saída inclui:

  1. duração (em tempo) da transação
  2. qual usuário executou a transação
  3. código de retorno
  4. versão
  5. qual comando foi executado
  6. pacotes alterados

Por exemplo:

# para ver detalhes da transação com ID=120
$ dnf history info 120
➡ Transaction ID : 120
➡ Begin time : Tue 02 Jul 2024 02:58:05 PM -03
➡ Begin rpmdb : 562f2fb16f16bf6744987e7ab94ef265d7e988c68995eb4ca7bf3ea9e1705337
➡ End time : Tue 02 Jul 2024 02:58:07 PM -03 (2 seconds)
➡ End rpmdb : d4583d3d187fc29fd02eb6e36847eda474f1e6097e60885543adfdb936446035
➡ User : Nome Completo do Usuário <usuario>
➡ Return-Code : Success
➡ Releasever : 40
➡ Command Line : install sublime-text
➡ Comment :
➡ Packages Altered:
➡ Install sublime-text-4169-1.x86_64 @sublime-text

Para desfazer uma transação listada no histórico usamos undo ID. No exemplo, a transação com ID=156 consistiu na remoção do Focus Writer. Além disso é possível fazer uma operação de rollback, desfazendo todas as transações realizadas após a transação de ID=n, fazendo sudo dnf history rollback 12. Todas as transações ocorridas depois da transação com ID=n, portanto as que aparecem primeiro na lista, são desfeitas.

$ sudo dnf history undo 156
# após essa operação o Focus Writer volta a estar instalado no sistema.

# para desfazer todas as transações até a de ID=12
$ sudo dnf history rollback 12

# para desfazer um rollback, as mesmas transações podem ser reexecutadas,
$ sudo dnf history redo 12

Mesmo após a execução da operação de reverter uma transação e de rollback (que reverte várias transações) as entradas no histórico não são removidas, mas uma nova entrada é acrescentada para armazenar a operação de desfazer.

Plugins

A funcionalidade do dnf pode ser ampliada por meio de plugins. Existem plugins oficiais e plugins de outros desenvolvedores. A instalação é feita com o próprio dnf.

# para instalar os plugins core
$ dnf install dnf-plugins-core-NOME_PLUGIN

# para instalar os plugins extras
$ dnf install dnf-plugins-core-NOME_PLUGIN

São exemplos de plugins nos repositórios oficiais:

$ dnf install dnf-plugin-kickstart
$ dnf install dnf-plugin-rpmconf
$ dnf install dnf-plugin-showvars
$ dnf install dnf-plugin-snapper
$ dnf install dnf-plugin-torproxy
$ dnf install dnf-plugin-tracer

Mais informações em Core DNF plugins e Extras DNF plugins.

Atualizações de Sistema


O comando dnf pode ser usado para a atualização do sistema, diretamente ou com o plugin de atualização dnf-plugin-system-upgrade. A atualização pode ser feita diretamente com DNF puro (sem plugins) ou com o plugin de atualização do sistema DNF. Consulte o documento de atualização do sistema DNF para obter mais detalhes, em Upgrading Fedora.

Para fazer uma atualização (upgrade) de sistema, fazemos primeiro a atualização da tabela do dnf com refresh.

# atualiza tabela dnf
$ sudo dnf upgrade --refresh
# em seguida reinicialie o sistema

# instala systema: substitua n pelo número da versão
$ sudo dnf system-upgrade download --releasever=n

# para disparar o processo (o computador reiniciará imediatamente)
$ sudo dnf system-upgrade reboot

Algumas tarefas são possíveis após uma atualização. A maioria dos arquivos de configuração são armazenados na pasta /etc. Se algum desses arquivos poi alterado o RPM cria novos arquivos com extensão .rpmnew (os novos arquivos) ou .rpmsave (os arquivos antigos). Você pode examinar manualmente esses arquivos ou usar a ferramenta rpmconf para simplificar esse processo.

# instala a ferramenta
$ sudo dnf install rpmconf

# use a ferramenta
$ sudo rpmconf -a

Mais informações nos manuais em man rpmconf.

Bibliografia

Flet: Pagelet


Layout

Controle Pagelet

O controle Pagelet permite a inserção de uma área no aplicativo usando o layout do Material Design. Com ele podemos criar uma barra de cabeçalho, uma área de conteúdo, uma barra inferior e um menu que surge quando requerido.

Um exemplo simples de uso mostra essas áreas e seus respectivos nomes.

import flet as ft

def main(page: ft.Page):
    page.bgcolor=ft.colors.LIGHT_BLUE_ACCENT_700

    titulo=ft.Text("Barra superior da Pagelet", color="BLACK")
    conteudo=ft.Text("Conteúdo do corpo da Pagelet", color="BLACK")
    icone_inferior=ft.IconButton(icon=ft.icons.SEARCH, icon_color=ft.colors.BLUE)
    txt_inferior=ft.Text("Texto na barra inferior da Pagelet", color="BLACK")
    barra_inferior=ft.Row([icone_inferior, txt_inferior])

    menu_direita=[
        ft.NavigationDrawerDestination(icon=ft.icons.HOME, label="Em casa"),
        ft.NavigationDrawerDestination(icon=ft.icons.HOME_WORK, label="No trabalho")
    ]

    pagelet = ft.Pagelet(
        appbar=ft.AppBar(title=titulo, bgcolor=ft.colors.DEEP_ORANGE_200),
        content=conteudo,
        bgcolor=ft.colors.AMBER_50,
        bottom_app_bar=ft.BottomAppBar(
            bgcolor=ft.colors.CYAN_ACCENT_700,
            content=barra_inferior,
        ),
        end_drawer=ft.NavigationDrawer(controls=menu_direita, bgcolor=ft.colors.BLUE_ACCENT),
        width=400,
        height=300
    )

    page.add(pagelet)

ft.app(target=main)

O resultado da execução do código está mostrado na figura 1.

Figura 1: componentes de um controle Pagelet.

Respectivamente as propriedades de Pagelet correspondem: appbar, a barra superior, cabeçalho do aplicativo;
content, o conteúdo central; bottom_app_bar, a barra inferior; end_drawer uma barra ocultável, normalmente usada para inserir um menu de opções.

Propriedades de Pagelet

Propriedade Descrição
appbar controle exibido na parte superior do aplicativo. Geralmente contém texto mas pode conter outros controles
bgcolor cor de fundo da Pagelet.
bottom_appbar controle BottomAppBar a ser exibido na parte inferior da Pagelet. Se as propriedades bottom_appbar e navigation_bar estão preenchidas uma barra de navegação NavigationBar também será exibida.
bottom_sheet uma folha de fundo persistente para exibir conteúdo adicional. Qualquer controle pode ser usado como bottom_sheet.
content o controle filho da Pagelet, para a exibição de conteúdo. Ele é posicionado em cima, à esquerda, no espaço disponível entre as barras superior e inferior da Pagelet.
drawer um controle NavigationDrawer a ser exibido como painel deslizante a partir da borda superior da Pagelet.
end_drawer um controle NavigationDrawer a ser exibido como painel deslizante a partir da borda lateral da Pagelet.
floating_action_button um controle FloatingActionButton a ser exibido em cima do conteudo da Pagelet.
floating_action_button_location define a posição do controle FloatingActionButton. O valor é um enum com os valores:

CENTER_DOCKED END_TOP MINI_END_TOP
CENTER_FLOAT MINI_CENTER_DOCKED MINI_START_DOCKED
CENTER_TOP MINI_CENTER_FLOAT MINI_START_FLOAT
END_CONTAINED MINI_CENTER_TOP MINI_START_TOP
END_DOCKED MINI_END_DOCKED START_DOCKED
END_FLOAT (default) MINI_END_FLOAT START_FLOAT
navigation_bar é um controle NavigationBar a ser exibido no fundo da página. Se as propriedades bottom_appbar e navigation_bar estão preenchidas uma barra de navegação NavigationBar também será exibida.

Métodos de Pagelet

close_drawer() fecha a gaveta ativa.
close_end_drawer() fecha a gaveta ativa na posição à direita.
show_drawer(drawer: NavigationDialog) exibe a gaveta
show_end_drawer(drawer: NavigationDialog) exibe a gaveta na posição à direita

† NotaTraduzi drawer por gaveta.

Uso de Pagelet

import flet as ft

def main(page: ft.Page):
    page.bgcolor=ft.colors.BLUE_50

    citacao=[(
        'Há três métodos para ganhar sabedoria: primeiro, por reflexão, '
        'que é o mais nobre; segundo, por imitação, que é o mais fácil; '
        'e terceiro, por experiência, que é o mais amargo. Confúcio'
    ),
    (
        'Todos os seres vivos temem a violência. Todos temem a morte,'
        'todos amam a vida. Projete você mesmo em todas as criaturas.'
        'Quem você poderia ferir? Que mal você poderia fazer? Buda'
    )]

    def clicou_algo(e):
        mensagem.content.value=e.control.tooltip
        page.update()

    def selecionar_sabedoria(e):
        conteudo.content.value=citacao[e.control.selected_index]
        pagelet.end_drawer.open = False
        page.update()

    def abrir_drawer(e):
        pagelet.end_drawer.open = True
        pagelet.end_drawer.update()

    def fecha_barra_direita(e):
        mensagem.content.value="Menu da direita fechado"
        page.update()

    class Botao(ft.IconButton):
        def __init__(self, icon, tip):
            super().__init__()
            self.icon=icon
            self.icon_color=ft.colors.WHITE
            self.tooltip=tip
            self.on_click=clicou_algo

    conteudo = ft.Container(
        content=ft.Text(citacao[0], size=19, color="BLACK"),
        padding=ft.padding.all(20),
        bgcolor=ft.colors.BLUE_300,
        border=ft.border.all(3, ft.colors.BLUE_400),
        border_radius=ft.border_radius.all(10),
        expand=True,
    )
    barra_inferior = ft.BottomAppBar(
        bgcolor=ft.colors.BLUE,
        shape=ft.NotchShape.CIRCULAR,
        content=ft.Row(
            controls=[
                Botao(ft.icons.SUNNY, "Escolha seu destino"),
                Botao(ft.icons.AIRPLANEMODE_ACTIVE, "Compre sua passagem"),
                ft.Container(expand=True),
                Botao(ft.icons.HOTEL, "Reserve o seu hotel"),
                Botao(ft.icons.ROCKET, "Viaje para mais longe"),
            ]
        )
    )
    barra_direita=ft.NavigationDrawer(
        controls=[
            ft.NavigationDrawerDestination(icon=ft.icons.SELECT_ALL, label="Confúcio"),
            ft.NavigationDrawerDestination(icon=ft.icons.ADD_COMMENT, label="Buda"),
        ],
        bgcolor=ft.colors.LIGHT_BLUE_ACCENT_700,
        on_change=selecionar_sabedoria,
        on_dismiss=fecha_barra_direita,
    )

    pagelet = ft.Pagelet(
        appbar=ft.AppBar(
            title=ft.Text("Clique no menu para selecionar", color=ft.colors.BROWN),
            bgcolor=ft.colors.LIGHT_BLUE_ACCENT_100,
            toolbar_height=60,
        ),
        content=conteudo,
        bottom_app_bar=barra_inferior,
        end_drawer=barra_direita,
        floating_action_button=ft.FloatingActionButton("Menu", on_click=abrir_drawer),
        floating_action_button_location=ft.FloatingActionButtonLocation.CENTER_DOCKED,
        bgcolor=ft.colors.BLUE_600,
        width=400,
        height=300,
    )
    mensagem=ft.Container(
        ft.Text("Eventos aparecem aqui...", size=17, color=ft.colors.BLACK),
        padding=ft.padding.all(20),
        bgcolor=ft.colors.BROWN_200,
        border=ft.border.all(3, ft.colors.BROWN_300),
        border_radius=ft.border_radius.all(10),
        width=400,
        height=70,
    )
    page.add(
        ft.Container(
            content=ft.Column([pagelet,mensagem]),
            padding=ft.padding.all(20),
            bgcolor=ft.colors.BLUE_300,
            border=ft.border.all(3, ft.colors.BLUE_400),
            border_radius=ft.border_radius.all(10),
        ),
    )

ft.app(target=main)
Figura 2: Aplicativo após um clique sobre o ícone de sol e após seleção do menu|Buda. Segunda imagem com o menu aberto.

O resultado da execução do código está mostrado na figura 2.

Nesse código alguns componentes da Pagelet foram definidos separadamente e inseridas depois no controle. Isso foi feito para tornar o código mais fácil de ser entendido. A definição da classe Botao tem apenas o objetivo de diminuir as repetições de código na montagem da barra_inferior.

Os cliques nos ícones na barra inferior transfere o conteúdo dos tooltips para o container inferior no aplicativo. Cliques no menu deslizante, nos itens Confúcio e Buda alteram o conteúdo de texto do controle central através da função selecionar_sabedoria. A mensagem é selecionada usando e.control.selected_index, que é o índice do controle NavigationDrawerDestination clicado.

Flet: CupertinoListTile

Layout

Controle CupertinoListTile

CupertinoListTile é um controle similar ao ListTile mas seguindo a estilização do iOS. O controle possui a propriedade notched que, se marcada como False (default) renderiza o controle no modo padrão do iOS, ou, se marcada como True gera o controle na aparência de “Inset Grouped”, o mesmo usado em iOS Notes ou Reminders.

Ele exibe uma linha de altura fixa com controles internos, geralmente imagens, ícones, textos e menus. Ele admite ícones à direita ou à esquerda do título, podendo seus objetos interiores responder a certos eventos.

Um exemplo mínimo de código aparece listado abaixo, com a ilustração de seu resultado ao ser executado.

import flet as ft

def main(page: ft.Page):
    page.bgcolor="#ABE6DB"

    cuperLT_1=ft.CupertinoListTile(
        title=ft.Text("Patches de recuperação do Windows 12", color="BLACK"),
        subtitle=ft.Text("Equipe Mostarda", color="#333355"),
        additional_info=ft.Text("Completar tarefa até 21:45H", color="#333355"),
        leading=ft.Icon(name=ft.cupertino_icons.CHAT_BUBBLE),
        trailing=ft.Icon(name=ft.cupertino_icons.BELL_CIRCLE_FILL),
        bgcolor="#BDAB8D"
    )

    cuperLT_2= ft.CupertinoListTile(
        title=ft.Text("Patches de recuperação do iOS 17", color="BLACK"),
        subtitle=ft.Text("Equipe Catchup", color="#333355"),
        additional_info=ft.Text("Completar tarefa até 09:15H", color="#333355"),
        leading=ft.Icon(name=ft.cupertino_icons.CHAT_BUBBLE_2),
        trailing=ft.Icon(name=ft.cupertino_icons.BELL_CIRCLE, color="RED"),
        bgcolor="#AD9B7D"
    )

    page.add(cuperLT_1, cuperLT_2)

ft.app(target=main)
Figura 1: renderização do código de exemplo de CupertinoListTile

Propriedades de CupertinoListTile

Propriedade Descrição
additional_info controle a ser exibido à direita do título mas antes do controle ao final da linha. Com frequência é usado um texto, seguido por um ícone, embora isso não seja obrigatório.
bgcolor_activated cor de fundo do controle após ter sido clicado ou tocado.
leading A Control para exibir antes do title.
leading_size restringe largura e altura do controle. Default é 28.0.
leading_to_title espaço horizontal entre leading e title. Default é 16.0.
notched booleano. Se notched=True o controle será criado com a forma de um “Inset Grouped”, usado em aplicativos do iOS como Notes ou Reminders. Default é False.
padding espaçamento interno no controle CupertinoListTile, descrevendo distância entre os controles internos: title, subtitle, additional_info e trailing.* Veja a propriedade Container.padding para mais informações e possíveis valores.
subtitle conteúdo adicional exibido abaixo do título. Em geral é usado um controle de texto.
title usado para exibir o conteúdo principal no controle CupertinoListTile. Em geral é usado um controle de texto.
toggle_inputs booleano. Se toggle_inputs=True um clique na lista de altera o estado dos controles internos Radio, Checkbox ou Switch. Default é False.
trailing controle a ser exibido após o título. Normalmente é usado um controle de ícone.
url URL usada para localizar um recurso quando a lista recebe um clique. Se o evento on_click estiver registrado (se houver uma função a ele associada), o evento é disparado.
url_target como abrir URL no modo web. Pode ser:

  • _blank: o recurso é aberto em nova aba ou janela (default).
  • _self: o recurso é aberto na aba ou janela atual.

Evento de CupertinoListTile

on_click dispara quando o usuário clica ou toca na lista.

Apesar de não encontrar documentação a respeito, na minha experiência os nomes dos ícones podem ser idênticos aos nomes do flutter, mas escritas em maiúsculas. Exemplos são:

  • ft.cupertino_icons.FOLDER,
  • ft.cupertino_icons.FUNCTION,
  • ft.cupertino_icons.HAMMER

Um aplicativo que busca ícones cupertino pode ser encontrada em Cupertino Icons.

Exemplo de uso de CupertinoListTile

import flet as ft

def main(page: ft.Page):
    page.bgcolor="#ABE6DB"

    def click(e):
        if e.control.notched:
            clt2.data +=1
            txt = f"Esse controle foi clicado {clt2.data} vez{'es' if clt2.data>1 else ''}."
            clt2.subtitle=ft.Text(txt, color="#333355")
        else:
            clt1.data +=1
            txt = f"Esse controle foi clicado {clt1.data} vez{'es' if clt1.data>1 else ''}."
            clt1.subtitle=ft.Text(txt, color="#333355")

        titulo.value =f"Os controles foram clicados {clt1.data+clt2.data} vezes."
        page.update()

    clt1=ft.CupertinoListTile(
        title=ft.Text("Patches de recuperação do Windows 12", color="BLACK"),
        subtitle=ft.Text(
            "Esse controle ainda não foi clicado.",
            color="#333355"
        ),
        additional_info=ft.Text("Completar tarefa até 21:45H", color="#333355"),
        leading=ft.Icon(name=ft.cupertino_icons.CAR_DETAILED, color="BLACK"),
        trailing=ft.Icon(name=ft.cupertino_icons.WRENCH, color="RED", size=30),
        padding=ft.padding.only(15, 20, 30, 40),
        bgcolor="#BDAB8D",
        bgcolor_activated=ft.colors.AMBER_600,
        data=0,
        on_click=click,
    )
    clt2=ft.CupertinoListTile(
        title=ft.Text("Patches de recuperação do iOS 17", color="BLACK"),
        subtitle=ft.Text(
            "Esse controle ainda não foi clicado.",
            color="#333355"
        ),
        additional_info=ft.Text("Completar tarefa até 09:15H", color="#333355"),
        leading=ft.Icon(
            name=ft.cupertino_icons.CAR_DETAILED,
            color="BLUE",
            size=30,
        ),
        trailing=ft.Icon(
            name=ft.cupertino_icons.GAUGE,
            color="RED",
            size=30,
        ),
        padding=20,
        bgcolor="#CDBB9D",
        bgcolor_activated=ft.colors.AMBER_100,
        notched=True,
        data=0,
        on_click=click,
    )
    titulo=ft.Text(
        "Nenhum controle foi clicado até agora",
        color="#0b4b8f",
        size=30,
        weight=ft.FontWeight.BOLD,
    )
    ct=ft.Container(
        content=titulo,
        margin=5,
        padding=5,
        alignment=ft.alignment.center,
        bgcolor="#95d3c8",
        width=850,
        height=50,
        border_radius=10,
    )
    page.add(ct, clt1, clt2)

ft.app(target=main)
Figura 2: renderização do código de exemplo de CupertinoListTile depois de 2 cliques em cada tile. O primeiro controle está sob efeito do clique, exibindo a cor bgcolor_activated (amber).

Valem as observações: um container é inserido na cabeçalho, contendo um título que usa titulo=ft.Text(...). A variável titulo foi definida separadamente para estar disponível para alterações na função click().

As duas tiles foram definidas com a propriedade data em clt=ft.CupertinoListTile(..., data=0), usada para contar quantos cliques foram aplicados sobre o controle. Essa mesma propriedade é usada para construir a propriedade clt.subtitle, também atualizada a cada clique.

A função click() distingue entre os dois controles através da propriedade e.control.notched, que só é verdadeira no segundo controle. Desta forma não é necessário criar uma função para cada controle.

Flet: DataTable

Outro controle de Layout

Controle DataTable

DataTable é um controle usado para dispor dados em forma tabular. Ele é composto de um cabeçalho definido na propriedade columns que recebe uma lista de flet.DataColumn(), em geral contendo um controle de texto. Ele também pode conter ícones ou uma linha (Row) com ícones e texto. As colunas podem ser definidas como texto ou numéricas e exibir botão de seleção e ordenamento. As linhas de dados são constituídas de flet.DataRow(), por sua vez preenchidas com flet.DataCell()

Um exemplo curto de DataTable é mostrado abaixo.

import flet as ft

def main(page: ft.Page):

    def tit(texto):
        return ft.DataColumn(ft.Text(texto))

    def cel(texto):
        return ft.DataCell(ft.Text(texto))

    cabecalho=[tit("Título 1"), tit("Título 2"), tit("Título 3")]

    linha1=ft.DataRow(cells=[cel("A-1,1"),  cel("A-1,2"), cel("A-1,3")])
    linha2=ft.DataRow(cells=[cel("A-2,1"),  cel("A-2,2"), cel("A-2,3")])
    linha3=ft.DataRow(cells=[cel("A-3,1"),  cel("A-3,2"), cel("A-3,3")])

    linhas = [linha1, linha2, linha3]
    tabela=ft.DataTable(columns=cabecalho, rows=linhas)
    page.add(tabela)

ft.app(target=main)
Figura 1: Representação do código de DataTable e com seus controles internos.

Propriedades de DataTable

Algumas das propriedades e eventos listados aqui são mais associados a ações provocadas por toques em telas sensíveis ao toque (touch screen), embora muitas vezes possam ser executadas também com cliques de mouse. Exemplos são os eventos:

  • on_tap: recebimento de um toque,
  • on_long_press: recebimento de um toque longo,
  • on_tap_cancel: cancelamento do evento de toque, e
  • on_tap_down: arrastamento para baixo.
Propriedade Descrição
bgcolor cor de fundo da tabela
border definição da borda da tabela. O valor deve ser uma instância da classe flet.Border.
Veja a propriedade Container.border para mais informações.
border_radius raio dos cantos da borda.

Veja a propriedade Container.border para mais informações.

checkbox_horizontal_margin margin horizontal em torno das caixas de checagem (checkbox) se exibidas.
column_spacing margin horizontal entre o conteúdo de cada coluna.
columns lista de controle DataColumn descrevendo as colunas da tabela.
data_row_color cor de fundo das linhas de dados.

A cor de fundo pode ser ajustada para depender do estado do MaterialState, ou seja, com o estado da linha de selecionada, pressionada, percorrida pelo cursor, em foco, desabilitada ou não. [selected, pressed, hovered, focused, disabled or enabled].

A cor é renderizada como uma sobreposição à linha. Para garantir boa visibilidade é recomendado usar uma cor de fundo transparente.

Veja a propriedade Checkbox.fill_color para mais informações.

data_row_min_height altura mínima de cada linha (exceto a linha com o cabeçalho).
data_row_max_height altura máxima de cada linha (exceto a linha com o cabeçalho).
data_text_style estilo de texto para as linhas de dados. O valor deve ser uma instância da classe flet.TextStyle.
divider_thickness espessura do divisor (divider) entre as linhas da tabela. Esse valor deve ser ≥ 0, sendo o default 1.0
.
gradient gradiente de cor aplicado ao fundo da tabela.
Veja a propriedade Container.gradient para mais informações.
heading_row_color cor de fundo para a linha de cabeçalho.

A cor de fundo pode ser ajustada para depender do estado do MaterialState, ou seja, com o estado da linha de selecionada, pressionada, percorrida pelo cursor, em foco, desabilitada ou não. [selected, pressed, hovered, focused, disabled or enabled].

A cor é renderizada como uma sobreposição à linha. Para garantir boa visibilidade é recomendado usar uma cor de fundo transparente.

Veja a propriedade Checkbox.fill_color para mais informações.

heading_row_height altura da linha de cabeçalho.
heading_text_style estilo de texto para a linha de cabeçalho. O valor deve ser uma instância da classe flet.TextStyle.
horizontal_lines ajusta cor e largura das linhas horizontais separando as linhas de dados. O valor deve ser uma instância da classe flet.BorderSide.
horizontal_margin margem horizontal entre as bordas da tabela e o conteúdo da primeira e última célula de cada linha. Também é a margem entre
uma caixa de checagem (checkbox) (quando exibida) e o conteúdo da primeira coluna de dados.
rows uma lista de controles DataRow que definem as linhas da tabela.
show_bottom_border booleano, se a borda de fundo da tabela é exibida. Por default essa borda não é visível para que se aplique uma decoração sobre a borda geral da tabela.
show_checkbox_column booleano, se o controle deve conter caixas checkboxes para linhas selecionáveis.

Quando False nenhuma checkbox será apresentada. Quando True uma checkbox será apresentada no início de cada linha selecionável. Caso nenhuma linha seja marcada com DataRow.on_select_changed nenhuma checkbox será visível.

sort_ascending booleano, se a coluna indicada em sort_column_index (caso exista) será ordenada em ordem crescente. Caso contrário, se sort_ascending=False a ordem será descendente.
sort_column_index indica a chave primária de ordenação das linhas. Se especificada a colunas marcada será usada para a ordenação. O número deve indicar a coluna, em numeração iniciando em 0. Quando especificada a coluna mostrará um indicador de que o ordenamento é possível. Quando sort_column_index=None nenhum ordenamento será realizado.
vertical_lines cor e largura das linhas verticais entre colunas. O valor deve ser uma instância de classe flet.BorderSide.

Evento de DataTable

on_select_all dispara quando o usuário seleciona (ou desseleciona) todas as linhas usando o checkbox no cabeçalho.

Se on_select_all=None o evento de linhas DataRow.on_select_changed é disparado quando cada linha é selecionada. Para selecionar se uma linha é selecionável ou não use DataRow.on_select_changed. Esse evento só é relevante de existe alguma linha selecionável.

DataColumn

Objeto de configuração das colunas de uma DataTable. Todas as colunas a serem exibidas na tabela devem ter uma coluna de configuração.

Propriedades de DataColumn

label Conteúdo apresentado no cabeçalho. Em geral um controle de texto mas pode ser um ícone (tipicamente com size=18), ou uma coluna com ícone e texto.
numeric booleano, se essa coluna exibe valores numéricos. Colunas com valores numéricos são alinhadas à direita.
tooltip uma dica (tooltip) para a coluna. Pode conter uma descrição maior que o título ou mesmo título completo quando esse for abreviado na redução da largura da janela.

Evento de DataColumn

on_sort disparado quando o usuário requisita o ordenamento da tabela usando essa coluna. Se não for especificada uma função para esse evento a coluna será considerada não ordenável.

DataRow

Objeto de configuração das linhas e células de uma DataTable. Deve exitir uma linha de confighuração para cada linha da tabela. Os dados contidos na linhas são inseridos por meio de objetos flet.DataCells, reunidos em listas atribuidas à propriedade flet.DataRow(cells).

Propriedades de DataRow

cells recebe os dados a serem exibidos por meio de uma lista de controles DataCell. Devem existir células para todas as colunas (o número de células e colunas devem serem iguais).
color a cor da linha. Por default essa cor é transparente quando a linha não está selecionada, e recebem uma coloração acinzentada transparente quando selecioandas.

O cor exibida como resultado final depende do estado do MaterialState se a linha está selecionada, pressionada, percorrida pelo cursor, em foco, desabilitada ou não. [selected, pressed, hovered, focused, disabled or enabled].

A cor é renderizada como uma sobreposição à linha. Para garantir boa visibilidade é recomendado usar uma cor de fundo transparente.

Veja a propriedade Checkbox.fill_color para mais informações.

selected booleano, define se a linha está selecionda.

Se on_select_changed é não nula para alguma linha da tabela uma caixa checkbox é exibida no início de cada linha. Caso selected=True em uma linha sua checkbox aparecerá ticada e a linha destacada. Caso contrário a checkbox (se estiver presente) não será marcada.

Eventos de DataRow

on_long_press disparado quando a linha recebe um clique ou pressionamento longo.

Se uma célula DataCell na linha contém a definição de ação para os eventosDataCell.on_tap, DataCell.on_double_tap, DataCell.on_long_press, DataCell.on_tap_cancel ou DataCell.on_tap_down então essa definição sobrescreverá (terá prioridade sobre) o evento da linha se a ação for feita sobre a célula.

on_select_changed disparado quando o usuário seleciona ou desseleciona uma linha selecionável.

A linha é selecionável se essa propriedade não for nula. O estado da linha atual fica marcado na propriedade selected.

Se qualquer uma das linhas é selecionável o cabeçalho apresentará um checkbox que pode ser marcado para selecionar todas as linhas selecionáveis. Esse checkbox aparece marcado se todas as linhas estiverem selecionadas. Linhas subsequentes recebem um checkbox para sua seleção individual.

Se uma linha tem o evento on_select_changed=Null ela terá um checkbox desabilitado e será ignorada na determinação do estado de “todos os checkbox marcados”.

Se uma das células DataCell da linha tem o evento DataCell.on_tap definido, então essa definição sobrescreverá, naquela célula, os eventos definidos para a linha em geral.

DataCell

O objeto DataCell contém os dados para cada entrada em uma tabela DataTable. Uma lista de objetos
DataCell deve ser fornecida para cada linha, contendo o exato número de células que o de colunas.

Propriedades de DataCell

content o conteúdo com dados a serem apresentados em cada célula. Em geral contém um controle de texto um Dropdown.

Se não existem dados nessa propriedade o controle de texto será preenchido com um texto substituto (placeholder) e deve ser marcada a propriedade placeholder=True.

Esse controle só admite um filho. Para incluir layouts mais complexos é necessário inserir um widget tal como Row, Column ou Stack, que têm a propriedade controls que podem abrigar múltiplos filhos.

placeholder booleano, se o conteúdo é um texto substituto (placeholder).

Se placeholder=True o estilo default de texto para a célula é alterado para o estilo ajustado para placeholders.

show_edit_icon booleano, se o ícone de edição deve ser mostrado no final da célula. Essa propriedade não torna a célula editável. Esse comportamento deve ser definido no evento DataCell.on_tap.

Eventos de DataCell

on_double_tap disparado no duplo toque da célula.
on_long_press disparado no toque prolongado da célula.
on_tap disparado no toque da célula.
on_tap_cancel disparado se o usuário cancela um toque.
on_tap_down disparado se a célula é puxada para baixo.
Nota † válido para todos os eventos de DataCell Se não-nulo, o duplo toque da célula dispara esse evento. Se nulo o toque apenas seleciona a linha. Esse comportamento se extende a on_tap, on_long_press, on_tap_cancel e on_tap_down, caso DataRow.on_select_changed tenha sido definido.

Exemplo de uso de DataTable, DataRow e DataCell

Um exemplo um pouco mais elaborado de DataTable aparece no código abaixo.

import flet as ft

def main(page: ft.Page):
    page.bgcolor="#BAD1EA"

    def formatar_tabela(t):
        t.bgcolor="#0E64C5"
        t.border=ft.border.all(3, "#304864")
        t.border_radius=5
        t.data_row_color="#6697CE"
        t.show_checkbox_column=True
        t.divider_thickness=2
        t.column_spacing=100
        t.sort_column_index=2
        t.sort_ascending=True
        t.heading_row_color="#1565C2"
        t.heading_row_height=60
        t.show_checkbox_column=True
        t.show_bottom_border=True

    def montar_tabela(matriz):
        cor=["#759DCB", "#DABD90"]
        colunas=[]
        for col in matriz[0]:
            colunas.append(ft.DataColumn(ft.Text(col)))

        linhas=[]
        i=0
        for linha in matriz[1:]:
            i+=1
            celulas=[]
            for celula in linha:
                celulas.append(
                    ft.DataCell(
                        ft.Text(celula, color="#000000"),
                        show_edit_icon=(i%2==0)
                    )
                )
            linhas.append(ft.DataRow(cells=celulas, color=cor[i%2]))
            tabela=ft.DataTable(columns=colunas, rows=linhas)
            formatar_tabela(tabela)
        return tabela

    matriz = [
        ["Nome",      "Sobrenome",  "Idade", "Profissão", "Nacionalidade"],
        ["Cristovão", "Colombo",    "43",    "Navegador", "Itália"],
        ["Joaquim",   "José",       "31",    "Militar",   "Brasil"],
        ["Maria",     "Joaquina",   "23",    "Realeza",   "Portugal"],
        ["Albert",    "Einstein",   "57",    "Físico",    "Alemanha"],
        ["Charles",   "Chaplin",    "73",    "Ator",      "França"]
    ]

    tabela = montar_tabela(matriz)

    page.add(ft.Text("Famosos na História", size=20,color="BLACK"), tabela)

ft.app(target=main)

O código mostra um exemplo de transformação de formato de dados obtidos inicialmente como uma matriz formada por uma lista de listas. A primeira linha é usada para capturar os títulos de cada coluna, as demais são os dados exibidos no corpo da tabela. A vantagem desse método está em que a tabela pode ter um número arbitrário de colunas e de linhas.

Figura 2: Representação do código executado do exemplo de DataTable.
† Nota: A transformação de dados entre formatos diferentes de exposição é uma das funções básicas em TI atualmente. Exemplos são dados obtidos em paginas da WEB (em formato HTML), em textos PDF, em planilhas, em arquivos csv, que devem ser separados e usados em código, geralmente expostos em formato diferente daquele original.

A função montar_tabela(matriz) (que recebe matriz, uma lista de listas) percorre as linhas e colunas montando o objeto DataTable. Antes de retornar o objeto tabela montado ele a submete à formatar_tabela(t). Lembramos que objetos são passados por referência no Python, por default. Isso significa que a variável t recebida como parâmetro da função é apenas apenas uma referência para o mesmo objeto indicado pela variável tabela e as transformações feitas nas propriedades dentro da função são efetivas no objeto retornado por montar_tabela(matriz).

Flet: GridView e ResponsiveRow

Outros controles de Layout: GridView e ResponsiveRow

Controle GridView

GridView, assim como ListView, é um controle apropriado para apresentar listas longas (milhares de ítens). Esses controles devem ser escolhidos ao invés de Column ou Row para se obter um rolamento suave de conteúdo. O Navegador de Ícones (Icons Brower) do Flet é construído com um GridView.

Um exemplo curto do uso do controle é mostrado abaixo. Nesse código um array (uma lista) de 8 containeres com cores de fundo escolhidas entre os valores da lista, cores. Essa lista é passada em flet.GridView.controls = array.

import flet as ft

cores = {0:"black",  1:"#1D74FF", 2:"#C51709", 3:"#FFE000",
         4:"#00CB4F",5:"#A866DC", 6:"#FF6600", 7:"#145375"}

def main(page: ft.Page):
    array = []
    for i in range(0, 8):
        array.append(
            ft.Container(
                bgcolor=cores[i],
                border=ft.border.all(1, "BLACK"),
                border_radius=ft.border_radius.all(15),
            )
        )

    grid_imagens = ft.GridView(
        controls = array,
        expand=1,
        runs_count=15,
        max_extent=100,
        child_aspect_ratio=1.0,
        spacing=2,
        run_spacing=2,
    )
    page.add(grid_imagens)

ft.app(target=main)

A execução desse código resulta na tela mostrada na figura 1.

Figura 1: oito containeres de cores diferentes adicionados a um GridView

Propriedades de GridView

Propriedade Descrição
auto_scroll booleano, auto_scroll=True se a barra de rolamento se move automaticamente para a posição onde o último filho foi inserido. Essa propriedade deve ser False para que o método scroll_to() funcione.
child_aspect_ratio proporção entre dimensões vertical e horizontal (cross-axis e main-axis) de cada filho.
controls lista de controles a serem renderizados dentro do GridView.
horizontal booleano, horizontal=True para que a grade empilhe itens horizontalmente.
max_extent largura ou altura máxima de cada ítem na grade.
on_scroll_interval intervalo em milisegundos para o disparo do evento on_scroll. Default é 10.
padding espaço interno de separação entre os filhos e a grade.

Veja a propriedade Container.padding para maiores informações.

reverse booleano, reverse=True faz com que o rolamento se dê invertido, de baixo para cima. Default é False.
run_spacing espaçamento entre cada filho em pixeis, ao longo do eixo vertical.
runs_count o número de controles filhos a serem apresentados na vertical.
spacing espaçamento entre cada filho em pixeis, ao longo do eixo horizontal.

Método de GridView

scroll_to(offset, delta, key, duration, curve) move a barra de rolamento para a posição definida, em termos absoluto, relativo ou salto para uma chave especificada.

Veja o método Column.scroll_to() para maiores informações.

Evento de GridView

on_scroll Dispara quando a posição de rolamento é alterada pelo usuário.Veja o evento Column.on_scroll para maiores informações.

Exemplo de uso de GridView

No exemplo abaixo usamos o controle flet.Image(src, fit, repeat, border_radius) que ainda não descrevemos nessas notas. Seu objetivo é o de inserir imagens, no caso atual lidas no repositório Image Gallery localizada em picsum.photos.

import flet as ft

def main(page: ft.Page):
    page.title = "GridView Example"
    page.theme_mode = ft.ThemeMode.DARK
    page.padding = 10
    
    txt = ft.Text("Controle GridView com Imagens", size = 28)
    titulo=ft.Container(txt, data=0)
    
    def incrementa(e):
        titulo.data+=12
        carregar_imagens()

    bt_mais=ft.ElevatedButton(
        "Carregar mais imagens",
        icon=ft.icons.BEACH_ACCESS,
        color="WHITE",
        bgcolor="#6E432B", width=300, height=40,
        on_click=incrementa
    )

    grid_imagens = ft.GridView(
        expand=1,
        runs_count=15,
        max_extent=150,
        child_aspect_ratio=1.0,
        spacing=5,
        run_spacing=5,
    )

    def carregar_imagens():
        grid_imagens.controls=[]
        for i in range(titulo.data, titulo.data+12):
            grid_imagens.controls.append(
                ft.Image(
                    src=f"https://picsum.photos/150/150?{i}",
                    fit=ft.ImageFit.NONE,
                    repeat=ft.ImageRepeat.NO_REPEAT,
                    border_radius=ft.border_radius.all(10),
                )
            )
        page.update()

    carregar_imagens()
    page.add(ft.Column([ft.Row([titulo,  bt_mais]), grid_imagens]))

ft.app(target=main)
Figura 2: Execução do código de exemplo de GridView

Nesse código o controle flet.GridView é criado com o array de controles vazio e depois populado pela função carregar_imagens() que insere 12 imagens na propriedade GridView.controls. A propriedade titulo.data armazena a posição das imagens baixadas e é incrementada de 12 a cada disparo do botão bt_mais.

Controle ResponsiveRow

ResponsiveRow traz para o Flet o mesmo conceito de layout de grade usado no framework Bootstrap. Com ele se pode alinhar os controles filhos em colunas virtuais que podem ser redimensionadas. Por padrão uma grade possui 12 colunas, número que pode ser modificado na propriedade ResponsiveRow.columns.

Cada coluna inserida em um ResponsiveRow possui a propriedade col especificando quantas colunas cada controle deve preencher, de modo análogo ao uso da propriedade expand. Por exemplo, para criar um layout que contém duas colunas abrangendo 6 colunas virtuais cada usamos:

import flet as ft

ft.ResponsiveRow([
    ft.Column(col=6, controls=[ft.Text("Coluna 1")]),
    ft.Column(col=6, controls=[ft.Text("Coluna 2")])
])

O controle é dito “responsivo” porque pode ajustar o tamanho de seus filhos à geometrias variáveis de telas, que pode ser a página, a janela, etc. No exemplo acima a propriedade col é um número constante, significando que o filho ocupará 6 colunas para qualquer tamanho de tela. Se a propriedade col de um filho não for especificada ele terá o número máximo de colunas.

A propriedade col pode ser configurada para ter um valor diferente para “pontos de interrupção” (breakpoints) específicos, de acordo com o tamanho da tela. Os pontos de interrupção, como intervalos de dimensão, recebem nomes:

Por exemplo, no código abaixo o conteúdo é colapsado em apenas uma coluna em uma tela pequena (como a de um telefone celular) e assume 2 colunas em telas grandes:

import flet as ft
ft.ResponsiveRow([
    ft.Column(col={"sm": 6}, controls=[ft.Text("Column 1")]),
    ft.Column(col={"sm": 6}, controls=[ft.Text("Column 2")])
])

Propriedades de ResponsiveRow

Propriedade Descrição
alignment alinhamento horizontal dos controles filhos. Por exemplo, MainAxisAlignment.START (que é o default) posiciona os filhos à esquerda da linha.

Os valores possíveis da propriedade estão no enum MainAxisAlignment com os valores:

  • START (default)
  • END
  • CENTER
  • SPACE_BETWEEN
  • SPACE_AROUND
  • SPACE_EVENLY
columns o número de colunas virtuais a usar no layout dos filhos. Default é columns=12.
controls uma lista de controles a serem exibidos dentro do ResponsiveRow.
run_spacing espaçamento entre as linhas quando o conteúdo está quebrado em múltiplas linhas. Default run_spacing=10.
spacing espaçamento entre os controles em uma linha. Default é spacing=10 (pixeis virtuais). Esse espaçamento só é aplicado quando o alinhamento (alignment) é start, end ou center.
vertical_alignment como os controles filhos são posicionados verticalmente.

Os valores possíveis da propriedade estão no enum CrossAxisAlignment com os valores:

  • START (default)
  • CENTER
  • END
  • STRETCH
  • BASELINE

Exemplo de uso de ResponsiveRow

No exemplo abaixo tanto as funções coluna() como linha() retornam containeres com um texto. Esses controles preenchem as posições de coluna do ResponsiveRow, a primeira delas especificando os pontos de quebra em
col={"sm": 6, "md": 4, "xl": 2}, a segunda em col={"md": 4}. Observe que Container não tem a propriedade col, exceto quando dentro de uma “linha responsiva”.

import flet as ft

def main(page: ft.Page):

    def coluna(texto, cor):
        # retorna um Container com um texto
        return ft.Container(
            ft.Text(texto),
            padding=15,
            bgcolor=cor,
            col={"sm": 6, "md": 4, "xl": 2}
        )

    def linha(texto, cor):
        # retorna um Container com um texto
        return ft.Container(
            ft.Text(texto, size=18),
            width=200, height=60, padding=15,
            border_radius=10,
            bgcolor=cor,
            col={"md": 4}
        )

    def page_resize(e):
        info.value = f"{page.width} px"
        info.update()

    page.on_resize = page_resize
    info = ft.Text(size=28)

    page.overlay.append(
        ft.Container(
            info,
            width=200, height=70, padding=10, border_radius=15,
            bottom=50, right=50, bgcolor="#acdeff",
        )
    )

    page.add(
        ft.ResponsiveRow(
            [
                coluna("Coluna 1", ft.colors.YELLOW),
                coluna("Coluna 2", ft.colors.GREEN),
                coluna("Coluna 3", ft.colors.BLUE),
                coluna("Coluna 4", ft.colors.RED),
            ],
        ),
        ft.ResponsiveRow(
            [
                linha("texto 1", ft.colors.BLUE_50),
                linha("texto 2", ft.colors.RED_50),
                linha("texto 3", ft.colors.GREEN_50),
            ],
            run_spacing={"xs": 10},
        ),
    )
    page_resize(None)

ft.app(target=main)

A propriedade de largura da página page.width é exibida dentro de um Container inserido na camada page.overlay para flutuar em cima dos controles da página. Esse controle está definido em espaçamento fixo bottom=50 e right=50, podendo se sobrepor aos controles da camada abaixo. A atualização é disparada no evento page.on_resize.

Observe que a atualização realizada no redimensionamento altera info.value e a renderização é exposta na página com info.update(). O mesmo efeito seria obtido com page.update() pois esse método cuida de atualizar apenas as partes modificadas da página.

O resultado da execução do código aparece nas figuras 3 e 4.

Figura 3: estado inicial do aplicativo com tela de largura superior a 1200 px.
Figura 4: estado do aplicativo com tela redimensionada para 567 px, depois 288px.

Flet: ListTile e ListView

Outros controles de Layout: ListTile e ListView

Controle ListTile

ListTile é um controle que exibe uma linha de altura fixa que pode conter outros controles, geralmente imagens, ícones, textos, botões e menus popups. Ele admite ícones à direita ou à esquerda do título, podendo seus objetos interiores responder a certos eventos.

Um exemplo mínimo de código aparece listado abaixo, com a ilustração de seu resultado ao ser executado. Um menu popup foi incluído porque esses controles são usados frequentemente juntos com ListTile.

import flet as ft

def main(page):

    lTile1=ft.ListTile(title=ft.Text("Você gosta de animais?", size=18))

    pop_menu=ft.PopupMenuButton(
        icon=ft.icons.MORE_VERT,
        items=[ft.PopupMenuItem(text="Jacaré"), ft.PopupMenuItem(text="Leão")]
    )
    lTile2=ft.ListTile(
        title=ft.Text("Qual é o seu animal favorito?"),
        subtitle=ft.Text("Selecione no menu popup"),
        trailing=pop_menu
    )
    page.add(ft.Card(ft.Column([lTile1, lTile2]), width=300))

ft.app(target=main)

A figura 1 mostra o resultado desse código, renderizado no desktop.

Figura 1:  (a) estado inicial do aplicativo; (b) estado após um clique no controle PopupMenuItem

Propriedades de ListTile

Propriedade Descrição
autofocus booleano, autofocus=True se o controle receberá o foco inicial (o cursor está sobre o controle). Se mais de um controle tiver esse ajuste o primeiro deles (na ordem da página) receberá o foco.
content_padding Espaçamento interno (padding) do controle. Separação entre os controles internos: leading, title, subtitle, e trailing. O default é padding.symmetric(horizontal=16). Veja Container.padding para maiores informações.
dense define se o controle tem exibição densa, com menor altura e caracteres compactos.
is_three_line booleano, se is_three_line=True o controle ListTile deve exibir 3 linhas de texto, e o subtitulo nao pode ser Null, uma vez que ele devera conter as linhas 2 e 3 de texto.Se is_three_line=False o ListTile deve conter uma linhas se subtitle=Null e duas linhas caso contrário.

Se um controle flet.Text for usado para title ou subtitle é possível estabelecer um limite máximo para o número de linhas com Text.max_lines.

leading controle a ser exibido antes (à esquerda) do título.
selected booleano; se selected=True para um tile então os ícones e textos terão a mesma cor. Por default a cor dos elementos em selected é a cor primária do tema.
subtitle Linhas (ou linhas de texto) a serem exibidas abaixo do título. Em geral um flet.Text.Se is_three_line=False o subtítulo não quebrará em linhas longas. Caso contrário deve ser configurado o número máximo de linhas (por exemplo com Text.max_lines).
title controle a ser exibido como conteúdo principal ou título, em geral um flet.Text. Linhas de título não são quebradas e um limite para essa linha única pode ser ajustado com Text.max_lines.
toggle_inputs booleano; define se um clique na ListTile deve abrir (ou fechar) uma caixa de Radio, Checkbox ou Switch. O default é toggle_inputs=False.
trailing controle a ser exibido após (à direita ) do título. Em geral é usado um controle de ícone.
url a URL a ser aberta quando o ListTile é clicado. O evento on_click, se definido, será acionado.
url_target a URL a ser aberta, no modo Web.

  • _blank: (default) – nova janela ou guia.
  • _self: na atual janela aberta.

Eventos de ListTile

Evento Descrição
on_click dispara com um clique (ou toque em touch screens) no controle ListTile.
on_long_press dispara com um clique longo (ou toque longo em touch screens) no controle ListTile

Exemplo de uso de ListTile

import flet as ft

def main(page):
    page.title = "Exemplo de uso do Widget ListTile"
    page.horizontal_alignment=ft.CrossAxisAlignment.CENTER

    def clk(e):
        lt1.data +=1
        lt1.title.value=f"Você clicou {lt1.data} {'vez' if lt1.data==1 else 'vezes'} no título"
        page.update()

    def clicado(e):
        ct_info.content.value=f"Foi clicado o item: {e.control.text}"
        page.update()

    class PI(ft.PopupMenuItem):
        def __init__(self, texto):
            super().__init__()
            self.text=texto
            self.on_click=clicado

    lt1=ft.ListTile(title=ft.Text("Linha única no título", size=18), data=0, on_click=clk)
    lt2=ft.ListTile(title=ft.Text("Uma linha densa nessa listTile"), dense=True)
    lt3=ft.ListTile(
        leading=ft.Icon(ft.icons.DELETE_FOREVER_ROUNDED),
        title=ft.Text("Essa linha abre selecionada"),
        selected=True
    )
    lt4=ft.ListTile(
        leading=ft.ElevatedButton(text="X", bgcolor="#cc8866", on_click=clicado),
        title=ft.Text("Linha com controle anterior"),
        subtitle=ft.Text("Clique para apagar caixa de info")
    )
    lt5=ft.ListTile(
        title=ft.Text("Linha com controle posterior"),
        trailing=ft.PopupMenuButton(
            icon=ft.icons.MORE_VERT,
            items=[PI("Popup 1.1"), PI("Popup  1.2")]
        )
    )
    lt6=ft.ListTile(
        leading=ft.Icon(ft.icons.BATTERY_1_BAR),
        title=ft.Text("Linha com controles anterior e posterior"),
        trailing=ft.PopupMenuButton(
            icon=ft.icons.MORE_VERT,
            items=[PI("Popup 2.1"), PI("Popup  2.2")]
        )
    )
    lt7=ft.ListTile(
        leading=ft.Icon(ft.icons.MENU_BOOK),
        title=ft.Text("Título e subtítulo com controles anterior e posterior"),
        subtitle=ft.Text("Aqui vai um subtítulo"),
        trailing=ft.PopupMenuButton(
            icon=ft.icons.MORE_VERT,
            items=[PI("Popup 3.1"), PI("Popup  3.2")]
        )
    )
    ct_info=ft.Container(
        content=ft.Text("Aqui você verá os itens clicados!"),
        width=500, height=40, bgcolor=ft.colors.AMBER_100,
        alignment=ft.alignment.center,
        border=ft.border.all(1, ft.colors.BLUE_100),
        border_radius = ft.border_radius.all(10)
    )
    titulo=ft.Tooltip(
        message="Exemplo de tooltip",
        content=ft.Text("Informações de cliques:"),
        padding=10
    )

    page.add(
        ft.Container(
            content=ft.Column(
                [lt1, lt2, lt3, lt4, lt5, lt6, lt7, titulo, ct_info],
                spacing=20
            ),
            width=500,
            bgcolor=ft.colors.BLUE_50,
            border=ft.border.all(1, ft.colors.BLUE_500),
            border_radius = ft.border_radius.all(15),
            padding=ft.padding.all(15),
            shadow=ft.BoxShadow(
                blur_radius=15,
                color=ft.colors.BLUE_GREY_500,
                offset=ft.Offset(0, 0),
                blur_style=ft.ShadowBlurStyle.OUTER
            )
        )
    )

ft.app(target=main)
Figura 2: (a) estado inicial do aplicativo; (b) estado após 3 cliques no título e um clique no popup 2.2.

A figura 2 exibe o código em execução.

Um clique no controle lt1 aciona a função clk(e) que incrementa o valor de lt1.data e o exibe em lt1.title.value.


Os controles lt5, lt6 e lt7 recebem em ListTile.trailing um menu popup PopupMenuButton. Cada um deles contém PopupMenuButton.items=[PI("Texto 1"), PI("Texto 2")], onde PI herda da classe PI(ft.PopupMenuItem). Isso cria menus popups que abrem ao serem clicados o ícone à direita, icon=ft.icons.MORE_VERT (três pontos verticais). Todos esses ítens respondem ao evento click abrindo a função clicado. Essa função recebe exibe o texto do controle que enviou o evento em ct_info.content.value. O texto é capturado em e.control.text.

Observe que ct_info.content.value é o texto dentro do container ct_info, um texto e não um objeto flet.Text. O nome do controle clicado está em e.control.text.

Como exemplo foi inserido um Tooltip (ferramenta de dicas) no controle titulo, responsável por exibir uma messagem quando o cursor percorre a área do controle.

Controle ListView

Ainda que seja possível adicionar listas longas nos controles Column e Row esses controles serão pouco eficazes e lentos nessa exibição, principalmente porque adicionam seu conteúdo de uma só vez, mesmo que não estejam visíveis. A solução para essa questão consiste em usar os controles ListView e GridView.

ListView é um controle especialmente útil para a exibição de conteúdo longo. Ele insere controle sucessivamente na direção estabelecida para o rolamento, permitindo percorrer sua extensão automaticamente (com o autoscroll). Controles podem ser adicionados na vertical (o default) ou na horizontal.

Apesar de que ListView insere gradualmente seu conteúdo e renderiza seus filhos com eficiência, o rolamento pode ser melhorado ajustando uma altura fixa para todos os ítens filhos no rolamento vertical, ou largura fixa no rolamento horizontal. Para fazer isso podemos determinar uma altura absoluta na propriedade item_extent ou, alternativamente marcando a propriedade first_item_prototype = True no primeiro filho, o que obriga todos os demais a ter a mesma extensão.

O seguinte exemplo mostra como se pode renderizar rapidamente 5000 linhas de texto com esse controle.

import flet as ft

def main(page: ft.Page):
    lv = ft.ListView(expand=True, spacing=10)
    for i in range(5000):
        lv.controls.append(ft.Text(f"Linha {i+1}"))
    page.add(lv)

ft.app(target=main)


Nesse caso o rolamento não é automático. Para que a tela role junto com inserção de controle modificamos a definição do controle usando:

lv = ft.ListView(expand=True, auto_scroll=True, spacing=10)

.
Note que foi usado expand=True no construtor de ListView. Para que essa propriedade funcione corretamente temos que ajustar a altura (height), ou largura (width) no rolamento horizontal. Podemos marcar ListView(height=300, spacing=10), por exemplo. No exemplo acima forçamos o controle a usar todo o espaço disponível com ListView.expand=True.

Propriedades de ListView

Propriedade Descrição
auto_scroll booleano, auto_scroll=True se a barra de rolamento (scrollbar) deve ser mover automaticamente até o final do último filho acrescentado. Para que o método scroll_to() funcione esse controle deve ser ajustado como auto_scroll=False.
controls A lista de controles a serem exibidos dentro do ListView.
divider_thickness Se esse valor for maior que zero um controle Divider é usado no espaçamento entre os ítens. O default é divider_thickness=0.
first_item_prototype booleano, first_item_prototype=True para que as dimensões do primeiro filho devem ser usadas como “protótipo” para os demais itens inseridos. Nesse caso todos os controles terão a mesma altura ou largura que o primeiro filho. O default é False.
horizontal booleano, horizontal=True para que os itens sejam dispostos horizontalmente.
item_extent uma altura (ou largura, no rolamento horizontal) fixa para que um item tenha renderização otimizada.
on_scroll_interval passo ou salto em milisegundos para o disparo do evento on_scroll. Default é on_scroll_interval=10.
padding espaçamento interno para posicionamento dos fihos. Veja a propriedade Container.padding.
reverse booleano, define a direção do rolamento. Default é reverse=False.
spacing altura do controle Divider entre itens do ListView. Espaçamento zero é usado se esse valor não é especificado.

Método de ListView

scroll_to(offset, delta, key, duration, curve) Move a posição da barra de rolagem (e, portanto, da posição visível dentro do controle) para uma marca absoluta, um salto relativo ou para uma chave especificada.

Veja Column.scroll_to() para detalhes.

Evento de ListView

on_scroll dispara quando a posição da barra de rolamento é alterada pelo usuário.

Veja Column.scroll_to() para detalhes.

Exemplo de uso de ListView

O exemplo mostra a montagem de conteúdo dentro de um ListView, com intervalo de tempo de .1 segundoprovocado por sleep(.1).

from time import sleep
import flet as ft

def main(page: ft.Page):
    page.title = "Use ListView com auto-rolagem"

    arr=[ft.Text("<h1>Título do conteúdo da Web</h1> ", size=20, bgcolor="#aabbff")]
    lv = ft.ListView(
        controls=[ft.Text("<h1>Título do conteúdo da Web</h1> ", size=20)],
        expand=True,
        spacing=1,
        padding=40,
        auto_scroll=True,
        divider_thickness=1
    )

    ct=ft.Container(
        content=lv,
        width=500, height=500, bgcolor=ft.colors.AMBER_50,
        alignment=ft.alignment.center,
        border=ft.border.all(1, ft.colors.BLUE_100),
        border_radius = ft.border_radius.all(10)
    )
    page.add(ct)

    def escreve_texto(texto):
        sleep(.1)
        lv.controls.append(ft.Text(texto))
        page.update()

    escreve_texto("<p>Podemos construir uma lista não-ordenada:</p>")
    escreve_texto("<ul>")

    for i in range(0, 3):
        escreve_texto(f"    <li>Exibindo conteúdo da linha {i}</li>")
    escreve_texto("</ul>")
    escreve_texto("<p>Podemos construir uma tabela</p>")
    escreve_texto("<table>")
    escreve_texto("<tr><th>Palavra</th><th>Número</th>")
    escreve_texto("<th>Palavra</th><th>Número</th>")
    escreve_texto("<th>Palavra</th><th>Número</th></tr>")

    for i in range(0, 9, 3):
        escreve_texto("<tr>")
        escreve_texto(f"    <td>palavra</td><td>{i}</td>")
        escreve_texto(f"    <td>palavra</td><td>{i+1}</td>")
        escreve_texto(f"    <td>palavra</td><td>{i+2}</td>")
        escreve_texto("</tr>")
    escreve_texto("</table>")

ft.app(target=main)
Figura 3: estado final do tivo após execução, com rolagem ajustada para o início.

O controle ListView é criado e inserido na página com apenas um controle (flet.Text). Controles filhos são adicionados depois por meio da função escreve_texto(texto). A figura 3 mostra o aplicativo resultante da execução desse código.

O texto gerado é parte de uma página HTML, algo usado apenas como uma demonstração da montagem gradual no controle, sem um significado especial pois esse controle não pode renderizar texto com marcação HTML. O texto gerado seria representado em um navegador como mostrado na figura 4, incluído aqui apenas por completeza.

Figura 4: texto html renderizado.

Flet: ExpansionTile

Outro controle de Layout

Controle ExpansionTile

Controle ExpansionTile é um controle que exibe uma linha contendo, por default, um ícone com setas, um título, subtítulo e admitindo como conteúdo um array de controles como filhos que serão exibidos como conteúdo. Um clique nas setas ou cabeçalho da linha provoca a a expansão ou colapso do controle. Quando colapsado o titulo e subtítulo continuam visíveis.

Um exemplo mínimo de código aparece listado abaixo, com a ilustração de seu resultado ao ser executado.

import flet as ft

class ETile(ft.ExpansionTile):
    def __init__(self, titulo, subtitulo, arrCtl):
        super().__init__()
        self.title=ft.Text(titulo, size=22, weight=ft.FontWeight.BOLD)
        self.subtitle=ft.Text(subtitulo)
        self.controls=arrCtl

def main(page: ft.Page):

    exp_tile1 = ETile("Título do primeiro ExpansionTile", "Primeiro subtítulo", [ft.Text("Conteúdo interno 1")])
    exp_tile2 = ETile("Título do segundo ExpansionTile", "Segundo subtítulo", [ft.Text("Conteúdo interno 2")])
    arr=[ft.Text("Conteúdo interno 3"), ft.Text("Conteúdo interno 4"), ft.Text("Conteúdo interno 5")]
    exp_tile3 = ETile("Título do terceiro ExpansionTile", "Terceiro subtítulo", arr)
    page.add(exp_tile1, exp_tile2, exp_tile3)

ft.app(target=main)
Figura 1: Estado inicial; Primeiro e segundo tiles expandidos; apenas terceiro tile expandido.

Propriedades de ExpansionTile

ExpansionTile tem as seguintes propriedades:

Propriedade Descrição
affinity usado para forçar a exibição da ícone de expansão e contração (setas por deafault) sejam colocados à esquerda (no início) ou direita (no final) do controle. O valor de affinity pode ser dado por um enum TileAffinity que tem os seguintes valores:

  • LEADING, no início do controle,
  • TRAILING, no final do controle,
  • PLATFORM (default), seguindo as convenções da plataforma.
bgcolor cor de fundo do controle, exibida por trás da sublista, quando exibida.
controls uma lista de controles que serão exibidos quando o ExpansionTile está expandido. Um controle ListTile é normalmente usado como filho, embora outros controles possam ser inseridos.
controls_padding define os espaçamentos (padding) em torno dos controles. Mais informações em Container.padding.
clip_behavior define o comportamento de recorte (clipping) doo contéudo. O valor da propriedade é um enum ClipBehavior com os seguintes valores:

  • NONE (default),
  • ANTI_ALIAS.
  • ANTI_ALIAS_WITH_SAVE_LAYER.
  • HARD_EDGE.
collapsed_bgcolor cor de fundo do controle ExpansionTile quando a sublista está colapsada.
collapsed_icon_color cor do ícone de expansão do controle quando a sublista está colapsada.
collapsed_shape o formato da borda do controle quando a sublista está colapsada. O valor é uma instância do tipo OutlinedBorder do qual se pode herdar:

  • StadiumBorder
  • RoundedRectangleBorder
  • radius: raio da borda, uma instância de BorderRadius ou um número.
  • CircleBorder
  • BeveledRectangleBorder
  • radius: raio da borda, uma instância de BorderRadius ou um número.
  • ContinuousRectangleBorder
  • radius: raio da borda, uma instância de BorderRadius ou um número.
collapsed_text_color cor do título do controle quando a sublista está colapsada.
expanded_alignment define a alinhamento dos filhos, que são distribuídos em uma coluna, quando o controle está expandido.
Veja o propriedade Mais informações em Container.alignment para maiores informações.
expanded_cross_axis_alignment Define o alinhamento de cada controle filho quando o controle está expandido.
O valor da propriedade é um enum CrossAxisAlignment com os valores:

  • START
  • CENTER (default)
  • END
  • STRETCH
  • BASELINE
icon_color cor do ícone de expansão quando a sublista está expandida.
initially_expanded booleno, define se o controle é inicializado expandido ou colapsado. Default = False.
leading um controle a ser exibido antes (à esquerda) do título.
maintain_state booleano, define se o estado do controle é mantido após ser expandido ou colapsado. Default = False.
shape formato da borda do controle quando a sublista está expandida. O valor é uma instância do tipo OutlinedBorder da qual herdam:

  • StadiumBorder
  • RoundedRectangleBorder
  • radius: raio da borda, uma instância de BorderRadius ou um número.
  • CircleBorder
  • BeveledRectangleBorder
  • radius: raio da borda, uma instância de BorderRadius ou um número.
  • ContinuousRectangleBorder
  • radius: raio da borda, uma instância de BorderRadius ou um número.
subtitle conteúdo a ser exibido abaixo do título. Pode ser um widget de texto (flet.Text).
text_color cor de texto do título quando a sublista está expandida.
tile_padding Espaçamento interno (padding) do controle. O valor default é padding.symmetric(horizontal=16.0).
Veja Container.padding.
title controle a ser exibido como título principal. Pode ser um widget de texto (flet.Text).
trailing controle a ser exibido depois (à direita) do título. Pode ser um controle de ícone

Eventos de ExpansionTile

ExpansionTile tem os seguintes eventos:

Evento Descrição
on_change dispara quando o usuário clica (ou toca) no controle.
on_long_press dispara quando o usuário clica (ou toca) demoradamente sobre o controle.

Exemplo de uso de ExpansionTile

O código abaixo faz uma demonstração do controle ExpansionTile. Abaixo segue uma imagem, figura 2, mostrando o resultado da execução desse código.

import flet as ft
import random

cor=["#DA2222", "#2CDA22", "#2275DA", "#5622DA", "#C99032", "#A7D7DE"]
bgcor=["#E7DADA", "#DCE8DB", "#CFE3FA", "#D2CEDB", "#F9E9CE", "#D2E9EC"]

def main(page: ft.Page):
    page.spacing = 0
    page.theme_mode = ft.ThemeMode.LIGHT # DARK
    page.padding = ft.padding.all(5)
    page.bgcolor="#7CC2D1"

    def mostrar_snack(texto):
       return ft.SnackBar(
           ft.Row(
               [ft.ElevatedButton("Mudar Cores", bgcolor="WHITE", on_click=mudar_cor),
               ft.Text(texto,size=18)]
            ), duration=5000 )

    def apagar(e):
        if e.control.data==1:
            page.controls.remove(etile1)
        elif e.control.data==2:
            page.controls.remove(etile2)
        else:
            page.controls.remove(etile3)
        page.show_snack_bar(mostrar_snack(f"Apagado o controle {e.control.data}"))
        page.update()

    def mudar_cor(e):
        i = random.randrange(0,6)
        etile1.bgcolor=bgcor[i]
        etile1.collapsed_bgcolor=bgcor[(i+1)%6]
        etile2.bgcolor=bgcor[(i+2)%6]
        etile2.collapsed_bgcolor=bgcor[(i+3)%6]
        etile3.bgcolor=bgcor[(i+4)%6]
        etile3.collapsed_bgcolor=bgcor[(i+5)%6]
        page.update()

    def alterou_etile(e):
        qual = f"Barra com título '{e.control._ExpansionTile__title.value}' foi " #a.__dict__
        qual += f"{'expandida' if e.data=='true' else 'colapsada'}"
        page.show_snack_bar(mostrar_snack(qual))

    class ETile(ft.ExpansionTile):
        def __init__(self, titulo, subtitulo, arrCtl,cor1,id):
            super().__init__()
            self.id = id
            self.title=ft.Text(titulo, size=22, weight=ft.FontWeight.BOLD)
            self.subtitle=ft.Text(subtitulo)
            self.controls=arrCtl
            self.controls.append(ft.TextButton("Apagar", on_click=apagar, data=id))
            self.text_color=cor[cor1]
            self.collapsed_text_color=cor[5]
            self.on_change=alterou_etile
            self.bgcolor=bgcor[(cor1+1)%6]
            self.collapsed_bgcolor=bgcor[(cor1+2)%6]
            self.controls_padding = ft.padding.all(-5)

    titulo="Primeiro ExpansionTile"
    subtitulo="Demonstrando ícone no final do controle"
    arrCtl=[ft.ListTile(title=ft.Text("Texto interno do ExpansionTile 1"))]
    etile1=ETile(titulo, subtitulo, arrCtl, 0, 1)

    etile1.affinity=ft.TileAffinity.TRAILING
    etile1.maintain_state=True
    etile1.initially_expanded=True

    titulo="Segundo ExpansionTile"
    subtitulo="Demonstrando ícone personalizado"
    arrCtl=[ft.ListTile(title=ft.Text("Texto interno do ExpansionTile 2"))]
    etile2=ETile(titulo, subtitulo, arrCtl, 2, 2)
    etile2.trailing=ft.Icon(ft.icons.ADD_A_PHOTO_ROUNDED)

    titulo="Terceiro ExpansionTile"
    subtitulo="Ícone no início do controle e várias linhas internas"
    arrCtl=[ft.ListTile(title=ft.Text("3.1: Texto interno 1 do ExpansionTile 3")),
            ft.ListTile(title=ft.Text("3.2: Texto interno 2 do ExpansionTile 3")),
            ft.ListTile(title=ft.Text("3.3: Texto interno 3 do ExpansionTile 3"))]
    etile3=ETile(titulo, subtitulo, arrCtl, 4, 3)

    etile3.affinity=ft.TileAffinity.LEADING

    page.add(etile1, etile2, etile3)

    page.show_snack_bar(mostrar_snack("Clique nas barras ou ícones"))

ft.app(target=main)
Figura 2: estado inicial do aplicativo. Segundo e terceiro tiles expandidos. Segundo tile apagado.

A classe ETile é usada para construir os controles ExpansionTile no aplicativo. Ela adiciona um botão a cada um deles que pode apagar esse controle. Para identificar qual controle deve ser apagado cada botão recebe um id anexado por meio de flet.TextButton.data. O apagamento consiste na remoção do controle da lista page.controls.

O apagamento ou mudança de estado entre colapsado ou expandido de cada controle são relatados em um flet.SnackBar, uma barra mostrada temporariamente no pé da página. Eesa barra também contém um botão que aciona o evento que troca as cores de fundo dos elementos colapsados ou não.

Flet: Dismissible

Outro controle de Layout

Controle Dismissible

Dismissible é um controle que pode ser arrastado em uma direção especificada. Quando arrastado ele gera eventos que podem ser capturados para efetuar ações sobre o aplicativo. O conteúdo desenhado dentro do controle permanece visível e acompanha o arrasto do controle que o contém. O controle é dispensado (eliminado) do aplicativo quando atinge um deslocamento mínimo determinado em código.

Nota: chamaremos de dispensa a ação de dismiss do controle. Antes da dispensa o controle encolhe em tamanho, gerando os eventos descritos abaixo.

Um exemplo bem reduzido de uso do dismiss é mostrado abaixo.

import flet as ft
import copy

def main(page):

    dm1=ft.Dismissible(content=ft.Text("Primeira Linha", size=20, bgcolor=ft.colors.AMBER_100))
    (dm2 := copy.deepcopy(dm1)).content.value="Segunda Linha"
    (dm3 := copy.deepcopy(dm1)).content.value="Terceira Linha"

    page.add(ft.Column([dm1, dm2, dm3]))
ft.app(main)

Esse código gera o seguinte aplicativo, mostrado na figura 1 em três estados:

Figura 1: Três linhas de controle Dismissible: estado original, arrasto para a direita, linha 1 eliminada.

Nesse exemplo criamos um controle dm1=ft.Dismissible() e o copiamos, usando deepcopy para dois outros controles. O método copy.deepcopy é necessário para que controles internos sejam copiados e a cópia seja feita por valor. Isso significa que dm2 e dm3 são novos objetos e não apenas uma nova referência para dm1. Com isso podemos mudar dm2.content.value="Segunda Linha" (o valor do conteúdo interno).

Nota: O operador walrus (:=) de atribuição tem o efeito de fazer uma atribuição e já retornar o objeto para operações posteriores. Portanto:

    (dm2 := copy.deepcopy(dm1)).content.value="Segunda Linha"
    # é o mesmo que
    dm2 = copy.deepcopy(dm1)
    dm2.content.value="Segunda Linha"

Em um caso mais elaborado podemos inserir linhas formatadas (por exemplo inserindo o texto dentro de containers), capturar outros eventos e definir outras propriedades. A figura 2 mostra um caso um pouco mais elaborado e o exemplo de uso de Dismissible no final desse texto mostra um código mais completo.

Figura 2: linhas formatadas com container: arrasto da linha 1 para ambos os lados e exclusão da linha 1.

Propriedades de Dismissible

Dismissible tem as seguintes propriedades:

Propriedade Descrição
background um controle que fica por trás do controle principal que exibe conteúdo.
Se secondary_background também for especificado esse controle secundário aparece como fundo quando o conteúdo é arrastado para os lados ou verticalmente.
content um controle filho que fica exibido dentro do Dismissible e se move com ele.
cross_axis_end_offset especifica um valor para deslocamento vertical quando o controle é movido horizontalmente, fazendo o controle se mover na diagonal, para baixo se o valor for positivo, para cima se negativo.
direction direção usada para dispensar o controle. Os valores possível estão no enum DismissDirection:

  • DismissDirection.UP
  • DismissDirection.DOWN
  • DismissDirection.LEFT
  • DismissDirection.RIGHT
  • DismissDirection.START_TO_END
  • DismissDirection.END_TO_START
dismiss_thresholds o valor mínimo de deslocamento considerado para dispensar o controle. A partir desse valor o controle é removido do aplicativo. Por exemplo, se o valor é de 0.4 (o default) a barra tem que ser movida de 40% ou mais de sua extensão para ser dispensada. Seu valor é especificado como um dicionário tendo como chaves um objeto DismissDirection e o valor um valor numérico entre 0.0 e 1.0. O bloco de código mostra isso:

    ft.Dismissible(
    # ...
    dismiss_thresholds={
        ft.DismissDirection.VERTICAL: 0.1,
        ft.DismissDirection.START_TO_END: 0.7
    }
)

Esse dicionário define que os valores mínimos (thresholds) são 10% na vertical e 70% na horizontal, se o movimento for do início para o final (para a direita).

movement_duration a duração do movimento para que o controle seja dispensado ou retorne para sua posição original.
resize_duration a duração do movimento de contração antes que o evento de dispensa seja acionado.
secondary_background um controle desenhado por tras do conteúdo principal, que é exposto quando o conteúdo é arrastado para os lados. Só pode ser especificado se o background também for definido.

Eventos de Dismissible

Evento Descrição
on_confirm_dismiss um evento que ocorre antes que o controle seja dispensado, dando a oportunidade de se confirmar ou negar essa operação. O controle não pode ser arrastado novamente até que essa pendência seja resolvida.

Para resolver essa pendência deve ser chamado o método confirm_dismiss(dismiss) passando um booleano: True para confirmar, provocando a dispensa do controle, False para negar, o que faz com que ele volte para sua posição original. Uma possibilidade consiste em apresentar a pergunta em uma caixa como AlertDialog.

on_dismiss dispara quando o controle é dispensado. Antes da dispensa o controle é contraído, diminuindo de tamanho.
on_resize dispara quando o controle muda de tamanho, o que ocorre, por exemplo, na contração ao ser dispensado.
on_update dispara quando o controle é arrastado, independentemente de ser ou não dispensado.

Método de Dismissible

Método Descrição
confirm_dismiss(dismiss: bool) resolve a pendência quando a dispensa é requerida. Esse evento pode ser chamado quando se trata do evento on_confirm_dismiss.

Exemplo de uso de Dismissible

Um exemplo mais completo de código está listado abaixo. Para manter a simplicidade e não usar caixas de diálogo (ainda não consideradas nessas notas) o evento on_confirm_dismiss=informe_dispensa apenas aciona a função informe_dispensa que registra na caixa de informações as ações de remoção dos controles pela direita ou pela esquerda, sem pedir a confirmação.

import flet as ft

def main(page):

    def informe_dispensa(e: ft.DismissibleDismissEvent):
        if e.direction == ft.DismissDirection.END_TO_START:
            msg.value += "\nSaída pela direita!"
        else:
           msg.value += "\nSaída pela esquerda!"
        e.control.confirm_dismiss(True)
        page.update()

    def dispensar(e):
        coluna.controls.remove(e.control)
        page.update()

    def atualizar(e: ft.DismissibleUpdateEvent):
        if e.direction == ft.DismissDirection.END_TO_START:
            icone.name=ft.icons.ARROW_BACK
        else:
            icone.name=ft.icons.ARROW_FORWARD
        texto.value=f"Progresso: {e.progress}"
        page.update()

    cor = [ft.colors.AMBER_100,ft.colors.BLUE_GREY_100, ft.colors.BLUE_50]
    array=[
        ft.Dismissible(
            content=ft.Container(
                ft.Text(f"Esta é a linha {i+1}", size=15),
                height=35, width=380, 
                border = ft.border.all(2, ft.colors.BLACK54),
                bgcolor= cor[i%2],
                padding = ft.padding.all(6),
                margin = ft.margin.all(2)
            ),
            dismiss_direction=ft.DismissDirection.HORIZONTAL,
            background=ft.Container(bgcolor=ft.colors.CYAN_100),
            secondary_background=ft.Container(bgcolor=ft.colors.BROWN_50),
            dismiss_thresholds={
                ft.DismissDirection.END_TO_START: 0.2,
                ft.DismissDirection.START_TO_END: 0.2
            },
            on_dismiss=dispensar, 
            on_update=atualizar,
            on_confirm_dismiss=informe_dispensa
        ) for i in range(10)
    ]

    coluna=ft.Column(array, spacing=1)
    icone=ft.Icon(name=ft.icons.KEYBOARD, color=ft.colors.BLUE, size=30)
    texto=ft.Text("Posição dos controles", size=18)
    linha_info = ft.Row([icone, texto])
    msg = ft.Text(f"Eventos capturados (Dispensar):\n" , size=15)
    ver_acao = ft.Container(
        msg,
        height=100, width=380, 
        expand=True,
        border = ft.border.all(2, ft.colors.BLACK54),
        bgcolor= cor[2],
        padding = ft.padding.all(12),
        margin = ft.margin.all(3)
    )

    page.add(coluna, linha_info, ver_acao)

ft.app(main)
Figura 3: (a) o estado inicial do aplicativo; (b) arrasto da linha 1 para a direita; (c) arrasto da linha 2 para a esquerda; (d) removidas linhas 1 e 3.

A operação array=[(objeto(i)) for i in range(10)] cria uma lista com 10 objetos objeto(i) passando o valor de i internamente para o objeto em cada loop.

A função informa_dispensa(e) recebe o evento e que contém uma descrição da direção do evento de arrastar. Ela atualiza a variável msg que é exibida dentro do container ver_acao.

A função dispensar(e) usa a referência ao controle e.control para removê-lo, usando coluna.controls.remove(e.control).

A função atualizar(e) escolhe um ícone de direção e insere texto mostrando a porcentagem de arrasto a cada momento, atualizando a variável texto que está em linha_lnfo.