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 = falseprivate 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? = nulloverride 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(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 = falseprivate 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(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图标进入的是第一个栈,这个时候用户就没有进入通话页面的入口了。

解决方案:

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