安卓悬浮窗----可移动的悬浮窗

news2024/11/20 8:26:08

目录

  • 前言
  • 一、添加对悬浮窗功能的支持
  • 二、通过service实现悬浮窗
    • 2.1 窗口属性和标志
    • 2.2 窗口移动
  • 三、完整代码


前言

记录一下基础的悬浮窗实现,分为几个重要的点进行阐述。

一、添加对悬浮窗功能的支持

app要实现悬浮窗功能,首先app要添加对悬浮窗功能的支持。

manifest文件添加权限:

   <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

app内也要去进行界面跳转,在设置里打开该应用的悬浮窗权限支持。

  if (!Settings.canDrawOverlays(this)) {
                    Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    startActivity(intent);
                }

在这里插入图片描述

二、通过service实现悬浮窗

通过Button开启服务的方式来实现悬浮窗,使用Windowmanager添加悬浮窗View。

2.1 窗口属性和标志

悬浮窗的属性一般为TYPE_APPLICATION_OVERLAY、TYPE_SYSTEM_ALERT、TYPE_TOAST、TYPE_APPLICATION_OVERLAY 等,这里采用TYPE_APPLICATION_OVERLAY 赋予悬浮窗基本属性。

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            params.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
// 设置悬浮框不可触摸
 params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

2.2 窗口移动

对view进行ontouch事件的重写,更新坐标即可

  // 设置悬浮框的Touch监听
  btnView.setOnTouchListener(new View.OnTouchListener() {
      //保存悬浮框最后位置的变量
      int lastX, lastY;
      int paramX, paramY;

      @Override
      public boolean onTouch(View v, MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  lastX = (int) event.getRawX();
                  lastY = (int) event.getRawY();
                  paramX = params.x;
                  paramY = params.y;
                  break;
              case MotionEvent.ACTION_MOVE:
                  int dx = (int) event.getRawX() - lastX;
                  int dy = (int) event.getRawY() - lastY;
                  params.x = paramX + dx;
                  params.y = paramY + dy;
                  // 更新悬浮窗位置
                  windowManager.updateViewLayout(btnView, params);
                  break;
          }
          return true;
      }
  });

三、完整代码

确实挺简单的,没什么可讲的。

activity

public class WindowActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn_on;
    Button btn_off;
    Boolean isOpen = false;
    Intent mIntent;

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

    private void bindViews() {
        btn_on = findViewById(R.id.btn_on);
        btn_on.setOnClickListener(this);
        btn_off = findViewById(R.id.btn_off);
        btn_off.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_on:
                mIntent = new Intent(WindowActivity.this, FloatService.class);
                mIntent.putExtra(FloatService.OPERATION, FloatService.OPERATION_SHOW);
                if (!Settings.canDrawOverlays(this)) {
                    Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    startActivity(intent);
                } else {
                    startService(mIntent);
                    Toast.makeText(WindowActivity.this, "悬浮框已开启~", Toast.LENGTH_SHORT).show();
                    isOpen = true;
                }
                break;
            case R.id.btn_off:
                if (isOpen) {
                    stopService(mIntent);
                    isOpen = false;
                }
                Toast.makeText(WindowActivity.this, "悬浮框已关闭~", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isOpen) {
            stopService(mIntent);
            isOpen = false;
        }
    }
}

FloatService

public class FloatService extends Service {
    Button btnView;
    WindowManager windowManager;
    WindowManager.LayoutParams params;
    Boolean isAdded;
    public static String OPERATION = "是否需要开启";
    public static int OPERATION_SHOW = 1;
    public static int OPERATION_HIDE = 2;
    int HANDLE_CHECK_ACTIVITY = 0;

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int operation = intent.getIntExtra(OPERATION, 3);
        if (operation == OPERATION_SHOW) {
            mHandler.sendEmptyMessage(HANDLE_CHECK_ACTIVITY);
        } else if (operation == OPERATION_HIDE) {
            mHandler.removeMessages(HANDLE_CHECK_ACTIVITY);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        createWindowView();
        super.onCreate();
    }

    @SuppressLint("ClickableViewAccessibility")
    private void createWindowView() {
        btnView = new Button(getApplicationContext());
        btnView.setBackgroundResource(R.drawable.author);
        windowManager = (WindowManager) getApplicationContext()
                .getSystemService(Context.WINDOW_SERVICE);
        params = new WindowManager.LayoutParams();

        // 设置悬浮框不可触摸
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应
        params.format = PixelFormat.RGBA_8888;
        // 设置悬浮框的宽高
        params.width = 200;
        params.height = 200;
        params.gravity = Gravity.LEFT;
        params.x = 200;
        params.y = 000;
        // 设置Window Type
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            params.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        // 设置悬浮框的Touch监听
        btnView.setOnTouchListener(new View.OnTouchListener() {
            //保存悬浮框最后位置的变量
            int lastX, lastY;
            int paramX, paramY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        lastX = (int) event.getRawX();
                        lastY = (int) event.getRawY();
                        paramX = params.x;
                        paramY = params.y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        int dx = (int) event.getRawX() - lastX;
                        int dy = (int) event.getRawY() - lastY;
                        params.x = paramX + dx;
                        params.y = paramY + dy;
                        // 更新悬浮窗位置
                        windowManager.updateViewLayout(btnView, params);
                        break;
                }
                return true;
            }
        });
        windowManager.addView(btnView, params);
        isAdded = true;
    }

    /**
     * 判断当前界面是否是桌面
     * android 6.0以上只能判断当前应用包名和Launcher
     */
    private boolean isAtHome() {
        ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> runningTaskInfos = mActivityManager.getRunningTasks(1);
        Log.d("henry", "是否在主页面" + runningTaskInfos);
        return getHomeApplicationList().contains(runningTaskInfos.get(0).topActivity.getPackageName());
    }

    /**
     * 获得属于桌面的应用的应用包名称
     *
     * @return 返回包含所有包名的字符串列表
     */

    /**
     * 获得属于桌面的应用的应用包名称
     * 返回包含所有包名的字符串列表数组
     *
     * @return
     */
    private List<String> getHomeApplicationList() {
        List<String> names = new ArrayList<String>();
        PackageManager packageManager = this.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resolveInfos) {
            names.add(resolveInfo.activityInfo.packageName);
        }
        Log.d("henry", "主屏幕应用列表" + names);
        return names;
    }

    //定义一个更新界面的Handler
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == HANDLE_CHECK_ACTIVITY) {
//                if (isAtHome()) {
                if (!isAdded) {
                    windowManager.addView(btnView, params);
                    isAdded = true;
                    new Thread(new Runnable() {
                        public void run() {
                            for (int i = 0; i < 10; i++) {
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                Message m = new Message();
                                m.what = 2;
                                mHandler.sendMessage(m);
                            }
                        }
                    }).start();
                }
//                } else {
//                    if (isAdded) {
//                        windowManager.removeView(btnView);
//                        isAdded = false;
//                    }
//                }
                mHandler.sendEmptyMessageDelayed(HANDLE_CHECK_ACTIVITY, 100);
            }
        }
    };

    @Override
    public void onDestroy() {
        if (isAdded) {
            windowManager.removeView(btnView);
        }
        mHandler.removeCallbacksAndMessages(null);
        windowManager = null;
        mHandler = null;
        super.onDestroy();
    }
}

别忘了在Manifest文件声明service

        <service android:name="com.henry.windowManagerTest.My_Floating_Window.FloatService" />

看一下实现效果:

在这里插入图片描述

后续增加缩放+MPAndroidChart效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1675222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

超链接a的应用

主要作用&#xff1a;从当前页面进行跳转 1.跳转到页面 <!-- 跳转到其他页面 --><a href"#" target"_blank">鸡你太美</a> <!-- 跳转到本地页面 --><a href"#" target"_self">鸡你太美</a> 2.跳转…

RAG 面向 LLM: 基于检索增强的大语言模型调研

摘要 作为 AI 领域最先进的技术之一,检索增强生成(RAG)技术可以提供可靠和最新的外部知识,为众多任务提供巨大的便利。特别是在 AI 生成内容(AIGC)时代,RAG 中检索强大的提供额外知识的能力使得检索增强生成能够辅助现有生成式 AI 生产高质量输出。最近,大语言模型(LLM)在语言…

Vue3组件库开发项目实战——03封装Button组件/输出vitePress文档

Vue3组件库开发项目实战——01组件开发必备知识导学-CSDN博客 Vue3组件库开发项目实战——02项目搭建&#xff08;配置Eslint/Prettier/Sass/Tailwind CSS/VitePress/Vitest&#xff09;-CSDN博客 在前面两篇博客中&#xff0c;我分别介绍了组件库开发必学知识&#xff0c;以及…

整合Tess4J图文识别技术

仓库地址&#xff1a;https://gitee.com/z3inc/tess4j-demo.git 1. OCR图文识别介绍 OCR&#xff08;全称 Optical Character Recognition&#xff0c;直译为光学字符识别&#xff09;用于图片文字识别&#xff0c;例如 提取图片中车牌号等等。 Java中实现OCR的技术方案有&…

文本到语音的学习笔记:从Docker开始

1.docker 是什么意思&#xff1f; Docker 是一种开源的容器化平台&#xff0c;它允许开发者将应用及其依赖打包到一个轻量级、可移植的容器中&#xff0c;然后可以在任何支持Docker的系统上运行这个应用&#xff0c;而不必担心环境差异导致的问题。 以下是Docker的一些关键特…

品鉴中的精神内涵:如何通过红酒品味生活的美好与哲学

红酒不仅仅是一种物质享受&#xff0c;更是一种精神体验。在品鉴云仓酒庄雷盛红酒的过程中&#xff0c;我们能够品味到生活的美好与哲学&#xff0c;感受到红酒所蕴含的精神内涵。 红酒的精神内涵源于其酿造过程中所融入的时间和匠心。一瓶上好的红酒需要经过长时间的陈年&…

二叉树专题(有关二叉树的相关学习)

二叉树 1.数概念及结构 1.1树的结构 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结…

使用yarn/npm安装插件(涉及electron),总是报错证书错误或者ssl解析错误

同学们可以私信我加入学习群&#xff01; 正文开始 前言一、问题描述二、解决措施总结 前言 最近electron更新到了30大版本&#xff0c;里面更新的一个重大变化是主进程增加了 WebContentsView 和 BaseWindow&#xff0c;对窗口的控制会更加丝滑。 正好最近在做把文章或者视频…

网页转长图插件html2canvas【前端】

网页转长图插件html2canvas【前端】 前言版权开源推荐网页转长图插件html2canvas【前端】wkImageStorage流程使用后端application.propertiesWkConfigShareControllerImageCleanupTask 前端html2canvas.jsshare.htmlshare.jsgetShare.jsgetShare.html 最后 前言 2024-5-10 18:…

linux学习:多媒体开发库SDL+视频、音频、事件子系统+处理yuv视频源

目录 编译和移植 视频子系统 视频子系统产生图像的步骤 api 初始化 SDL 的相关子系统 使用指定的宽、高和色深来创建一个视窗 surface 使用 fmt 指定的格式创建一个像素点​编辑 将 dst 上的矩形 dstrect 填充为单色 color​编辑 将 src 快速叠加到 dst 上​编辑 更新…

SpringAMQP Work Queue 工作队列

消息模型: 代码模拟: 相较于之前的基础队列&#xff0c;该队列新增了消费者 不再是一个&#xff0c;所以我们通过代码模拟出两个consumer消费者。在原来的消费者类里写两个方法 其中消费者1效率高 消费者2效率低 RabbitListener(queues "simple.queue")public voi…

经典文献阅读之--U-BEV(基于高度感知的鸟瞰图分割和神经地图的重定位)

0. 简介 高效的重定位对于GPS信号不佳或基于传感器的定位失败的智能车辆至关重要。最近&#xff0c;Bird’s-Eye-View (BEV) 分割的进展使得能够准确地估计局部场景的外观&#xff0c;从而有利于车辆的重定位。然而&#xff0c;BEV方法的一个缺点是利用几何约束需要大量的计算…

React useEffect Hook: 理解和解决组件双重渲染问题

在React中&#xff0c;useEffect可能会在组件的每次渲染后运行&#xff0c;这取决于它的依赖项。如果你发现useEffect运行了两次&#xff0c;并且你正在使用React 18或更高版本的严格模式&#xff08;Strict Mode&#xff09;&#xff0c;这可能是因为在开发模式下&#xff0c;…

解锁楼宇自动化新维度西门子Insight+BACnet IP I/O控制器

数字城市的楼宇自动化已不再是一个遥不可及的概念&#xff0c;而是成为了现代建筑的标配。特别是在大型商业综合体、高端写字楼和公共设施中&#xff0c;高效的楼宇管理系统是确保环境舒适度与能源效率的关键。当提及楼宇自动化领域的佼佼者&#xff0c;西门子Insight楼宇自动化…

Spring WebFlux:响应式编程

在软件开发领域&#xff0c;随着互联网应用的规模和复杂性不断增加&#xff0c;传统的编程模型逐渐暴露出一些局限性&#xff0c;尤其是在面对高并发、大规模数据流处理等场景时。为了应对这些挑战&#xff0c;响应式编程&#xff08;Reactive Programming&#xff09;应运而生…

AuroraFOC使用指南一(STM32F405双路FOC)

一. 简介 哈喽&#xff0c;感谢各位选择AuroraFOC开发板&#xff0c;在这里将对其进行一个详细的介绍&#xff0c;方便大家使用。并且对提供的工程文件和上位机的操作也进行了详细的说明。 有什么疑问或者好的建议 可以微信联系: WU1356742146 最后再次感谢大家的支持。 Aur…

Transformers中加载预训练模型的过程剖析(一)

使用HuggingFace的Transformers库加载预训练模型来处理下游深度学习任务很是方便,然而加载预训练模型的方法多种多样且过程比较隐蔽,这在一定程度上会给人带来困惑。因此,本篇文章主要讲一下使用不同方法加载本地预训练模型的区别、加载预训练模型及其配置的过程,藉此做个记…

PostgreSQL 用户及授权管理 04:授予及回收权限

PostgreSQL 是一个坚如磐石的数据库&#xff0c;它非常注重安全性&#xff0c;提供了非常丰富的基础设施来处理权限、特权和安全策略。在前面的章节中以我们介绍的基本概念为基础&#xff0c;重新审视角色概念&#xff0c;特别关注授予角色的安全性和权限&#xff08;角色可以是…

Linux/ubuntu build编译make时出现has modification time int the future的问题解决方法

针对Linux由于双系统之间的时间冲突导致linux时间经常变化&#xff0c;出现执行make命令时出现“make[2]: Warning: File xxx.c’ has modification time 1.6e05 s in the future “警告的问题&#xff0c;亦或者虚拟机出现相同的问题。 由于时钟同步问题&#xff0c;出现 warn…

CAST: Cross-Attention in Space and Time for Video Action Recognition

标题&#xff1a;CAST: 时空交叉注意力网络用于视频动作识别 原文链接&#xff1a;2311.18825v1 (arxiv.org)https://arxiv.org/pdf/2311.18825v1 源码链接&#xff1a;GitHub - KHU-VLL/CASThttps://github.com/KHU-VLL/CAST 发表&#xff1a;NeurIPS-2023&#xff08;CCF A…