Android-悬浮窗功能的实现(附Java、KT实现源码,经验总结

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

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

Android-悬浮窗功能的实现(附Java、KT实现源码,经验总结_第1张图片

七大模块学习资料:如NDK模块开发、Android框架体系架构…

Android-悬浮窗功能的实现(附Java、KT实现源码,经验总结_第2张图片

只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。

这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
**第三,**到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!

由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。如有需要获取完整的资料文档的朋友点击我的【GitHub】免费获取。

适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!

由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。如有需要获取完整的资料文档的朋友点击我的【GitHub】免费获取。

你可能感兴趣的:(程序员,面试,移动开发,android)