Android 实现一个系统级的悬浮秒表

news2024/11/13 10:39:28

前言

由于项目需要将手机录屏和时间日志对应起来,一般的手机录屏只能看到分钟,但是APP的日志输出通常都是秒级别的,于是决定自己手撸一个悬浮秒表(有拖拽效果)。
效果如下
在这里插入图片描述

具体实现

大致的实现思路:

创建一个悬浮窗口,并在该窗口中显示当前时间。它继承自 Service 类,并使用 WindowManager 来管理悬浮窗口的布局和显示。该类还处理悬浮窗口的触摸事件,使用户可以拖动窗口

FloatingImageDisplayService 代码如下:

//继承service类,开启服务通过服务来显示悬浮窗
public class FloatingImageDisplayService extends Service {
    public static boolean isStarted = false;

    private WindowManager windowManager;
    private WindowManager.LayoutParams layoutParams;
    private View displayView;

    @Override
    public void onCreate() {
        super.onCreate();
        // 标记服务已启动
        isStarted = true;
        // 获取WindowManager服务
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        // 初始化WindowManager.LayoutParams对象
        layoutParams = new WindowManager.LayoutParams();
        // 根据Android版本设置窗口类型
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        }

        // 设置窗口的格式
        layoutParams.format = PixelFormat.RGBA_8888;
        // 设置窗口的位置和对齐方式
        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        // 设置窗口的标志,窗口不获取焦点且不拦截触摸事件
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // 设置窗口的宽度和高度
        layoutParams.width = 400;
        layoutParams.height = 200;
        // 设置窗口的初始位置
        layoutParams.x = 300;
        layoutParams.y = 300;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //开启服务后加载悬浮窗
        showFloatingWindow();
        return super.onStartCommand(intent, flags, startId);
    }

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

    @SuppressLint("SetTextI18n")
    private void showFloatingWindow() {
        // 检查是否有悬浮窗权限
        if (Settings.canDrawOverlays(this)) {
            // 获取LayoutInflater服务
            LayoutInflater layoutInflater = LayoutInflater.from(this);
            // 加载悬浮窗布局
            displayView = layoutInflater.inflate(R.layout.image_display, null);
            // 设置触摸监听器,使悬浮窗可以拖动
            displayView.setOnTouchListener(new FloatingOnTouchListener());
            // 获取TextView控件
            TextView textView = displayView.findViewById(R.id.textView);

            // 创建Handler对象
            final Handler handler = new Handler();
            // 启动一个定时任务,每秒更新一次TextView的内容
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // 获取当前时间
                    long currentTime = System.currentTimeMillis();
                    Date date = new Date(currentTime);
                    // 格式化时间戳精确到秒
                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
                    String formattedTime = sdf.format(date);
                    // 设置TextView的文本内容
                    textView.setText(formattedTime + "");
                    // 延迟1秒后再次执行
                    handler.postDelayed(this, 1000);
                }
            });
            // 将悬浮窗添加到窗口中
            windowManager.addView(displayView, layoutParams);

        }
    }

    //监听手势类,实现悬浮窗的拖动
    private class FloatingOnTouchListener implements View.OnTouchListener {
        private int x;
        private int y;

        @Override
        public boolean onTouch(View view, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int nowX = (int) event.getRawX();
                    int nowY = (int) event.getRawY();
                    int movedX = nowX - x;
                    int movedY = nowY - y;
                    x = nowX;
                    y = nowY;
                    layoutParams.x = layoutParams.x + movedX;
                    layoutParams.y = layoutParams.y + movedY;
                    windowManager.updateViewLayout(view, layoutParams);
                    break;
                default:
                    break;
            }
            return false;
        }
    }
}

布局文件image_display.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:background="#90000000"
        android:gravity="center"
        android:text="my is Float"
        android:textColor="@color/white"
        android:textSize="20dp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

服务和activity一样都是需要在AndroidManifest.xml中进行注册,并且对于这种系统级别的弹窗是需要申请系统权限的,

AndroidManifest.xml 进行声明

<!--    申请系统弹窗权限-->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <!--        注册服务-->
        <service android:name=".FloatingImageDisplayService"/>

到这里我们用来运行悬浮窗口的service就完成了。

如何运行

如何想要我们的悬浮秒表真正的运行起来还需要两步操作:
1.开启服务
service 作为四大组件之一,我们需要找一个中介去运行我们的服务,我这里依托一个activity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //直接在Activity中开启服务
        startFloatingImageDisplayService()
    }
    }

2.如何申请权限
系统级别的悬浮窗是需要动态申请权限的,我们通过startActivityForResult 申请

startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName")), 1)

并在onActivityResult处理权限结果

完整代码如下:

@Suppress("DEPRECATION")
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 启动悬浮图片显示服务
        startFloatingImageDisplayService()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        // 检查请求码是否为1
        if (requestCode == 1) {
            // 检查是否有悬浮窗权限
            if (!Settings.canDrawOverlays(this)) {
                Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show()
                // 启动悬浮图片显示服务
                startService(Intent(this@MainActivity, FloatingImageDisplayService::class.java))
            }
        }
    }

    private fun startFloatingImageDisplayService() {
        // 如果服务已经启动,则返回
        if (FloatingImageDisplayService.isStarted) {
            return
        }
        // 检查是否有悬浮窗权限
        if (!Settings.canDrawOverlays(this)) {
            Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT).show()
            // 请求悬浮窗权限
            startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName")), 1)
        } else {
            Toast.makeText(this, "启动服务", Toast.LENGTH_SHORT).show()
            // 启动悬浮图片显示服务
            startService(Intent(this@MainActivity, FloatingImageDisplayService::class.java))
        }
    }
}

end

以上便是实现一个安卓悬浮秒表的全部代码,由于是自己写着玩的,代码比较粗糙,需要的话可以优化下。需要源码的话也可以call我,CSDN这玩意不太好上传

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

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

相关文章

【科普小白】LLM大语言模型的基本原理

一、要了解LLM大模型的基本原理就要先来了解一下自然语言处理&#xff08;NLP&#xff09;。 NLP 是 AI 的一个子领域&#xff0c;专注于使计算机能够处理、解释和生成人类语言&#xff0c;主要任务包括&#xff1a;文本分类、自动翻译、问题回答、生成文本等。到底是NLP促生了…

Go语言开发商城管理后台-GoFly框架商城插件已发布 需要Go开发商城的朋友可以来看看哦!

温馨提示&#xff1a;我们分享的文章是给需要的人&#xff0c;不需要的人请绕过&#xff0c;文明浏览&#xff0c;误恶语伤人&#xff01; 前言 虽然现在做商城的需求不多&#xff0c;但有很多项目中带有商城功能&#xff0c;如社区医院系统有上服务套餐、理疗产品需求、宠物…

ts 如何配置引入 json 文件

ts 如何配置引入 json 文件 参考文档&#xff1a; https://maxgadget.dev/article/how-to-import-a-json-file-in-typescript-a-comprehensive-guide 项目中有一个 .json 的文件是配置文件&#xff0c;如何引入到 ts 项目中 配置 tsconfig.json 文件&#xff0c;添加这两个 {…

如何让ffmpeg运行时从当前目录加载库,而不是从/lib64

程序在linux下运行时&#xff0c;一般从 /lib64 目录下加载依赖的库文件&#xff0c;如xxx.so. 有时候&#xff0c;系统里没有这些库&#xff0c;也不想从系统目录下加载&#xff0c;怎么办呢&#xff1f; 看下面的调整过程。 使用的源代码是 ffmpeg-6.1.tar.xz 解压后&…

【基于轻量型架构的WEB开发】课程 12.4 页面跳转 Java EE企业级应用开发教程 Spring+SpringMVC+MyBatis

12.4 页面跳转 12.4.1 返回值为void类型的页面跳转 返回值为void类型的页面跳转到默认页面 当Spring MVC方法的返回值为void类型&#xff0c;方法执行后会跳转到默认的页面。默认页面的路径由方法映射路径和视图解析器中的前缀、后缀拼接成&#xff0c;拼接格式为“前缀方法…

订单日记助力“实峰科技”提升业务效率

感谢北京实峰科技有限公司选择使用订单日记&#xff01; 北京实峰科技有限公司&#xff0c;成立于2022年&#xff0c;位于北京市石景区&#xff0c;是一家以从事生产、销售微特电机、输配电及控制设备等业务为主的企业。 在业务不断壮大的过程中&#xff0c;想使用一种既能提…

【Ubuntu24.04】部署服务(基础)

目录 0 背景1 设置静态IP2 连接服务器3 部署服务3.1 安装JDK3.2 下载并安装MySQL8.43.2.1 从官网下载 APT Repository 配置文件3.2.2 安装 MySQL8.43.2.3 配置远程连接 3.3 下载并配置Redis3.4 上传jar包并部署应用3.5 开放端口 4 总结 0 背景 在成功安装了Ubuntu24.04操作系统…

Docker镜像分成

1. 镜像分层原理 1.1 镜像分层的定义与结构 Docker 镜像的分层存储机制是其核心特性之一&#xff0c;它允许 Docker 镜像由多个只读层组成&#xff0c;这些层叠加在一起形成一个完整的文件系统。每个层代表 Dockerfile 中的一个指令&#xff0c;并且每一层都是不可变的&#…

JavaWeb开发9

ResponseBody 类型&#xff1a;方法注解、类注解 位置&#xff1a;Controller方法上/类上 作用&#xff1a;将方法返回值直接响应&#xff0c;如果返回值类型是实体对象/集合&#xff0c;将会转换为JSON格式响应 说明&#xff1a;RestControllerControllerResponseBody; 统…

C#中日期和时间的处理

目录 前言 时间对于我们的作用 一些关于时间的名词说明 格里高利历 格林尼治时间(GMT) 协调世界时(UTC) 时间戳 DateTime 初始化 获取时间 计算时间 字符串转DateTime 存储时间 TimeSpan 初始化它来代表时间间隔 用它相互计算 自带常量方便用于和ticks进行计…

Docker + Jenkins + gitee 实现CICD环境搭建

目录 前言 关于Jenkins 安装Jenkins docker中运行Jenkins注意事项 通过容器中的Jenkins&#xff0c;把服务打包到docker进行部署 启动Jenkins 创建第一个任务 前言 CI/CD&#xff08;持续集成和持续交付/持续部署&#xff09;&#xff0c;它可以实现自动化的构建、测试和部署…

linux之文件(上)

linux之文件&#xff08;上&#xff09; 一.文件的预备知识二.C语言的文件接口和linux的系统接口2.1fopen2.2fclose2.3open2.4close2.5write2.6read 三.文件与系统3.1文件描述符3.2 标准输入&#xff0c;标准输出和标准错误3.3fd的分配规则 四.重定向4.1重定向的概念4.2重定向的…

【进度猫-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

多维视角下的知识管理:Spring Boot应用

2 开发技术 2.1 VUE框架 Vue.js&#xff08;读音 /vjuː/, 类似于 view&#xff09; 是一套构建用户界面的渐进式框架。 Vue 只关注视图层&#xff0c; 采用自底向上增量开发的设计。 Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。 2.2 Mysql数据库 …

【卷积基础】CNN中一些常见卷积(1*1卷积、膨胀卷积、组卷积、深度可分离卷积)

文章目录 逐通道卷积&#xff08;Pointwise Convolution&#xff0c;1x1 卷积&#xff09;主要作用逐通道卷积的操作过程优势代码示例典型应用 膨胀卷积&#xff08;Dilated Convolution&#xff09;主要作用工作原理膨胀率 (dilation rate) 的定义代码实例膨胀卷积的优点 组卷…

算法【Java】—— 动态规划之路径问题

前言 本文章终点解析第一道题目【不同路径】和最后一道题目【地下城游戏】的动态规划思路&#xff0c;中间几道题目会很快过完&#xff0c;大家如果不熟悉动态规划的思路可以重点看一下这两道题目的解析。 不同路径 https://leetcode.cn/problems/unique-paths 解析&#xf…

Linux - 信号

文章目录 一、信号的定义二、查看信号三、产生信号1、指令2、系统调用3、由软件条件产生信号4、异常5、键盘输入 四、保存信号1、补充&#xff1a;信号其他相关概念2、信号保存在哪&#xff0c;怎么保存&#xff1f;3、信号集操作函数 五、捕获信号1、概念2、捕获信号的时机3、…

group_concat配置影响程序出bug

在 ThinkPHP 5 中&#xff0c;想要临时修改 MySQL 数据库的 group_concat_max_len 参数&#xff0c;可以使用 原生 SQL 执行 来修改该值。你可以通过 Db 类来执行 SQL 语句&#xff0c;从而修改会话&#xff08;Session&#xff09;级别的变量。 步骤 设置 group_concat_max_l…

云专线优势有哪些?对接入网络有什么要求?

云专线是一种连接企业本地数据中心与云服务提供商之间的专用网络连接方式&#xff0c;具有以下优势&#xff1a; 高安全性&#xff1a;云专线提供了物理隔离的数据传输通道&#xff0c;减少了数据在公共互联网上传输时可能遭遇的安全风险。 低延迟&#xff1a;由于是直接连接&a…

【提高篇】3.1 GPIO(二,结构与工作模式介绍)

目录 一,GPIO的基本结构 1.1 保护二极管 1.2 上拉、下拉电阻 1.3 施密特触发器 1.4 P-MOS 管和 N-MOS 管 P-MOS管和N-MOS管的区别 1.5 片上外设 1.6 IDR,ODR,BSRR寄存器 1.6.1 IDR(Input Data Register) 1.6.2 ODR(Output Data Register) 1.6.3 BSRR(Bit Se…