写点什么

Android- 悬浮窗功能的实现(附 Java、KT 实现源码,Android 黑科技实现原理揭秘

用户头像
Android架构
关注
发布于: 2021 年 11 月 05 日

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=0wmParams!!.x = winManager!!.defaultDisplay.widthwmParams!!.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.LayoutParamsget() {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 orWindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR orWindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCHwmParams!!.width = WindowManager.LayoutParams.WRAP_CONTENTwmParams!!.height = WindowManager.LayoutParams.WRAP_CONTENTreturn 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 = 0private var mTouchStartY: Int = 0private var mTouchCurrentX: Int = 0private var mTouchCurrentY: Int = 0//开始时的坐标和结束时的坐标(相对于自身控件的坐标)private var mStartX: Int = 0private var mStartY: Int = 0private var mStopX: Int = 0private 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.actionwhen (action) {MotionEvent.ACTION_DOWN -> {isMove = falsemTouchStartX = 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 - mTouchStartXwmParams!!.y += mTouchCurrentY - mTouchStartYwinManager!!.updateViewLayout(mFloatingLayout, wmParams)mTouchStartX = mTouchCurrentXmTouchStartY = 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? = nullprivate var wmParams: WindowManager.LayoutParams? = nullprivate var inflater: LayoutInflater? = null//浮动布局 private var mFloatingLayout: View? = nullprivate var linearLayout: LinearLayout? = nullprivate var chronometer: Chronometer? = null


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


inner class MyBinder : Binder() {val service: FloatWinfowServicesget() = 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 = 0private var mTouchStartY: Int = 0private var mTouchCurrentX: Int = 0private var mTouchCurrentY: Int = 0//开始时的坐标和结束时的坐标(相对于自身控件的坐标)private var mStartX: Int = 0private var mStartY: Int = 0private var mStopX: Int = 0private 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.actionwhen (action) {MotionEvent.ACTION_DOWN -> {isMove = falsemTouchStartX = 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 - mTouchStartXwmParams!!.y += mTouchCurrentY - mTouchStartYwinManager!!.updateViewLayout(mFloatingLayout, wmParams)mTouchStartX = mTouchCurrentXmTouchStartY = 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=0wmParams!!.x = winManager!!.defaultDisplay.widthwmParams!!.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.LayoutParamsget() {wmParams = WindowManager.LayoutParams()if (Build.VERSION.SDK_INT >= Bui


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ld.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 orWindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR orWindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCHwmParams!!.width = WindowManager.LayoutParams.WRAP_CONTENTwmParams!!.height = WindowManager.LayoutParams.WRAP_CONTENTreturn 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)}}


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


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

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android-悬浮窗功能的实现(附Java、KT实现源码,Android黑科技实现原理揭秘