Android 实现悬浮窗功能

前言

我们大多数在两种情况下可以看到悬浮窗,一个是视频通话时的悬浮窗,另一个是360卫士的悬浮球,实现此功能的方式比较多,这里以视频通话悬浮窗中的需求为例。编码实现使用Kotlin。Java版本留言邮箱即可。

业务场景

以微信视频通话为例,在视频通话时,我们打开其他应用或点击Home键退出时或点击缩放图标,悬浮窗会显示在其他应用之上,给人的假象是通话页面变小了,点击悬浮窗回到通过页面,悬浮窗消失。退出通话页面悬浮窗消失。

业务场景技术分析

在编码之前,我们必须将流程整理好,这样更有利于编码的实现。实现一个功能如果需要10分钟,思考的时间是7分钟,编码占用的时间只是三分钟。

1.悬浮窗可以显示在其他应用或launchers之上,这个肯定需要悬浮窗权限,而悬浮窗权限属于特殊权限,所以只能通过引导用户去打开无法像危险权限那样直接申请。可以做到后台显示则说明悬浮窗是一个Service。

2.通话页面隐藏时悬浮窗显示,通话页面显示时悬浮窗隐藏,可以看出悬浮窗和Activity的生命周期相关联,所以悬浮窗的Service和通话页面的Activity是通过bind去绑定的。

3.既然Service和Activity是通过bind去绑定的,说明当悬浮窗显示的时候,通话Activity虽然不可见但仍在运行。

结合上述技术问题分析,我们倒叙一一通过编码实现

悬浮窗实现方案

实现效果

准备工作

首先我们新建一个项目,项目中有两个Activity,我们在第二个Activity编写通话模拟页面。在第二个页面的原因我们后面会讲到。

如何将acitivity置于后台

其实很简单,我们调用一个方法即可

moveTaskToBack(true);

这个方法的含义就是将当前的任务战置于后台,so,为什么我要在第二个Activity中实现的原因之一,因为默认的Activity的启动模式是标准模式,而上面方法会将任务栈置于后台而不是一个单独的Activity,所以我们为了显示悬浮窗时不影响操作软件的其他功能,我们要将通话页面的Activity设置为singleInstance,这样当调用上面方法的时候只是将通话页面所在的Activity栈置于后台,如果你还不了解启动模式可以移步至上一篇文章:Activity的启动模式。

我们现在在右上方的点击事件中添加上述代码,可以看到通话页面的Activity的已经在后台运行了。

判断是否有悬浮窗权限

点击左上角图标时,我们要先判断当前app是否有悬浮窗权限,首先我们在配置文件中添加,悬浮窗的权限。

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

(很多文章标题都是悬浮窗如何绕过权限,什么设置类型为TOAST或者PHONE,我想说不可能的事,TOAST类型的虽然部分机型可以显示但是就是一个普通的TOSAT会自动消失)

那么我们如何判断是否有悬浮窗权限呢,这一块不同厂商处理方案可能不一样,这里我们用一种通用的处理方案,测试表明除了(vivo部分)无效,其他多数机型都ok。并且vivo部分机型微信通话也不会弹出提示(这我就放心了~)

fun zoom(v: View) {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (!Settings.canDrawOverlays(this)) {
      Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT)
      GlobalDialogSingle(this, "", "当前未获取悬浮窗权限", "去开启", DialogInterface.OnClickListener { dialog, which ->
        dialog.dismiss()
        startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName)), 0)
      }).show()

    } else {
      moveTaskToBack(true)
      val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
      hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
    }
  }
}

我们通过Settings.canDrawOverlays(this)来判断当前应用是否有悬浮窗权限,如果没有,我们弹窗提示,通过

startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName)), 0)

跳转到开启悬浮窗权限页面。如果悬浮窗权限已开启,直接将当前任务栈置于后台,开启服务即可。

其实回调方法,并没有直接告诉我们是否授权成功,所以我们需要在回调中再次判断

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
  if (requestCode == 0) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      if (!Settings.canDrawOverlays(this)) {
        Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show()
      } else {
        Handler().postDelayed({
          val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
          intent.putExtra("rangeTime", rangeTime)
          hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
          moveTaskToBack(true)
        }, 1000)

      }
    }
  }
}

这里我们可以看到回调中延迟了1秒,因为测试发现某些机型反应“过快”,收到回调的时候还以为没有授权成功,其实已经成功了。

绑定Service我们需要一个ServiceConnection对象

internal var mVideoServiceConnection: ServiceConnection = object : ServiceConnection {
  override fun onServiceConnected(name: ComponentName, service: IBinder) {
    // 获取服务的操作对象
    val binder = service as FloatWinfowServices.MyBinder
    binder.service
  }
  override fun onServiceDisconnected(name: ComponentName) {}
}

Main2Activity的完整代码如下所示:

/**
 * @author Huanglinqing
 */
class Main2Activity : AppCompatActivity() {
  private val chronometer: Chronometer? = null
  private var hasBind = false
  private val rangeTime: Long = 0
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main2)
  }
  fun zoom(v: View) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      if (!Settings.canDrawOverlays(this)) {
        Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT)
        GlobalDialogSingle(this, "", "当前未获取悬浮窗权限", "去开启", DialogInterface.OnClickListener { dialog, which ->
          dialog.dismiss()
          startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName)), 0)
        }).show()
      } else {
        moveTaskToBack(true)
        val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
        hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
      }
    }
  }
  internal var mVideoServiceConnection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
      // 获取服务的操作对象
      val binder = service as FloatWinfowServices.MyBinder
      binder.service
    }
    override fun onServiceDisconnected(name: ComponentName) {}
  }
  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    if (requestCode == 0) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (!Settings.canDrawOverlays(this)) {
          Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show()
        } else {
          Handler().postDelayed({
            val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
            intent.putExtra("rangeTime", rangeTime)
            hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
            moveTaskToBack(true)
          }, 1000)
        }
      }
    }
  }
  override fun onRestart() {
    super.onRestart()
    Log.d("RemoteView", "重新显示了")
    //不显示悬浮框
    if (hasBind) {
      unbindService(mVideoServiceConnection)
      hasBind = false
    }
  }
  override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
  }
  override fun onDestroy() {
    super.onDestroy()
  }
}

新建悬浮窗Service

新建悬浮窗Service FloatWinfowServices,因为我们使用的BindService,我们在onBind方法中初始化service中的布局

override fun onBind(intent: Intent): IBinder? {
  initWindow()
  //悬浮框点击事件的处理
  initFloating()
  return MyBinder()
}

service中我们通过WindowManager来添加一个布局显示。

/**
 * 初始化窗口
 */
private fun initWindow() {
  winManager = application.getSystemService(Context.WINDOW_SERVICE) as WindowManager
  //设置好悬浮窗的参数
  wmParams = params
  // 悬浮窗默认显示以左上角为起始坐标
  wmParams!!.gravity = Gravity.LEFT or Gravity.TOP
  //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
  wmParams!!.x = winManager!!.defaultDisplay.width
  wmParams!!.y = 210
  //得到容器,通过这个inflater来获得悬浮窗控件
  inflater = LayoutInflater.from(applicationContext)
  // 获取浮动窗口视图所在布局
  mFloatingLayout = inflater!!.inflate(R.layout.remoteview, null)
  // 添加悬浮窗的视图
  winManager!!.addView(mFloatingLayout, wmParams)
}

悬浮窗的参数主要设置悬浮窗的类型为

WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY

8.0 以下可设置为:

wmParams!!.type = WindowManager.LayoutParams.TYPE_PHONE

代码如下所示:

private //设置window type 下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上
    //设置可以显示在状态栏上
    //设置悬浮窗口长宽数据
val params: WindowManager.LayoutParams
  get() {
    wmParams = WindowManager.LayoutParams()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      wmParams!!.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
    } else {
      wmParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
    }
    wmParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
    wmParams!!.width = WindowManager.LayoutParams.WRAP_CONTENT
    wmParams!!.height = WindowManager.LayoutParams.WRAP_CONTENT
    return wmParams
  }

当点击悬浮窗的时候回到Activity2页面,并且悬浮窗消失,所以我们只需要给悬浮窗添加点击事件

linearLayout!!.setOnClickListener { startActivity(Intent(this@FloatWinfowServices, Main2Activity::class.java)) }

当Service走到onDestory的时候将view移除,对于Activity2页面来说 当onResume的时候 解绑Service,当onstop的时候 绑定Service。

从效果图中我们可以看到悬浮窗可以拖拽的,所以还要设置触摸事件,当移动距离超过某个值的时候让onTouch消费事件,这样就不会触发点击事件了。这个算是view比较基础的知识,相信大家都明白了。

//开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
private var mTouchStartX: Int = 0
private var mTouchStartY: Int = 0
private var mTouchCurrentX: Int = 0
private var mTouchCurrentY: Int = 0
//开始时的坐标和结束时的坐标(相对于自身控件的坐标)
private var mStartX: Int = 0
private var mStartY: Int = 0
private var mStopX: Int = 0
private var mStopY: Int = 0
//判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
private var isMove: Boolean = false
private inner class FloatingListener : View.OnTouchListener {
  override fun onTouch(v: View, event: MotionEvent): Boolean {
    val action = event.action
    when (action) {
      MotionEvent.ACTION_DOWN -> {
        isMove = false
        mTouchStartX = event.rawX.toInt()
        mTouchStartY = event.rawY.toInt()
        mStartX = event.x.toInt()
        mStartY = event.y.toInt()
      }
      MotionEvent.ACTION_MOVE -> {
        mTouchCurrentX = event.rawX.toInt()
        mTouchCurrentY = event.rawY.toInt()
        wmParams!!.x += mTouchCurrentX - mTouchStartX
        wmParams!!.y += mTouchCurrentY - mTouchStartY
        winManager!!.updateViewLayout(mFloatingLayout, wmParams)
        mTouchStartX = mTouchCurrentX
        mTouchStartY = mTouchCurrentY
      }
      MotionEvent.ACTION_UP -> {
        mStopX = event.x.toInt()
        mStopY = event.y.toInt()
        if (Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1) {
          isMove = true
        }
      }
      else -> {
      }
    }
    //如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
    return isMove
  }
}

FloatWinfowServices所有代码如下所示:

class FloatWinfowServices : Service() {
   private var winManager: WindowManager? = null
  private var wmParams: WindowManager.LayoutParams? = null
  private var inflater: LayoutInflater? = null
  //浮动布局
  private var mFloatingLayout: View? = null
  private var linearLayout: LinearLayout? = null
  private var chronometer: Chronometer? = null
  override fun onBind(intent: Intent): IBinder? {
    initWindow()
    //悬浮框点击事件的处理
    initFloating()
    return MyBinder()
  }
  inner class MyBinder : Binder() {
    val service: FloatWinfowServices
      get() = this@FloatWinfowServices
  }
  override fun onCreate() {
    super.onCreate()
  }
  /**
   * 悬浮窗点击事件
   */
  private fun initFloating() {
    linearLayout = mFloatingLayout!!.findViewById<LinearLayout>(R.id.line1)
    linearLayout!!.setOnClickListener { startActivity(Intent(this@FloatWinfowServices, Main2Activity::class.java)) }
    //悬浮框触摸事件,设置悬浮框可拖动
    linearLayout!!.setOnTouchListener(FloatingListener())
  }
  //开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
  private var mTouchStartX: Int = 0
  private var mTouchStartY: Int = 0
  private var mTouchCurrentX: Int = 0
  private var mTouchCurrentY: Int = 0
  //开始时的坐标和结束时的坐标(相对于自身控件的坐标)
  private var mStartX: Int = 0
  private var mStartY: Int = 0
  private var mStopX: Int = 0
  private var mStopY: Int = 0
  //判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
  private var isMove: Boolean = false
  private inner class FloatingListener : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean {
      val action = event.action
      when (action) {
        MotionEvent.ACTION_DOWN -> {
          isMove = false
          mTouchStartX = event.rawX.toInt()
          mTouchStartY = event.rawY.toInt()
          mStartX = event.x.toInt()
          mStartY = event.y.toInt()
        }
        MotionEvent.ACTION_MOVE -> {
          mTouchCurrentX = event.rawX.toInt()
          mTouchCurrentY = event.rawY.toInt()
          wmParams!!.x += mTouchCurrentX - mTouchStartX
          wmParams!!.y += mTouchCurrentY - mTouchStartY
          winManager!!.updateViewLayout(mFloatingLayout, wmParams)
          mTouchStartX = mTouchCurrentX
          mTouchStartY = mTouchCurrentY
        }
        MotionEvent.ACTION_UP -> {
          mStopX = event.x.toInt()
          mStopY = event.y.toInt()
          if (Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1) {
            isMove = true
          }
        }
        else -> {
        }
      }
      //如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
      return isMove
    }
  }
  /**
   * 初始化窗口
   */
  private fun initWindow() {
    winManager = application.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    //设置好悬浮窗的参数
    wmParams = params
    // 悬浮窗默认显示以左上角为起始坐标
    wmParams!!.gravity = Gravity.LEFT or Gravity.TOP
    //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
    wmParams!!.x = winManager!!.defaultDisplay.width
    wmParams!!.y = 210
    //得到容器,通过这个inflater来获得悬浮窗控件
    inflater = LayoutInflater.from(applicationContext)
    // 获取浮动窗口视图所在布局
    mFloatingLayout = inflater!!.inflate(R.layout.remoteview, null)
    chronometer = mFloatingLayout!!.findViewById<Chronometer>(R.id.chronometer)
    chronometer!!.start()
    // 添加悬浮窗的视图
    winManager!!.addView(mFloatingLayout, wmParams)
  }
  private //设置window type 下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上
      //设置可以显示在状态栏上
      //设置悬浮窗口长宽数据
  val params: WindowManager.LayoutParams
    get() {
      wmParams = WindowManager.LayoutParams()
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        wmParams!!.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
      } else {
        wmParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
      }
      wmParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
          WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
          WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
      wmParams!!.width = WindowManager.LayoutParams.WRAP_CONTENT
      wmParams!!.height = WindowManager.LayoutParams.WRAP_CONTENT
      return wmParams
    }
  override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
    return super.onStartCommand(intent, flags, startId)
  }
  override fun onDestroy() {
    super.onDestroy()
    winManager!!.removeView(mFloatingLayout)
  }
}

实际应用中需要考虑的一些其他问题

在使用使用的过程中,我们肯定会遇到其他问题:

1.用户使用过程中,可能会直接按Home键,这个时候如何提示呢?

产生问题原因:因为用户按Home键之后,开发者无法重写Home键逻辑,此时应用不在前台运行,无法弹窗提醒,此时用户点击APP图标进入的是第一个栈,这个时候用户就没有进入通话页面的入口了。

解决方案:

第一种解决方案 我们可以仿照微信那样去做,就是在整个通话过程中开启一个前台通知,用户点击通知时进入通话页面。

第二种解决方案 就是检测应用是否在前台,当通话页面在运行的时候,并且应用重新回到前台,我们广播到其他页面,提示权限引导即可。

2.用户在通话页面(singleInstance模式),点击Home键

应用在后台运行的时候,通话结束,Activity被finish,此时从任务程序中切回应用你会发现打开的竟然是通话页面!

这个问题简单的说就是,如果你在通话页面呼叫某人,通话过程中按Home键,然后电话挂断,此时你从任务程序中切回应用,会再次呼叫这个人,也就是这种状态下重新回到了onCreate方法。

问题产生原因:

1.因为通话页面是singleInstance模式,此时有两个任务栈,按Home键后再从任务程序中切回,此时应用只保留了第二个任务栈,已经失去了和第一个任务栈的关系,finish之后无法在回到第一个任务栈。

解决方案:

1.(不推荐)通话页面不使用singleInstance模式,这种情况下,在通话过程中无法操作软件的其他功能,一般都不采取。

2.(我目前的解决方案)设置一个标记位,标记当前是否在通话,在onCreate中如果通话已经结束了,跳转到一个过渡页面(标准模式),过渡页面中finish,就可以了,添加过渡页面的原因是我们不知道上一个页面是哪里,因为我们收到来电可能是任意页面,我们我们在过渡页面finsh之后,就再次回到了第一个任务栈。

总结

以上所述是小编给大家介绍的Android 实现悬浮窗功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

(0)

相关推荐

  • 详解android6.0版本下悬浮窗实现

    悬浮窗在安卓中实现起来还是比较容易的,这几天在网上温习了相关资料,运行在我安卓6.0手机上才发现,原来在6.0手机上不是行的. 第一反应肯定是权限相关问题,做了相关处理后,果然让悬浮窗原形毕露了.直接贴代码. public class MainActivity extends AppCompatActivity { private static final int ALERT_WINDOW_PERMISSION_CODE = 100; private Button start_float; @O

  • Android 悬浮窗权限各机型各系统适配大全(总结)

    这篇博客主要介绍的是 Android 主流各种机型和各种版本的悬浮窗权限适配,但是由于碎片化的问题,所以在适配方面也无法做到完全的主流机型适配,这个需要大家的一起努力,这个博客的名字永远都是一个将来时. 悬浮窗适配 悬浮窗适配有两种方法:第一种是按照正规的流程,如果系统没有赋予 APP 弹出悬浮窗的权限,就先跳转到权限授权界面,等用户打开该权限之后,再去弹出悬浮窗,比如 QQ 等一些主流应用就是这么做得:第二种就是利用系统的漏洞,绕过权限的申请,简单粗暴,这种方法我不是特别建议,但是现在貌似有些

  • Android 8.0如何完美适配全局dialog悬浮窗弹出

    前言 最近项目targetSdkVersion升级到了26,出现很多问题趟了很多坑,其中就包括本篇的需要解决的问题:全局dialog 不显示. 出现场景 有时候我们需要在App中弹dialog,但是却不知道依附的是哪个Activity,这个时候通常会启动一个service来依附,显示一个全局的dialog. Android 6.0出现的悬浮窗权限 为什么从6.0说起? 在Android6.0之后,使用悬浮窗功能需要申请开启悬浮窗权限,在API23以下版本编译,悬浮窗权限关闭的,但是没有权限限制,

  • Android利用WindowManager实现悬浮窗

    前言 你会发现QQ视频的时候,就算手机回到主页,视频小模块依旧能悬浮在桌面上.还有当年很火的各种手机杀毒软件的桌面小助手,总能在呆在桌面.这种悬浮窗的操作就需要用到Window. 效果 gif图看着有点儿卡,其实实际上还是很流畅的. Window Window即窗口,是个抽象类,具体实现就是PhoneWindow,对就是那个装着DecorView的PhoneWindow. Window整体分三种类型:应用Window.子Window.系统Window. 应用Window:对应一个Activity

  • Android悬浮窗屏蔽悬浮窗外部所有的点击事件的实例代码

    Android可以在所有应用上方添加View,就是给WindowManager添加一个View,在创建的View的时候可以给这个View设置LayoutParams(android.view.WindowManager.LayoutParams.LayoutParams())属性,默认是屏蔽View区域以外的所有点击事件,即在显示View的时候,屏幕的其它区域都不可点击,包括菜单和返回键等. 当给LayoutParams设置合适的flag后,其它区域就可以响应点击了. 示例代码: /** * 创

  • Android实现类似qq微信消息悬浮窗通知功能

    实现方法:(需要开启悬浮窗通知权限.允许应用在其他应用上显示) 一.利用headsup 悬挂式Notification,他是5.0中新增的,也就是API中的Headsup的Notification,可以在不打断用户操作的时候,给用户通知 二.使用Window创建悬浮窗 当window属性设置为FLAGE_NOT_FOCUSABLE表示不需要获取焦点,也不需要接受各种输入事件,此标记会同时启用FLAGE_NOT_TOUCH_MODEL,最终事件会直接传递给下层具有焦点的Widow FLAGE_NO

  • Android应用内悬浮窗的实现方案示例

    1.悬浮窗的基本介绍 悬浮窗,大家应该也不陌生,凌驾于应用之上的一个小弹窗,实现上很简单,就是添加一个系统级别的窗口,Android中通过WindowManagerService( WMS)来管理所有的窗口,对于WMS来说,管你是Activity.Toast.Dialog,都不过是通过WindowManagerGlobal.addView()添加的一个个View. Android中的窗口分为三个级别: 1.1 应用窗口,比如Activity的窗口; 1.2 子窗口,依赖于父窗口,比如PopupW

  • Android 实现悬浮窗功能

    前言 我们大多数在两种情况下可以看到悬浮窗,一个是视频通话时的悬浮窗,另一个是360卫士的悬浮球,实现此功能的方式比较多,这里以视频通话悬浮窗中的需求为例.编码实现使用Kotlin.Java版本留言邮箱即可. 业务场景 以微信视频通话为例,在视频通话时,我们打开其他应用或点击Home键退出时或点击缩放图标,悬浮窗会显示在其他应用之上,给人的假象是通话页面变小了,点击悬浮窗回到通过页面,悬浮窗消失.退出通话页面悬浮窗消失. 业务场景技术分析 在编码之前,我们必须将流程整理好,这样更有利于编码的实现

  • Android 实现可任意拖动的悬浮窗功能(类似悬浮球)

    最近开发项目中,有个在屏幕上任意拖动的悬浮窗功能,其实就是利用 WindowManager的api来完成这个需求,具体的实现的功能如下: 1.自定义view import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Message; import android.util.Log; import android.util.TypedValu

  • 微信小程序中悬浮窗功能的实现代码

    问题场景 所谓悬浮窗就是图中微信图标的按钮,采用fixed定位,可拖动和点击. 这算是一个比较常见的实现场景了. 为什么要用cover-view做悬浮窗?原生组件出来背锅了~ 最初我做悬浮窗用的不是cover-view,而是view. 这是简化的代码结构: index.wxml: <view class="move-view" style=" top:{{top}}px;left:{{left}}px;" bindtap="goToHome"

  • Android实现悬浮窗的简单方法实例

    目录 1. 前言 2.原理 3.具体实现 3.1浮窗布局 3.2 悬浮窗的实现 1. 使用服务Service 2. 获取WindowManager并设置LayoutParams 3. 创建View并添加到WindowManager 4. 实现悬浮窗的拖拽和关闭功能 5. 利用广播进行通信 6. 设置权限 3.3 完整代码 4. 总结 1. 前言 现在很多应用都有小悬浮窗的功能,比如看直播的时候,通过Home键返回桌面,直播的小窗口仍可以在屏幕上显示.下面将介绍下悬浮窗的的一种简单实现方式. 2.

  • Kotlin实现Android系统悬浮窗详解

    目录 Android 弹窗浅谈 系统悬浮窗具体实现 权限申请 代码设计 具体实现 FloatWindowService 类 FloatWindowManager 类 FloatWindowManager 类代码 FloatLayout 类及其 Layout HomeKeyObserverReceiver 类 FloatWindowUtils 类 总结 Android 弹窗浅谈 我们知道 Android 弹窗中,有一类弹窗会在应用之外也显示,这是因为他被申明成了系统弹窗,除此之外还有2类弹窗分别是

  • android桌面悬浮窗显示录屏时间控制效果

    本文实例为大家分享了android桌面悬浮窗,实现录屏时间控制显示效果的具体代码,供大家参考,具体内容如下 悬浮窗效果如上图所示: 很简单的一个布局直接上代码 悬浮窗布局如下record_screen_time_float.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/an

  • Android创建悬浮窗的完整步骤

    在Android中想要创建悬浮窗分为三步 1.申请权限 2.使用服务启动悬浮窗 3.设置悬浮窗参数并添加进WindowManager 下面话不多说了,来一起看看详细的实现过程 申请权限 首先需要申请悬浮窗权限,在清单文件中 manifest 下添加 <!-- 低版本悬浮窗所需权限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> 在Activity中动态申请权

  • Android实现悬浮窗效果

    本文实例为大家分享了Android实现悬浮窗效果的具体代码,供大家参考,具体内容如下 一.权限: <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 二.悬浮窗其实就是 WindowManager.addView(view,layoutParams),直接上代码 1.单例创建FloatWindowManager  /**   悬浮Manager */ public class F

  • 不依赖于Activity的Android全局悬浮窗的实现

    前言 当我们在手机上使用360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口).那么这种不受Activity界面影响的悬浮窗口是怎么实现的呢? Android悬浮窗实现 实现基础 Android悬浮窗实现使用WindowManager WindowManager介绍 通过Context.getSystemService(Context.WINDOW_S

  • Android实现悬浮窗全系统版本

    悬浮窗是在系统上显示的内容,好像微信视频聊天时的小窗口一样,在退出软件后依然存在的一个窗口,本博客以窗口中放一个button组件为例,简单展示悬浮窗,其中包括了对Android 6.0以下.Android 6.0到Android 8.0.Android 8.0以上版本的处理,下面开始介绍实现方法: 1.MainActivity中的代码 public Button mFloatingButton; @Override protected void onCreate(Bundle savedInst

随机推荐