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

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

前言

由于项目需要将手机录屏和时间日志对应起来,一般的手机录屏只能看到分钟,但是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.kler.cn/a/387957.html

相关文章:

  • openSUSE 环境下通过 zypper 安装软件
  • MTSET可溶于DMSO、DMF、THF等有机溶剂,并在水中有轻微的溶解性,91774-25-3
  • 如何为电子课程创造创意
  • Xshell,Shell的相关介绍与Linux中的权限问题
  • Toeplitz矩阵循环矩阵
  • 第8章利用CSS制作导航菜单
  • 基于 STM32 的天气时钟项目中添加天气数据的网络获取功能
  • Edge浏览器打开PDF无法显示电子签章
  • mac 本地docker-mysql主从复制部署
  • Hive-testbench套件使用文档
  • Matlab绘制箭头(annotation 、quiver、​quiver3)
  • Python批量合并多个PDF
  • 【Ubuntu24.04】从双系统到虚拟机再到单系统的故事
  • 产品如何3D建模?如何根据使用场景选购3D扫描仪?
  • 书生大模型第四期闯关任务与笔记
  • 在 WPF 中,绑定机制是如何工作的?WPF数据绑定机制解析
  • onnx-runner:使用ORT运行YOLO的ONNX模型
  • 安全关键型嵌入式系统设计模式整理及应用实例
  • Flink的流、批处理
  • 【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
  • 使用ref操作DOM(React)
  • Qt菜单功能实现
  • 【Ant.designpro】上传图片
  • LeetCode-两整数之和
  • 清华大学提出Mini-Omni2:开源多模态模型,功能与GPT-4o媲美!
  • 继承的学习