Android悬浮窗功能实现

[复制链接]
发表于 2024-10-5 02:37:51 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目录

一.使用悬浮窗功能的原因:
二.悬浮窗的具体实现步骤:
一.添加权限:
二.动态申请权限:
 三.WindowManager.LayoutParams的TYPE类型:
四.编写代码:
1.XML代码:
2.Activity相干代码:
3.WindowManager相干代码 


一.使用悬浮窗功能的原因:

      在Android开辟中我们想要做到提示用户关键信息的作用的时间.比方App更新信息之类的,有很多种方式去实现,紧张的话还是三种方式Dialog,AlertDialog,PopupWindow,但是他们都有一个共同的缺点那就是依靠于Activity,而悬浮窗是不依靠Activity的,甚至,App在背景运行,悬浮窗仍旧会弹出来,只要App进程不被杀死,但是悬浮窗也有缺点,那就是权限标题
二.悬浮窗的具体实现步骤:

一.添加权限:

  1. <!-- 悬浮窗需要添加该权限-->
  2. <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
复制代码
二.动态申请权限:

  1. private fun initFloatWindow() {
  2.         // 权限判断
  3.         if (!Settings.canDrawOverlays(applicationContext)) {
  4.             // 启动Activity让用户授权
  5.             val mIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
  6.             Uri.parse("package:${packageName}"))
  7.             startActivityForResult(mIntent, 10)
  8.         } else {
  9.             // 已经有权限了,就去初始化对应的视图或者悬浮窗弹窗的初始化
  10.             initView()
  11.         }
  12.     }
  13.     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  14.         super.onActivityResult(requestCode, resultCode, data)
  15.         if (resultCode == 10) {
  16.             if (Settings.canDrawOverlays(applicationContext)) {
  17.                 initView()
  18.             } else {
  19.                 ToastUtils.showToast(this, "请设置对应权限")
  20.             }
  21.         }
  22.     }
复制代码
       这段代码是用于在Activity中动态申请悬浮窗权限的,上面谁人 ToastUtils是我本身封装的吐司类,这个很简单,相信大家都会,终极我们进入对应的Activity当中我们会跳转到如许一个界面来获取该应用的悬浮窗权限,像电视大概车载开辟之类的对于权限已经做了静默处理了,总不能我们在使用车载导航的时间,弹出一个是否开启定位吧哈哈
 


 三.WindowManager.LayoutParams的TYPE类型:

     为什么要将这个TYPE类型放在第三位呢,因为这个就是导致各种标题出现的罪魁祸首
  1. type = when {
  2.     Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
  3.     Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 -> WindowManager.LayoutParams.TYPE_PHONE
  4.     else -> WindowManager.LayoutParams.TYPE_TOAST
  5. }
复制代码
  上述这段代码是用来根据不同的Android版本设置窗口类型的,使用的Kotlin的when表达式,来判断设备的Android版本(Build.VERSION.SDK_INT),并根据版本返回不同的窗口类型(TYPE)
  1. 假如 Android 版本大于或等于 Oreo(API 级别 26),则窗口类型为TYPE_APPLICATION_OVERLAY ,这通常用于应用程序的悬浮窗。  
2. 假如 Android 版本大于或等于 Nougat MR1(API 级别 25),则窗口类型为  TYPE_PHONE ,这通常用于电话应用的窗口。 
3. 假如以上两个条件都不满意,则使用  TYPE_TOAST ,这通常用于显示短暂的 Toast 消息。 
  我举一个例子,如下: 

这是因为我的手机为Android7我用的是被镌汰的TYPE类型,这个情况下我们需要更换TYPE类型就能办理对应的标题 
四.编写代码:

1.XML代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent"
  5.     android:orientation="vertical"
  6.     >
  7.     <TextView
  8.         android:background="#f46d43"
  9.         android:paddingVertical="10dp"
  10.         android:gravity="center"
  11.         android:textSize="28sp"
  12.         android:textColor="#ffffff"
  13.         android:text="WindowManager与悬浮框"
  14.         android:layout_width="match_parent"
  15.         android:layout_height="wrap_content"/>
  16.     <Button
  17.         android:textAllCaps="false"
  18.         android:id="@+id/activityshow_btn"
  19.         android:text="Activity显示悬浮框"
  20.         android:layout_width="match_parent"
  21.         android:layout_height="wrap_content"/>
  22.     <Button
  23.         android:textAllCaps="false"
  24.         android:id="@+id/serviceshow_btn"
  25.         android:text="Service显示悬浮框"
  26.         android:layout_width="match_parent"
  27.         android:layout_height="wrap_content"/>
  28.     <Button
  29.         android:textAllCaps="false"
  30.         android:id="@+id/activityshow_btn_cancle"
  31.         android:text="Activity隐藏悬浮框"
  32.         android:layout_width="match_parent"
  33.         android:layout_height="wrap_content"/>
  34. </LinearLayout>
复制代码
2.Activity相干代码:

  1. /**
  2. *  悬浮窗: 不依赖Activity, 当Activity销毁的时候悬浮窗仍然可以显示,注意悬浮窗需要手动获取权限以及声明权限
  3. */
  4. class FloatingWindowActivity : AppCompatActivity(), View.OnClickListener {
  5.     private lateinit var mActivityFloatingWindow: ActivityFloatingWindow
  6.     private val mServiceshowBtn by lazy { findViewById<Button>(R.id.serviceshow_btn) }
  7.     private val mActivityshowBtn by lazy { findViewById<Button>(R.id.activityshow_btn) }
  8.     private val m_activityshow_btn_cancle by lazy { findViewById<Button>(R.id.activityshow_btn_cancle) }
  9.     override fun onCreate(savedInstanceState: Bundle?) {
  10.         super.onCreate(savedInstanceState)
  11.         setContentView(R.layout.activity_floating_window)
  12.         initFloatWindow()
  13.         initListener()
  14.     }
  15.     private fun initFloatWindow() {
  16.         // 权限判断
  17.         if (!Settings.canDrawOverlays(applicationContext)) {
  18.             // 启动Activity让用户授权
  19.             val mIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
  20.             Uri.parse("package:${packageName}"))
  21.             startActivityForResult(mIntent, 10)
  22.         } else {
  23.             // 已经有权限了
  24.             initView()
  25.         }
  26.     }
  27.     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  28.         super.onActivityResult(requestCode, resultCode, data)
  29.         if (resultCode == 10) {
  30.             if (Settings.canDrawOverlays(applicationContext)) {
  31.                 initView()
  32.             } else {
  33.                 ToastUtils.showToast(this, "请设置对应权限")
  34.             }
  35.         }
  36.     }
  37.     private fun initView() {
  38.         mActivityFloatingWindow = ActivityFloatingWindow(this)
  39.     }
  40.     private fun initListener() {
  41.         mServiceshowBtn.setOnClickListener(this)
  42.         mActivityshowBtn.setOnClickListener(this)
  43.         m_activityshow_btn_cancle.setOnClickListener(this)
  44.     }
  45.     override fun onClick(v: View?) {
  46.         when(v?.id) {
  47.             R.id.serviceshow_btn -> {
  48.             }
  49.             R.id.activityshow_btn -> {
  50.                 activityShowFloatingWindow()
  51.             }
  52.             R.id.activityshow_btn_cancle -> {
  53.                 mActivityFloatingWindow.remove()
  54.             }
  55.         }
  56.     }
  57.     private fun activityShowFloatingWindow() {
  58.         mActivityFloatingWindow.showFloatWindow()
  59.     }
  60. }
复制代码
3.WindowManager相干代码 

  1. class ActivityFloatingWindow(context: Context) : View.OnTouchListener {
  2.     private var mContext: Context
  3.     private lateinit var mWindowParams: WindowManager.LayoutParams
  4.     private lateinit var mWindowManager: WindowManager
  5.     private lateinit var rootLayout: View
  6.     init {
  7.         mContext = context
  8.         initFloatWindow()
  9.     }
  10.     private var mInViewX = 0f
  11.     private var mInViewY = 0f
  12.     private var mDownInScreenX = 0f
  13.     private var mDownInScreenY = 0f
  14.     private var mInScreenX = 0f
  15.     private var mInScreenY = 0f
  16.     var isMoving = false
  17.     /**
  18.      *  初始化布局
  19.      */
  20.     private fun initFloatWindow() {
  21.         rootLayout = LayoutInflater.from(mContext)
  22.             .inflate(R.layout.floatingwidow_in_activity, null)
  23.         rootLayout.setOnTouchListener(this)
  24.         mWindowParams = WindowManager.LayoutParams()
  25.         mWindowManager = MyApp.mApplicationContext?.getSystemService(Context.WINDOW_SERVICE) as WindowManager
  26.         mWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE
  27.         mWindowParams.format = PixelFormat.RGBA_8888
  28.         // 设置悬浮窗不获取焦点的原因就是为了传递事件
  29.         mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  30.         mWindowParams.gravity = Gravity.START or Gravity.TOP
  31.         mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT
  32.         mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT
  33.         defaultPosition()
  34.     }
  35.     fun showFloatWindow() {
  36.         if (null == rootLayout.parent) {
  37.             mWindowManager.addView(rootLayout, mWindowParams)
  38.         }
  39.     }
  40.     fun remove() {
  41.         if (null != rootLayout.parent) {
  42.             mWindowManager.removeView(rootLayout)
  43.         }
  44.     }
  45.     override fun onTouch(v: View?, event: MotionEvent?): Boolean {
  46.        return floatLayoutTouch(event!!)
  47.     }
  48.     private fun floatLayoutTouch(motionEvent: MotionEvent): Boolean {
  49.         when (motionEvent.action) {
  50.             MotionEvent.ACTION_DOWN -> {
  51.                 // 获取相对View的坐标,即以此View左上角为原点
  52.                 mInViewX = motionEvent.x
  53.                 mInViewY = motionEvent.y
  54.                 // 获取相对屏幕的坐标,即以屏幕左上角为原点
  55.                 mDownInScreenX = motionEvent.rawX
  56.                 mDownInScreenY = motionEvent.rawY
  57.                 mInScreenX = motionEvent.rawX
  58.                 mInScreenY = motionEvent.rawY
  59.                 isMoving = true
  60.             }
  61.             MotionEvent.ACTION_MOVE -> {
  62.                 // 更新浮动窗口位置参数
  63.                 mInScreenX = motionEvent.rawX
  64.                 mInScreenY = motionEvent.rawY
  65.                 mWindowParams.x = (mInScreenX - mInViewX).toInt()
  66.                 mWindowParams.y = (mInScreenY - mInViewY).toInt()
  67.                 // 手指移动的时候更新小悬浮窗的位置
  68.                 mWindowManager.updateViewLayout(rootLayout, mWindowParams)
  69.                 isMoving = true
  70.             }
  71.             MotionEvent.ACTION_UP -> {
  72.                 isMoving = false
  73.                 // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。
  74.                 if (mDownInScreenX == mInScreenX && mDownInScreenY == mInScreenY) {
  75.                     Toast.makeText(mContext, "Click", Toast.LENGTH_SHORT).show()
  76.                 }
  77.             }
  78.         }
  79.         return true
  80.     }
  81.     private fun defaultPosition() {
  82.         val metrics = DisplayMetrics()
  83.         // 默认固定位置,靠屏幕左边缘的中间
  84.         mWindowManager.defaultDisplay.getMetrics(metrics)
  85.         mWindowParams.x = 0
  86.         mWindowParams.y = metrics.heightPixels/2
  87.     }
  88. }
复制代码
 !!!这里的WIndowManager相干的代码我保举大家使用单例,我这里没有使用单例,运行结果如下:

 


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表