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.

Leave a Reply

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