前言
本篇以简单的浮窗视频为例, 练习 Service, 浮窗, MediaPlayer视频播放等;
本篇涉及内容:
Service 的基本用法; MediaPlayer 播放本地视频 通过 WindowManager 添加浮窗 Android Result API 自定义协议类, 校验浮窗权限
一、先来张效果图
![在这里插入图片描述](https://img-blog.csdnimg.cn/f5e78a8e5e534b52979bc5a7c5763062.gif#pic_center)
二、使用步骤
1.配置清单文件
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
...
<!-- 读取本地文件需要 -->
android:requestLegacyExternalStorage="true" >
<service android:name=".test.textservice.VideoFloatingService"/>
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 = 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())
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()
}
R.id.tv_two -> {
stopService(Intent(this, VideoFloatingService::class.java))
}
}
}
private fun startFloatingWindow() {
launcher2.launch(null)
}
private val launcher2 = registerForActivityResult(RequestFloating(this)) {
if(it){
toast("有权限")
startService(Intent(this, VideoFloatingService::class.java))
}else{
toast("授权失败")
}
}
4.请求权限
这是 俺自定义的 协议类; 有权限时 同步响应回调; 没有权限, 则跳转开启权限的页面;
class RequestFloating(val context: Context): ActivityResultContract<Unit, Boolean>(){
@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<Boolean>? {
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
当然也可以用下面这段代码:
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.浮窗的页面贴一下
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<SurfaceView
android:id="@+id/sv_video"
android:layout_width="180dp"
android:layout_height="108dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<ImageView
android:id="@+id/iv_play"
style="@style/img_wrap"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@mipmap/video_icon_play"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Activity 的页面就不贴了; 就俩按钮. 至此, 简单的视频浮窗播放 Service 完成 😀
总结
新手还是建议 自己把代码敲一下. 涉及的东西还是不少的. 至于视频播放, 博主会在下一篇文章中, 用画中画方式代替浮窗视频.
上一篇: Service: 二、简单的音乐播放器 下一篇: 酝酿中…
|