Android悬浮窗实现步骤

news/2024/12/28 22:09:15/

最近想做一个悬浮窗秒表的功能,所以看下悬浮窗具体的实现步骤

1、初识WindowManager

实现悬浮窗主要用到的是WindowManager

@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {...
}

WindowManager是接口类,继承自接口ViewManager,可以通过获取WINDOW_SERVICE系统服务得到。而ViewManager接口有addView方法,我们就是通过这个方法将悬浮窗控件加入到屏幕中去。

2、设置权限

当API Level >= 23,显示悬浮窗功能,需要在清单文件AndroidManifest.xml中添加SYSTEM_ALERT_WINDOW权限,添加这个权限后才可以在其他应用上显示悬浮窗。

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

通过getSystemService方式获取WindowManager

val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager

3、LayoutParam设置

WindowManager的addView方法有两个参数,一个是需要加入的控件对象,另一个参数是WindowManager.LayoutParams对象。

	// view – The view to be added to this window.// params – The LayoutParams to assign to view.public void addView(View view, ViewGroup.LayoutParams params);

其中LayoutParams的type变量,这个变量是用来指定窗口的类型。在设置这个变量时,需要对不同版本的Android系统进行适配。

        val layoutParams = WindowManager.LayoutParams()if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY} else {layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE}

在Android 8.0之前,悬浮窗口设置可以为TYPE_PHONE,这种类型是用于提供用户交互操作的非应用窗口,现在这个类型已弃用了。
而Android 8.0对系统和API行为做了修改,包括使用SYSTEM_ALERT_WINDOW权限的应用无法再使用窗口类型来在其他应用和窗口上方显示提醒窗口:

  • TYPE_PHONE(已弃用)

  • TYPE_PRIORITY_PHONE

  • TYPE_SYSTEM_ALERT

  • TYPE_SYSTEM_OVERLAY

  • TYPE_SYSTEM_ERROR

如果需要实现在其他应用和窗口上方显示提醒窗口,那么必须该为TYPE_APPLICATION_OVERLAY的新类型。

4、检测是否允许开启悬浮窗

开启悬浮窗之前,还需要检测用户是否允许开启悬浮窗,通过系统提供的canDrawOverlays来检测

//检测是否允许开启悬浮窗
Settings.canDrawOverlays(context)

如果没有允许开启,需要跳转开启页面,让用户允许开启悬浮窗

startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION))

5、FloatingService服务

悬浮窗一直显示在其他应用上层,需要新建FloatingService服务类,用于处理悬浮窗相关逻辑。

class FloatingService : Service() {override fun onCreate() {super.onCreate()}override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {showFloatingWindow();return super.onStartCommand(intent, flags, startId)}override fun onBind(intent: Intent?): IBinder? {return null}/*** 显示悬浮窗*/private fun showFloatingWindow() {// 获取WindowManager服务val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager// 新建悬浮窗控件val button = Button(applicationContext)button.text = "Floating Window"button.setBackgroundColor(Color.BLUE)// 设置LayoutParamval layoutParams = WindowManager.LayoutParams()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_8888layoutParams.flags =WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLElayoutParams.width = ActionBar.LayoutParams.WRAP_CONTENTlayoutParams.height = ActionBar.LayoutParams.WRAP_CONTENTlayoutParams.x = 300layoutParams.y = 300// 将悬浮窗控件添加到WindowManagerwindowManager.addView(button, layoutParams);}
}

6、启动FloatingService

       viewBinding.btnFloating.setOnClickListener {if (Settings.canDrawOverlays(this)) {//检测是否具有悬浮窗权限startService(Intent(this,FloatingService::class.java))} else {startActivity(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));}}

开启效果如下:
在这里插入图片描述

7、增加拖动功能

悬浮窗显示的位置可能会遮挡其他信息,这时就需要新增拖动功能,可以拖动到任何位置,实现的逻辑就是给布局View添加触摸事件,根据触摸和移动的位置来决定悬浮窗显示的位置。

        var x = 0var y = 0button.setOnTouchListener { view, event ->when (event.action) {MotionEvent.ACTION_DOWN -> {x = event.rawX.toInt()y = event.rawY.toInt()}MotionEvent.ACTION_MOVE -> {val nowX = event.rawX.toInt()val nowY = event.rawY.toInt()val movedX = nowX - xval movedY = nowY - yx = nowXy = nowYlayoutParams.x = layoutParams.x + movedXlayoutParams.y = layoutParams.y + movedY// 更新悬浮窗控件布局windowManager.updateViewLayout(view, layoutParams)}else -> {}}false}

8、图片自动播放

效果图如下:
在这里插入图片描述

页面布局layout_floating_image.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="wrap_content"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/imgView"android:layout_width="wrap_content"android:contentDescription="@null"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"android:layout_height="wrap_content"/></androidx.constraintlayout.widget.ConstraintLayout>

FloatingImageService服务如下:

class FloatingImageService : Service() {override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {showFloatingWindow();return super.onStartCommand(intent, flags, startId)}override fun onBind(intent: Intent?): IBinder? {return null}/*** 显示悬浮窗*/@SuppressLint("ClickableViewAccessibility", "InflateParams")private fun showFloatingWindow() {// 获取WindowManager服务val windowManager = getSystemService(WINDOW_SERVICE) as WindowManager// 获取悬浮窗布局val viewBinding = LayoutFloatingImageBinding.inflate(LayoutInflater.from(this))val imageArray = intArrayOf(R.drawable.pic1, R.drawable.pic2, R.drawable.pic3)var imageIndex = 0viewBinding.imgView.setImageResource(imageArray[imageIndex])val job = Job()val scope = CoroutineScope(job)scope.launch {while (true) {delay(2000)imageIndex++if (imageIndex == imageArray.size) {imageIndex = 0}withContext(Dispatchers.Main) {viewBinding.imgView.setImageResource(imageArray[imageIndex])}}}// 设置LayoutParamval layoutParams = WindowManager.LayoutParams()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_8888layoutParams.gravity = Gravity.START or Gravity.TOPlayoutParams.flags =WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLElayoutParams.width = ActionBar.LayoutParams.WRAP_CONTENTlayoutParams.height = ActionBar.LayoutParams.WRAP_CONTENTlayoutParams.x = 0layoutParams.y = 0// 将悬浮窗控件添加到WindowManagerwindowManager.addView(viewBinding.root, layoutParams)var x = 0var y = 0viewBinding.root.setOnTouchListener { view, event ->when (event.action) {MotionEvent.ACTION_DOWN -> {x = event.rawX.toInt()y = event.rawY.toInt()}MotionEvent.ACTION_MOVE -> {val nowX = event.rawX.toInt()val nowY = event.rawY.toInt()val movedX = nowX - xval movedY = nowY - yx = nowXy = nowYlayoutParams.x = layoutParams.x + movedXlayoutParams.y = layoutParams.y + movedY// 更新悬浮窗控件布局windowManager.updateViewLayout(view, layoutParams)}else -> {}}false}}
}

http://www.ppmy.cn/news/1340548.html

相关文章

【NLP冲吖~】一、朴素贝叶斯(Naive Bayes)

0、朴素贝叶斯法 朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集&#xff0c;首先基于特征条件独立假设学习输入输出的联合概率分布&#xff0c;然后基于此模型&#xff0c;对给定的输入 x x x&#xff0c;利用贝叶斯定理求出后验概率最大的…

如何在Shopee平台上进行手机类目选品?

在Shopee平台上进行手机类目的选品是一个关键而复杂的任务。卖家需要经过一系列的策略和步骤&#xff0c;以确保选品的成功和销售业绩的提升。下面将介绍一些有效的策略&#xff0c;帮助卖家在Shopee平台上进行手机类目选品。 先给大家推荐一款shopee知虾数据运营工具知虾免费…

C# 引用同一个dll不同版本的程序集

因为项目需要所以必须在项目中引用不同版本的同一程序集 我要引用的文件是newtonsoft.json.dll 两个版本为12.0.0.0 和4.0.0.0 1.如果已经先引入了newtonsoft.json 12.0.0.0版本的程序集&#xff0c;如果直接引入另一个版本的程序集的话会提示不成功&#xff0c;所以先将另一个…

c++ 语法指针

指针 1.指针就是一个地址 2. 指针本身也是有地址的 3.取指针所指向的地址保存的值 用变量名取 4.取指针所指向地址保存的值 *变量名取&#xff08;解引用&#xff09; int main(int argc, const char * argv[]) {// insert code here...std::cout << "Hello, …

Leetcode—2670. 找出不同元素数目差数组【简单】

2024每日刷题&#xff08;一零七&#xff09; Leetcode—2670. 找出不同元素数目差数组 哈希表实现代码 class Solution { public:vector<int> distinctDifferenceArray(vector<int>& nums) {unordered_set<int> s;int n nums.size();vector<int&g…

openstack

在虚拟机上安装完openstack之后&#xff0c;根据你想提供的服务&#xff0c;再去安装一些组件(服务)&#xff0c;比如说 (Nova)&#xff1a;用于虚拟机的管理、调度和协调。 (Neutron)&#xff1a;用于管理虚拟网络和网络服务。 (Cinder)&#xff1a;提供块级存储服务&#…

vue 发布自己的npm组件

1、在项目任意位置创建index.ts文件 2、导入要到处的组件&#xff0c;使用vue提供的install 功能全局挂在&#xff1b; import GWButton from "/views/GWButton.vue"; import GWAbout from "/views/AboutView.vue";const components {GWButton,GWAbout, …

C++学习Day01之namespace命名空间

目录 一、程序及输出1.1 命名空间用途&#xff1a; 解决名称冲突1.2 命名空间内容1.3 命名空间必须要声明在全局作用域下1.4 命名空间可以嵌套命名空间1.5 命名空间开放&#xff0c;可以随时给命名空间添加新的成员1.6 命名空间可以是匿名的1.7 命名空间可以起别名 二、分析与总…