Flet: Exemplo 4

Controles de Layout

Card, Tabs, SafeArea, Divider, VerticalDivider, ExpansionPanelList são outros controles que facilitam a divisão, exibição ou ocultação de conteúdo dentro de uma página de aplicativo.

Seguem-se alguns exemplos de códigos usando esses controles.

Exemplo de uso de Card

Leia sobre a descrição do controle, suas propriedades e evento em Controles de Layout: Card.
O código abaixo exibe a construção de uma página com 3 cards.

import flet as ft

def main(page):
    page.title = "Exemplo usando \"card\""

    listitulo = ft.ListTile(leading=ft.Icon(ft.icons.ACCESS_ALARM,  size=50, color="#D3143E"),
                            title=ft.Text("Compromisso: 28 de junho de 2024"),
                            subtitle=ft.Text("Aniversário de seu filho. Não esqueça o presente!"),)
    
    def apagar(e):
        listitulo.title= ft.Text("")
        listitulo.subtitle=ft.Text("* apagado", color="#aaaaaa")
        cartao.color="WHITE"
        page.update()

    def proximo(e):
        listitulo.title=ft.Text("Compromisso: 01 de agosto de 2024")
        listitulo.subtitle=ft.Text("Viagem para São Paulo", color="#000000")
        cartao.color="#CBEBA8"
        page.update()

    bt1 = ft.TextButton("Apagar", on_click=apagar)
    bt2 = ft.TextButton("Próximo compromisso", on_click=proximo)

    linha = ft.Row([bt1, bt2], alignment=ft.MainAxisAlignment.END)
    coluna = ft.Column([listitulo, linha,])
    contem = ft.Container(coluna, width=500, padding=20,)
    cartao = ft.Card(contem, color="YELLOW", shadow_color="white", elevation=8.0)

    page.add(cartao)

ft.app(target=main)

Na figura 1 o resultado desse código, quando executado.

Figura 1: O Card amarelo mostra o estado incial do app. Card verde, se for clicado “Próximo compromisso”. Último Card, se o clique for no botão “Apagar”.

Tabs

Leia sobre a descrição do controle, suas propriedades e evento em Controle Tabs.

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

Exemplo de uso de Tabs

O código abaixo exibe a construção de uma página o uso de Tabs.

import flet as ft

def main(page: ft.Page):
    page.bgcolor=ft.colors.BROWN_200
    
    ct_interno=ft.Container(content=ft.Text("Conteúdo do Tab 1 dentro de um Card",
                            size=20, weight=ft.FontWeight.BOLD), padding=45)
    
    card = ft.Card(content= ct_interno, elevation=20, expand=1)

    ct_tab1 = ft.Container(content=card, bgcolor="#BAE3ED", alignment=ft.alignment.center)

    tab1 = ft.Tab(text="Tab 1", content=ct_tab1)

    ct_tab2 = ft.Container(content=ft.Text("Conteúdo de texto do Tab 2", size=30),
                           bgcolor="#BBF3EF",
                           margin = ft.margin.all(10.0),
                           padding=ft.padding.all(1.0),
                           alignment=ft.alignment.top_right,
                           border=ft.border.all(2, "#55555"),
                           border_radius=20)

    tab2 = ft.Tab(text="Tab 2", icon=ft.icons.SEARCH, content=ct_tab2)

    ct_tab3 = ft.Container(content=ft.Text("Conteúdo de texto do Tab 3", size=30),
                           bgcolor="#FCF5CC", alignment=ft.alignment.bottom_left)

    tab3 = ft.Tab(text="Tab 3", icon=ft.icons.SETTINGS, content=ct_tab3)

    tabs = ft.Tabs(tabs=[tab1, tab2, tab3],
                   selected_index=0,
                   divider_color="#ABCAD0",
                   label_color="WHITE",
                   unselected_label_color="BLACK",
                   overlay_color = "YELLOW",
                   indicator_padding=10,
                   indicator_color="RED")

    page.add(ft.Container(tabs, bgcolor=ft.colors.BROWN_200, expand=1))

ft.app(target=main)

O resultado desse código está mostrado na figura 2.

Figura 2: Tabs 1, 2 e 3 exibidos, com cliques nas respectivas guias ou abas (tab).

O primeiro tab exibe um card com texto interno, centralizado. O segundo tab tem um container formatado com bordas arredondadas e texto no alto, à direita. O terceiro tab tem texto em baixo, à esquerda.

Observe que no código acima, e na maioria dos blocos de código nessas notas, os controles são montados por partes, separando controles que serão depois inseridos dentro de outros. Para montar o tab1, criamos ct_interno, um container com um texto e o inserimos dentro de card, que é inserido em ct_tab1 e finalmente em tab1. Isso é feito por motívo didático, para que o código fique mais fácil de ser entendido, e não tem efeito no resultado no momento da execução. Esse estilo é uma preferência do programador. Uma forma alternativa para a construção do mesmo controle pode ser vista abaixo.

    tab1 = ft.Tab(
        text="Tab 1",
        content=ft.Container(
            content=ft.Card(
                content=ft.Container(
                    content=ft.Text("Conteúdo do Tab 1 dentro de um Card",
                    size=20, weight=ft.FontWeight.BOLD), padding=45
                ),
                elevation=20, expand=1
            ),
            bgcolor="#BAE3ED",
            alignment=ft.alignment.center
        )
    )

De qualquer forma é importante estabelecer um critério de indentação e se manter coerente com ele.

SafeArea

SafeArea insere conteúdo com preenchimento (padding, as distâncias entre as margens internas do container com as bordas externas do controle) de modo a impedir que os elementos de interface do sistema operacional se sobreponham às do aplicativo. Leia sobre a descrição do controle, suas propriedades em Controles de Layout: SafeAreaCard.

Um exemplo é mostrado no código abaixo.

import flet as ft

class Contador:
    contagem = 0

def main(page: ft.Page):    
    def add_click(e):
        cliques.contagem += 1
        contador.value = f"Contador: {cliques.contagem}"
        contador.update()

    page.horizontal_alignment=ft.CrossAxisAlignment.CENTER
    page.vertical_alignment=ft.MainAxisAlignment.CENTER
    page.floating_action_button = ft.FloatingActionButton(icon=ft.icons.ADD, on_click=add_click)

    cliques = Contador()

    contador = ft.Text("Contador: 0", size=30, color="WHITE", weight=ft.FontWeight.BOLD)

    page.add(
        ft.SafeArea(
            expand=False,
            content=ft.Container(
                content=contador,
                padding=35, width=300, height=190,
                alignment=ft.alignment.center,
                bgcolor="BLUE",
                gradient=ft.LinearGradient(colors=[ft.colors.BLACK, ft.colors.BLUE_900]),
                border=ft.border.all(3, ft.colors.BLUE_100),
                border_radius=ft.border_radius.all(10)
            )
        )
    )

ft.app(main)

A execução desse código gera um aplicativo mostrado na figura 3.

Figura 3: Controle SafeArea tendo um container no seu interior.

Observe que, para manter um contador global foi criada a classe e propriedade State.counter. O objeto cliques = Contador() tem, portanto a propriedade cliques.contagem, que é incrementada a cada clique no botão floating_action_button.

Divider e VerticalDivider

São controles usados para desenhar uma linha divisória, horizontal e vertical, respectivamente. Leia sobre a descrição dos controles e suas propriedades em Controle Divider e VerticalDivider.

import flet as ft

def main(page: ft.Page):
    ct1 = ft.Container(bgcolor="#797EF6", expand=True)
    ct2 = ft.Container(bgcolor="#4ADEDE", expand=True)
    divisorVertical = ft.VerticalDivider(width=50, thickness=10, color="#960cc9")
    ct3 = ft.Container(bgcolor="#1AA7EC", expand=True)
    ct4 = ft.Container(bgcolor="#1E2F97", expand=True)
    divisorHorizontal= ft.Divider(height=50, thickness=40, color="#F6E750")

    page.add(
        ft.Row([ct1, divisorVertical, ct2], spacing=0, expand=True),
        ft.Column([ct3, divisorHorizontal, ct4], spacing=0, expand=True)
    )

ft.app(target=main)

O código executado é renderizado como a figura 4.

Figura 4: Divider e VerticalDivider.

A única diferença entre propriedades de Divider e VerticalDivider são as propriedades Divider.height e VerticalDivider.width.

ExpansionPanel e ExpansionPanelList

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

import flet as ft

class Painel(ft.ExpansionPanel):
    def __init__(self, cabecalho, titulo, texto, cor, botao=None, indice=0):
        super().__init__()
        self.can_tap_header=True
        self.bgcolor=cor
        self.header=ft.Text("  " + cabecalho, size=22, weight=ft.FontWeight.BOLD)
        self.titulo=ft.Text(titulo, size=18)
        self.texto=ft.Text(texto, size=17)
        self.conteudo=ft.ListTile(
            title=self.titulo,
            subtitle=self.texto,
            trailing=botao
        )
        self.content=self.conteudo
        self.indice = indice

def main(page: ft.Page):
    cor = [ft.colors.GREEN_100, ft.colors.BLUE_100, ft.colors.RED_100,
           ft.colors.WHITE10, ft.colors.BLUE_500, ft.colors.WHITE70]
    
    def pegar_painel(index):
        for pn in panel.controls:
            if index == pn.indice: return pn
        return None    
            

    def mudou(e: ft.ControlEvent):
        if int(e.data)==4: return
        atividade.conteudo.subtitle.value += f"\nO painel {e.data} foi modificado"
        page.update()

    def apagar(e: ft.ControlEvent):
        pn = pegar_painel(e.control.data)
        if pn:
            panel.controls.remove(pn)
            atividade.conteudo.subtitle.value += f"\nFoi apagado o Painel {e.control.data}"
        page.update()

    panel = ft.ExpansionPanelList(
        elevation=8,
        spacing=6,
        expand_icon_color=ft.colors.AMBER_100,
        divider_color=ft.colors.CYAN_ACCENT_100,
        on_change=mudou
    )
    panel.controls.append(
        Painel("Atividades Online", "Lista das ações realizadas no aplicativo.", "",
               cor[4], None,0
        )
    )
  
    panel.controls.append(
        Painel(
            "Painel 1", "Conteúdo do painel 1", "Clique no ícone para apagar o painel 1.", cor[0],
            ft.ElevatedButton("Apagar", icon=ft.icons.DELETE, width=150, height=30, data=1,
            on_click=apagar), 1
        )
    )

    panel.controls.append(
        Painel(
            "Painel 2", "Conteúdo do painel 2", "Clique no ícone para apagar o painel 2.", cor[1],
            ft.ElevatedButton("Apagar", icon=ft.icons.DELETE, width=150, height=30, data=2,
            on_click=apagar), 2
        )
    )

    panel.controls.append(
        Painel(
            "Painel 3", "Conteúdo do painel 3", "Clique no ícone para apagar o painel 3.", cor[2],
            ft.ElevatedButton("Apagar", icon=ft.icons.DELETE, width=150, height=30, data=3,
            on_click=apagar), 3
        )
    )

    atividade = Painel("Resumo de Atividades", "", "Conteúdo de texto da informação de uso", cor[5])
    panel.controls.append(atividade)

    page.add(panel)

ft.app(target=main)

O resultado pode ser visto na figura 5.

Figura 5: à esquerda o painel com todos os painéis recolhidos. À direita o estado do app após algumas operações de expandir e apagar.

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

2 thoughts on “Flet: Exemplo 4

Leave a Reply

Your email address will not be published. Required fields are marked *