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.

Leave a Reply

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