Implementação Personalizada de Toast Nativo no Android

Análise de Requisitos

O contêiner do componente de toast deve seguir um layout de cima para baixo. Quando um toast é adicionado, uma animação de desaparecimento deve ser reproduzida, consistindo em dois efeitos: primeiro, o layout é empurrado para cima e gradualmente se torna transparente até desaparecer; segundo, durante o desaparecimento, a posição dos toasts adicionados posteriormente é ajustada conforme a mudança de posição do toast em desapaercimento.

Implementação do Código

Layout Principal

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainAtividade">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Olá Mundo!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    <Button
        android:id="@+id/btn_teste"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Testar"
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainAtividade.java

package com.exemplo.customtoast;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Button;

public class MainAtividade extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button botaoTeste = findViewById(R.id.btn_teste);
        botaoTeste.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        UtilidadeToast.exibirToast(MainAtividade.this, "Mensagem de exemplo", 1500);
                    }
                });
            }
        });
    }
}

UtilidadeToast.java

package com.exemplo.customtoast;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.lang.ref.WeakReference;

public class UtilidadeToast {
    private static WeakReference<LinearLayout> layoutReferenciaFraca;

    @SuppressLint({"RtlHardcoded", "UseCompatLoadingForDrawables"})
    public static void exibirToast(Context contexto, String mensagem, int duracao) {
        if (layoutReferenciaFraca == null) {
            WindowManager gerenciadorJanela = (WindowManager) contexto.getSystemService(Context.WINDOW_SERVICE);
            LinearLayout layoutPrincipal = new LinearLayout(contexto);
            LinearLayout.LayoutParams paramsLayout = new LinearLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            layoutPrincipal.setLayoutParams(paramsLayout);
            layoutPrincipal.setOrientation(LinearLayout.VERTICAL);
            WindowManager.LayoutParams paramsJanela = new WindowManager.LayoutParams();
            paramsJanela.gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL;
            paramsJanela.type = WindowManager.LayoutParams.TYPE_APPLICATION;
            paramsJanela.width = WindowManager.LayoutParams.MATCH_PARENT;
            paramsJanela.height = WindowManager.LayoutParams.MATCH_PARENT;
            paramsJanela.flags = WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS |
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
            paramsJanela.format = PixelFormat.RGBA_8888;
            gerenciadorJanela.addView(layoutPrincipal, paramsJanela);
            layoutReferenciaFraca = new WeakReference<>(layoutPrincipal);
            exibirToast(contexto, mensagem, duracao);
            return;
        }
        LinearLayout itemToast = new LinearLayout(contexto);
        itemToast.setHorizontalGravity(Gravity.CENTER_HORIZONTAL);
        LinearLayout.LayoutParams paramsItem = new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, 60);
        paramsItem.setMargins(0, 0, 0, 20);
        itemToast.setLayoutParams(paramsItem);
        TextView textoConteudo = new TextView(contexto);
        textoConteudo.setText(mensagem);
        textoConteudo.setPadding(12, 12, 12, 12);
        textoConteudo.setMinWidth(220);
        textoConteudo.setHeight(60);
        textoConteudo.setGravity(Gravity.CENTER);
        textoConteudo.setBackground(contexto.getResources().getDrawable(R.drawable.fundo_toast));
        textoConteudo.setTextColor(Color.parseColor("#FFFFFF"));
        itemToast.addView(textoConteudo);
        itemToast.animate()
                .setStartDelay(duracao)
                .setDuration(1300)
                .alpha(0f)
                .translationYBy(-30f)
                .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    private float alturaAnterior = 0;
                    @Override
                    public void onAnimationUpdate(ValueAnimator animacao) {
                        float progressoAtual = (float) animacao.getAnimatedValue();
                        if (alturaAnterior == 0) {
                            alturaAnterior = itemToast.getMeasuredHeight();
                        }
                        LinearLayout.LayoutParams paramsAtualizados = (LinearLayout.LayoutParams) itemToast.getLayoutParams();
                        int novaMargemInferior = (int) (-progressoAtual * alturaAnterior);
                        paramsAtualizados.setMargins(0, 0, 0, novaMargemInferior);
                        itemToast.post(new Runnable() {
                            @Override
                            public void run() {
                                itemToast.setLayoutParams(paramsAtualizados);
                            }
                        });
                    }
                })
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animacao) {
                        super.onAnimationEnd(animacao);
                        if (layoutReferenciaFraca.get() != null) {
                            layoutReferenciaFraca.get().removeView(itemToast);
                        }
                    }
                })
                .start();
        layoutReferenciaFraca.get().addView(itemToast);
    }
}

fundo_toast.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#333333"/>
    <corners android:radius="8dp"/>
    <stroke android:color="#4A90E2" android:width="2dp"/>
</shape>

A funcionalidade demonstrada permite a exibição de toasts personalizados com animações de desaparecimento suaves. A implementação utiliza WindowManager para sobrepor a interface e animações do Android para manipular a transparência e o posicionamento dos elementos.

Tags: android Toast Personalização Animação java

Publicado em 6-1 16:44 por Thomas