Android 悬浮窗

悬浮窗是可以在不同软件最上面,默认的效果,不需要过多设置,通常放在服务里面,因为需要长时间存在

思路

写一个悬浮窗大概是以下几个步骤
1、写一个服务,因为悬浮窗长期存在,不依赖于界面,所有最好写在服务里面
2、在服务需要获取到WindowManager这个类,用来加载一个悬浮窗的布局和一些列点击事件
3、启动服务,悬浮窗就可以启动

难点

1、悬浮窗的穿透点击
当悬浮窗悬浮的时候,理想状态,应该是悬浮窗里面的按钮和悬浮窗底层点击触摸事件不冲突。
2、需要注意,悬浮窗的可能会出现黑色背景,需要加params.format = PixelFormat.RGBA_8888;

代码逻辑

1、写一个服务

public class BackService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        
    }

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

2、在服务里面写一个一个方法,去创建一个一个悬浮窗的样式

    /**
     * 初始化一个悬浮窗
     */
    private void initWindow() {
        // 获取WindowManager
        mSystemService = (WindowManager) getSystemService(WINDOW_SERVICE);
        // 创建布局参数
        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        //这里需要进行不同的设置
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            params.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        //设置透明度
        params.alpha = 1.0f;
        //设置内部视图对齐方式
        params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
        //窗口的右上角角坐标
        params.x = 20;
        params.y = 20;
        //是指定窗口的像素格式为 RGBA_8888。
        //使用 RGBA_8888 像素格式的窗口可以在保持高质量图像的同时实现透明度效果。
        params.format = PixelFormat.RGBA_8888;
        //设置窗口的宽高,这里为自动
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        //这段非常重要,是后续是否穿透点击的关键
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  //表示悬浮窗口不需要获取焦点,这样用户点击悬浮窗口以外的区域,就不需要关闭悬浮窗口。
                |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//表示悬浮窗口不会阻塞事件传递,即用户点击悬浮窗口以外的区域时,事件会传递给后面的窗口处理。
        //这里的引入布局文件的方式,也可以动态添加控件
        mView = View.inflate(getApplicationContext(), R.layout.item_back, null);
        Button btnBack = mView.findViewById(R.id.btn_back);
        mSystemService.addView(mView,params);
    }

ps:此处要注意一下,当服务销毁的时候,需要记得,把布局的view给removeView

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mSystemService != null && mView != null){
            mSystemService.removeView(mView);
        }
    }

3、启动服务,悬浮窗就可以启动

startService(new Intent(context, BackService.class));

注意

1、需要注意在悬浮窗的点击中,需要效果是悬浮窗里面的按钮和悬浮窗底层点击触摸事件不冲突,关键代码是这儿

params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  //表示悬浮窗口不需要获取焦点,这样用户点击悬浮窗口以外的区域,就不需要关闭悬浮窗口。
                |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//表示悬浮窗口不会阻塞事件传递,即用户点击悬浮窗口以外的区域时,事件会传递给后面的窗口处理。

2、悬浮窗如果出现黑色背景,必须加这儿

//是指定窗口的像素格式为 RGBA_8888。
//使用 RGBA_8888 像素格式的窗口可以在保持高质量图像的同时实现透明度效果。
params.format = PixelFormat.RGBA_8888;

3、如果要隐藏当前的avtivity,只有悬浮窗,可以通过moveTaskToBack(true);设置
当activity的启动模式是singleInstance的时候,在当前的activity直接调用moveTaskToBack(true),即可将activity 退到后台
参数说明:
参数为false——代表只有当前activity是task根,指应用启动的第一个activity时,才有效;
参数为true——则忽略这个限制,任何activity都可以有效。
设置avtivity启动模式在AndroidManifest里面

<activity
    android:name=".MainActivity"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:exported="true"
    android:launchMode="singleInstance">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

整体代码

服务里面,启动服务很简单

public class BackService extends Service {

    private View mView;
    private WindowManager mSystemService;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        initWindow();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mSystemService != null && mView != null){
            mSystemService.removeView(mView);
        }
    }

    /**
     * 初始化一个悬浮窗
     */
    private void initWindow() {
        // 获取WindowManager
        mSystemService = (WindowManager) getSystemService(WINDOW_SERVICE);
        // 创建布局参数
        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            params.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        //设置透明度
        params.alpha = 1.0f;
        //设置内部视图对齐方式
        params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
        //窗口的左上角坐标
        params.x = 20;
        params.y = 20;
        //是指定窗口的像素格式为 RGBA_8888。
        //使用 RGBA_8888 像素格式的窗口可以在保持高质量图像的同时实现透明度效果。
        params.format = PixelFormat.RGBA_8888;
        //设置窗口的宽高,这里为自动
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  //表示悬浮窗口不需要获取焦点,这样用户点击悬浮窗口以外的区域,就不需要关闭悬浮窗口。
                |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//表示悬浮窗口不会阻塞事件传递,即用户点击悬浮窗口以外的区域时,事件会传递给后面的窗口处理。
        mView = View.inflate(getApplicationContext(), R.layout.item_back, null);
        Button btnBack = mView.findViewById(R.id.btn_back);
        btnBack.setOnClickListener(view1 -> {
            ToastUtils.showShort("点击了");//此处是点击逻辑,可以自己完成
        });
        mSystemService.addView(mView,params);
    }

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

效果图

Animation.gif
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容