Implementação de Janelas Flutuantes no Android


import android.app.Service;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;

public class ServicoJanelaFlutuante extends Service {
    private WindowManager gerenciadorJanela;
    private View visaoFlutuante;

    @Override
    public void onCreate() {
        super.onCreate();
        gerenciadorJanela = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        visaoFlutuante = LayoutInflater.from(this).inflate(R.layout.layout_janela_flutuante, null);

        WindowManager.LayoutParams parametros = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT
        );

        parametros.gravity = Gravity.CENTER;
        parametros.x = 50;
        parametros.y = 200;
        gerenciadorJanela.addView(visaoFlutuante, parametros);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (visaoFlutuante != null) {
            gerenciadorJanela.removeView(visaoFlutuante);
        }
    }

    @Override
    public IBinder onBind(android.content.Intent intent) {
        return null;
    }
}

Explicação dos parâmetros principais: WRAP_CONTENT ajusta o tamanho ao conteúdo, TYPE_APPLICATION_OVERLAY permite exibição sobre outras apps (requer permissão no Android 8.0+), FLAG_NOT_FOCUSABLE impede que a janela capture foco, e TRANSLUCENT torna o fundo semitransparente.

Adicione funcionalidade de arrastar para tornar a janela interativa. Isso envolve capturar eventos de toque e atualizar a posição.


import android.app.Service;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;

public class ServicoJanelaFlutuante extends Service {
    private WindowManager gerenciadorJanela;
    private View visaoFlutuante;
    private WindowManager.LayoutParams parametrosLayout;
    private int posicaoInicialX;
    private int posicaoInicialY;
    private float toqueInicialX;
    private float toqueInicialY;

    @Override
    public void onCreate() {
        super.onCreate();
        gerenciadorJanela = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        visaoFlutuante = LayoutInflater.from(this).inflate(R.layout.layout_janela_flutuante, null);

        parametrosLayout = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT
        );

        parametrosLayout.gravity = Gravity.TOP | Gravity.START;
        parametrosLayout.x = 0;
        parametrosLayout.y = 150;
        gerenciadorJanela.addView(visaoFlutuante, parametrosLayout);

        visaoFlutuante.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent evento) {
                switch (evento.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        posicaoInicialX = parametrosLayout.x;
                        posicaoInicialY = parametrosLayout.y;
                        toqueInicialX = evento.getRawX();
                        toqueInicialY = evento.getRawY();
                        return true;
                    case MotionEvent.ACTION_MOVE:
                        parametrosLayout.x = posicaoInicialX + (int) (evento.getRawX() - toqueInicialX);
                        parametrosLayout.y = posicaoInicialY + (int) (evento.getRawY() - toqueInicialY);
                        gerenciadorJanela.updateViewLayout(visaoFlutuante, parametrosLayout);
                        return true;
                    case MotionEvent.ACTION_UP:
                        return true;
                }
                return false;
            }
        });
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (visaoFlutuante != null) {
            gerenciadorJanela.removeView(visaoFlutuante);
        }
    }

    @Override
    public IBinder onBind(android.content.Intent intent) {
        return null;
    }
}

Os eventos de toque (MotionEvent) são processados: ACTION_DOWN registra a posição inicial, ACTION_MOVE calcula o deslocamento e atualiza o layout, e ACTION_UP finaliza a ação. A chamada updateViewLayout redesenha a janela na nova posição.

Para uma janela com botões, defina um layout XML com componentes interativos e vincule-os a ações específicas, como captura de tela ou cliques de acessibilidade.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="12dp">

    <Button
        android:id="@+id/btn_captura_tela"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Capturar Tela" />

    <Button
        android:id="@+id/btn_clique_acessibilidade"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Simular Clique" />
</LinearLayout>

No Service, encontre os botões e adicione listeners. Os métodos executarCapturaCompleta e executarCliqueAcessibilidade devem conter a lógica real, como usar MediaProjection ou AccessibilityService.


import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;

public class ServicoJanelaFlutuante extends Service {
    private WindowManager gerenciadorJanela;
    private View visaoFlutuante;
    private WindowManager.LayoutParams parametrosLayout;
    private int posicaoInicialX;
    private int posicaoInicialY;
    private float toqueInicialX;
    private float toqueInicialY;

    @Override
    public void onCreate() {
        super.onCreate();
        gerenciadorJanela = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        visaoFlutuante = LayoutInflater.from(this).inflate(R.layout.layout_janela_flutuante, null);

        parametrosLayout = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT
        );

        parametrosLayout.gravity = Gravity.CENTER;
        parametrosLayout.x = 0;
        parametrosLayout.y = 0;
        gerenciadorJanela.addView(visaoFlutuante, parametrosLayout);

        visaoFlutuante.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent evento) {
                if (evento.getAction() == MotionEvent.ACTION_DOWN) {
                    posicaoInicialX = parametrosLayout.x;
                    posicaoInicialY = parametrosLayout.y;
                    toqueInicialX = evento.getRawX();
                    toqueInicialY = evento.getRawY();
                    return true;
                } else if (evento.getAction() == MotionEvent.ACTION_MOVE) {
                    parametrosLayout.x = posicaoInicialX + (int) (evento.getRawX() - toqueInicialX);
                    parametrosLayout.y = posicaoInicialY + (int) (evento.getRawY() - toqueInicialY);
                    gerenciadorJanela.updateViewLayout(visaoFlutuante, parametrosLayout);
                    return true;
                }
                return false;
            }
        });

        Button btnCaptura = visaoFlutuante.findViewById(R.id.btn_captura_tela);
        Button btnAcessibilidade = visaoFlutuante.findViewById(R.id.btn_clique_acessibilidade);

        btnCaptura.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                executarCapturaCompleta();
            }
        });

        btnAcessibilidade.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                executarCliqueAcessibilidade();
            }
        });
    }

    private void executarCapturaCompleta() {
        // Implementar lógica de captura de tela usando MediaProjection ou similar
    }

    private void executarCliqueAcessibilidade() {
        // Implementar lógica de clique via AccessibilityService
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (visaoFlutuante != null) {
            gerenciadorJanela.removeView(visaoFlutuante);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

Os botões são localizados com findViewById e cada um aciona um método correspondente. A estrutura do código permite extensão para funcionalidades mais complexas.

Tags: android WindowManager Service MotionEvent AccessibilityService

Publicado em 6-5 23:52 por Thomas