- 前言
- 一、先来张效果图
- 二、使用步骤
- 1.配置清单文件
- 2.编写 Service
- 3. Activity
- 4.请求权限
- 5.浮窗的页面贴一下
- 总结
前言
本篇以简单的浮窗视频为例, 练习 Service, 浮窗, MediaPlayer视频播放等;
本篇涉及内容:
- Service 的基本用法;
- MediaPlayer 播放本地视频
- 通过 WindowManager 添加浮窗
- Android Result API 自定义协议类, 校验浮窗权限
一、先来张效果图
 二、使用步骤 1.配置清单文件
2.编写 Service
这次的 Service 并没有 上一篇 简单的音乐播放器 中的 Service 复杂;
只需要初始化 MediaPlayer, 播放视频. 添加悬浮窗. 以及内部简单的控制逻辑;
class VideoFloatingService: Service() {
private val TAG = "VideoFloatingService"
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
private lateinit var binding: ViewVideoFloatingBinding
private lateinit var mediaPlayer: MediaPlayer
override fun onCreate() {
super.onCreate()
init() // 初始化播放器
showFloatingWindow() // 添加浮窗
}
override fun onBind(intent: Intent?): IBinder? = null
private fun showFloatingWindow() {
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
// 初始化浮窗 layoutParams
layoutParams = WindowManager.LayoutParams().also {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
it.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
it.type = WindowManager.LayoutParams.TYPE_PHONE
}
it.format = PixelFormat.RGBA_8888
it.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
// 浮窗位置和尺寸, 偏移量等
it.gravity = Gravity.CENTER_VERTICAL or Gravity.START
it.width = WindowManager.LayoutParams.WRAP_ConTENT
it.height = WindowManager.LayoutParams.WRAP_ConTENT
it.x = 0
it.y = 0
}
// 初始化浮窗布局, 为播放按钮添加事件; 为整个浮窗添加触摸监听(满足拖动, 点击等)
binding = ViewVideoFloatingBinding.inflate(LayoutInflater.from(this))
binding.ivPlay.setonClickListener {
binding.ivPlay.setImageResource(if(mediaPlayer.isPlaying){
mediaPlayer.pause()
R.mipmap.video_icon_play
}else{
mediaPlayer.start()
R.mipmap.video_icon_suspend
})
}
// 添加拖拽事件
binding.root.setOnTouchListener(FloatingListener())
// 要等 SurfaceHolder Created
binding.svVideo.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
mediaPlayer.setDisplay(binding.svVideo.holder)
}
override fun surfaceChanged(s: SurfaceHolder, f: Int, w: Int, h: Int ) {}
override fun surfaceDestroyed(holder: SurfaceHolder) {}
})
// 添加浮窗
windowManager.addView(binding.root, layoutParams)
}
private fun init() {
mediaPlayer = MediaPlayer().also {
it.isLooping = true // 循环播放
val file = File(Environment.getExternalStorageDirectory(),"big_buck_bunny.mp4")
it.setDataSource(file.path)
it.prepareAsync()
it.setonPreparedListener {
mediaPlayer.start()
}
}
}
override fun onDestroy() {
mediaPlayer.stop()
mediaPlayer.release()
windowManager.removeView(binding.root)
super.onDestroy()
}
inner class FloatingListener: View.OnTouchListener{
private var x = 0 // 当前位置值
private var y = 0
private var cx = 0 // 点击初始值;
private var cy = 0
private var checkClick: Boolean = false
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
checkClick = true
cx = event.rawX.toInt()
cy = event.rawY.toInt()
x = cx
y = cy
}
MotionEvent.ACTION_MOVE -> {
val nowX = event.rawX.toInt()
val nowY = event.rawY.toInt()
val movedX = nowX - x
val movedY = nowY - y
x = nowX
y = nowY
layoutParams.let {
it.x = it.x + movedX
it.y = it.y + movedY
}
// 更新悬浮窗控件布局
windowManager.updateViewLayout(binding.root, layoutParams)
if (checkClick && (abs(x - cx) >= 5 || abs(y - cy) >= 5)) {
checkClick = false
}
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
if (checkClick && abs(cx - event.rawX.toInt()) < 5 && abs(cy - event.rawY.toInt()) < 5) {
// 判断为 浮窗点击事件; 控制播放按钮显隐;
if(binding.ivPlay.visibility == View.VISIBLE){
binding.ivPlay.visibility = View.GONE
}else{
binding.ivPlay.visibility = View.VISIBLE
binding.ivPlay.setImageResource(if(mediaPlayer.isPlaying){
R.mipmap.video_icon_suspend
} else {
R.mipmap.video_icon_play
})
}
}
checkClick = false
}
}
return true
}
}
}
代码看起来不少, 但逻辑却很简单; 总体来看只有 初始化播放器, 添加浮窗, 添加触摸监听 等;
3. Activity
Activity 就很简单了, 先校验或请求一下权限. 然后直接启动 Service 就完事了;
// 点击事件
fun onClick(v: View) {
when(v.id){
R.id.tv_one -> {
startFloatingWindow() // 检验权限后 开启Service
}
R.id.tv_two -> {
stopService(Intent(this, VideoFloatingService::class.java))
}
}
}
private fun startFloatingWindow() {
// Android Result Api 的方式, 请求权限
launcher2.launch(null)
}
private val launcher2 = registerForActivityResult(RequestFloating(this)) {
if(it){
// toast("") 这是 kotlin 自定义的扩展函数.
toast("有权限")
// 有了权限, 直接启动 Service 即可
startService(Intent(this, VideoFloatingService::class.java))
}else{
toast("授权失败")
}
}
4.请求权限
这是 俺自定义的 协议类; 有权限时 同步响应回调; 没有权限, 则跳转开启权限的页面;
class RequestFloating(val context: Context): ActivityResultContract(){ @RequiresApi(Build.VERSION_CODES.M) override fun createIntent(context: Context, input: Unit?) = Intent().also { it.action = Settings.ACTION_MANAGE_OVERLAY_PERMISSION it.data = Uri.parse("package:${context.packageName}") } override fun getSynchronousResult(context: Context, input: Unit?): SynchronousResult ? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) { null } else { SynchronousResult(true) } } override fun parseResult(resultCode: Int, intent: Intent?): Boolean{ return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(context) } }
不了解 Android Result Api 的小伙伴可以看我这篇文章: Android Result API
当然也可以用下面这段代码:
//检查悬浮窗权限,小于6.0系统不需要权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 100);
}else{
//已有权限
}
这种. 还得重写 onActivityResult; 但是 onActivityResult 中并没有结果, 需要自己再判断一遍
5.浮窗的页面贴一下
Activity 的页面就不贴了; 就俩按钮.
至此, 简单的视频浮窗播放 Service 完成
总结
新手还是建议 自己把代码敲一下. 涉及的东西还是不少的. 至于视频播放, 博主会在下一篇文章中, 用画中画方式代替浮窗视频.
上一篇: Service: 二、简单的音乐播放器
下一篇: 酝酿中…



