当前位置: 首页 > news >正文

Android之悬浮窗实现

文章目录

  • 前言
  • 一、效果图
  • 二、实现步骤
    • 1.AndroidManifest权限以及service注册
    • 2.service代码
    • 3.activity实现
  • 总结


前言

经常接触音视频以及直播的同学应该知道,悬浮窗是必备需求,今天就记录一下自己悬浮窗的视线过程,流程就是点击缩小按钮回到桌面,把悬浮窗放桌面,点击悬浮窗回到应用界面。


一、效果图

效果图一张为正常界面,一张为悬浮窗效果界面。
在这里插入图片描述
在这里插入图片描述

二、实现步骤

1.AndroidManifest权限以及service注册

代码如下(示例):

    <!--悬浮窗-->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />


 	 <!-- 悬浮窗Service -->
        <service
            android:name=".service.FloatingWindowService"
            android:enabled="true"
            android:exported="false"/>

2.service代码

代码如下(示例):

/**
 * @Author : CaoLiulang
 * @Time : 2025/3/18 13:22
 * @Description :悬浮窗service
 */
public class FloatingWindowService extends Service {
    private WindowManager windowManager;
    private View floatingView;
    private ScheduledExecutorService executor;
    // 定义变量记录初始触摸点和窗口初始位置
    private float initialTouchX, initialTouchY;
    private float startX; // 声明为成员变量
    private float startY;

    @Override
    public void onCreate() {
        super.onCreate();
        executor = Executors.newSingleThreadScheduledExecutor();
        createFloatingWindow();
    }

    private void createFloatingWindow() {
        // 引入悬浮窗口布局
        floatingView = LayoutInflater.from(this).inflate(R.layout.livestreamingconsolesx, null);
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        // 窗口参数设置
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                380,  // 显式设置宽度(像素)
                600,  // 显式设置高度(像素)
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
                        WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
                        WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                PixelFormat.TRANSLUCENT);

        params.gravity = Gravity.START | Gravity.TOP;
        params.x = 20;
        params.y = 20;
        windowManager.addView(floatingView, params);
        floatingView.setOnTouchListener((v, event) -> {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    initialTouchX = event.getRawX() - params.x;
                    initialTouchY = event.getRawY() - params.y;
                    startX = event.getRawX();
                    startY = event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float newX = event.getRawX() - initialTouchX;
                    float newY = event.getRawY() - initialTouchY;
                    // 获取屏幕尺寸
                    WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
                    DisplayMetrics metrics = new DisplayMetrics();
                    if (wm != null) {
                        wm.getDefaultDisplay().getMetrics(metrics);
                        int screenWidth = metrics.widthPixels;
                        int screenHeight = metrics.heightPixels;

                        // 限制移动范围
                        newX = Math.max(0, Math.min(newX, screenWidth - params.width));
                        newY = Math.max(0, Math.min(newY, screenHeight - params.height));
                    }
                    params.x = (int) newX;
                    params.y = (int) newY;
                    windowManager.updateViewLayout(floatingView, params);
                    return true;
                case MotionEvent.ACTION_UP:
                    // 计算移动距离
                    float deltaX = Math.abs(event.getRawX() - startX);
                    float deltaY = Math.abs(event.getRawY() - startY);

                    // 如果移动距离小于阈值,视为点击
                    if (deltaX < 5 && deltaY < 5) {
                        // 手动触发点击事件
                        floatingView.performClick();
                    }
                    return true;
            }
            return false;
        });

        // 单独设置 OnClickListener
        floatingView.setOnClickListener(v -> {
            // 点击逻辑  这里是跳转本软件,有的手机会重启,所以直接选择下面方式跳转当前界面
            //Intent intent = getPackageManager().getLaunchIntentForPackage("这里就是自己的包名");
            //if (intent != null) {
                //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
               // startActivity(intent);
            //}
            //跳转当前界面
            Intent intent = new Intent(this, LiveStreamingConsole.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);
            //延迟一秒关闭
            executor.schedule(() -> stopSelf(), 1, TimeUnit.SECONDS);
        });
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 关闭线程池(根据实际情况选择是否立即关闭)
        executor.shutdownNow();
        if (floatingView != null) windowManager.removeView(floatingView);
    }

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

3.activity实现

//定义变量
private var YOUR_REQUEST_CODE = 1000
//调用 动态判断权限
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    if (!Settings.canDrawOverlays(this)) {
                        val intent = Intent(
                            Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                            Uri.parse("package:$packageName")
                        )
                        startActivityForResult(intent, YOUR_REQUEST_CODE)
                    } else {
                        createFloatingWindow();
                    }
                }

 @RequiresApi(Build.VERSION_CODES.O)
    private fun createFloatingWindow() {
        //启动service
        startService(Intent(this, FloatingWindowService::class.java))
        // 返回桌面
        moveTaskToBack(true)
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == YOUR_REQUEST_CODE) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(
                    this
                )
            ) {
                createFloatingWindow()
            } else {
                Toast.makeText(this, "需要悬浮窗权限", Toast.LENGTH_SHORT).show()
            }
        }
    }

总结

提示:service里面的布局文件就是悬浮窗的布局,所以xml我就没有贴代码,测试的时候可以自己随意画个xml看效果:

以上就是Android悬浮窗的实现了,service中还可以交互数据,也就是悬浮窗,欢迎各位同学指出问题。

相关文章:

  • 如何设计一个 RPC 框架?需要考虑哪些点?
  • 结合基于标签置信度的特征选择方法用于部分多标签学习-简介版
  • C++ —— 线程同步(互斥锁)
  • vue 中常用操作数组的方法
  • Minecraft命令总结(持续更新)
  • Deal - DbC、检查Python 值、异常和副作用
  • 鸿蒙NEXT项目实战-百得知识库03
  • jpa报错 Validation failed for query for method public abstract
  • MySQL单表查询
  • 练习题:94
  • MutableList 和 ArrayList 区别
  • 格力地产更名“珠免集团“ 全面转型免税赛道
  • 【AI绘画教程】从MJ到SD,九周精通AI绘画,MJ基础至SD模型训练全方位教学
  • 高级java每日一道面试题-2025年3月06日-微服务篇[Eureka篇]-Eureka Server和Eureka Client关系?
  • OpenWrt中使用GPIO模拟I2C控制CAT9555芯片的示例代码
  • 显示模组ESD损伤探讨
  • 调用百度智能云API实现货币识别
  • 蓝桥杯第九天 2022 省赛 第 4 题 最少刷题数
  • QtCreator16创建WebAssembly工程在浏览器中显示图片
  • MSys2统一开发环境,快速搭建windows opencv环境
  • 深圳两家会所涉卖淫嫖娼各被罚7万元逾期未缴,警方发催告书
  • 习近平向中国人民解放军仪仗队致意
  • 商务部:中方愿同各国一道加强合作,促进跨境电商健康可持续发展
  • 深入贯彻中央八项规定精神学习教育中央第一指导组指导督导河北省见面会召开
  • 李彦宏:技术迭代速度之快从业30年来未见过,要提升执行力战胜对手
  • 联想发布超级智能体矩阵,杨元庆:美国关税影响反映在产品定价上,未来不确定性很大