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;
    }
}

效果图

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值