Android-悬浮窗功能的实现(附Java、KT实现源码,安卓framework层
@author Huanglinqing
*/
class Main2Activity : AppCompatActivity() {
private val chronometer: Chronometer? = null
private var hasBind = false
private val rangeTime: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main2)
}
fun zoom(v: View) {
if (Build.VERSION.SDK_INT >= Bu
《Android学习笔记总结+最新移动架构视频+⼤⼚安卓⾯试真题+项⽬实战源码讲义》
【docs.qq/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
ild.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() {
Log.d(“RemoteView”, “重新显⽰了”)
//不显⽰悬浮框
if (hasBind) {
unbindService(mVideoServiceConnection)
hasBind = false
}
}
override fun onNewIntent(intent: Intent) {
}
override fun onDestroy() {
}
}
新建悬浮窗Service
新建悬浮窗Service FloatWinfowServices,因为我们使⽤的BindService,我们在onBind⽅法中初始化service中的布局
override fun onBind(intent: Intent): IBinder? {
initWindow()
//悬浮框点击事件的处理
initFloating()
return MyBinder()
}
service中我们通过WindowManager来添加⼀个布局显⽰。
/**
初始化窗⼝
*/
private fun initWindow() {
winManager = SystemService(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(view, 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。
java影视app源码
从效果图中我们可以看到悬浮窗可以拖拽的,所以还要设置触摸事件,当移动距离超过某个值的时候让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 = Int()
mTouchStartY = Int()
mStartX = Int()
mStartY = Int()
}
MotionEvent.ACTION_MOVE -> {
mTouchCurrentX = Int()
mTouchCurrentY = Int()
wmParams!!.x += mTouchCurrentX - mTouchStartX
wmParams!!.y += mTouchCurrentY - mTouchStartY
winManager!!.updateViewLayout(mFloatingLayout, wmParams)
mTouchStartX = mTouchCurrentX
mTouchStartY = mTouchCurrentY
}
MotionEvent.ACTION_UP -> {
mStopX = Int()
mStopY = Int()
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() {
}
/**
悬浮窗点击事件
*/
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 {