IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Service: 三、小窗口(浮窗) 播放视频 -> 正文阅读

[移动开发]Service: 三、小窗口(浮窗) 播放视频


前言

本篇以简单的浮窗视频为例, 练习 Service, 浮窗, MediaPlayer视频播放等;

本篇涉及内容:

  • Service 的基本用法;
  • MediaPlayer 播放本地视频
  • 通过 WindowManager 添加浮窗
  • Android Result API 自定义协议类, 校验浮窗权限

一、先来张效果图


![在这里插入图片描述](https://img-blog.csdnimg.cn/f5e78a8e5e534b52979bc5a7c5763062.gif#pic_center)

二、使用步骤

1.配置清单文件

<!-- SD卡读写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<!-- 8.0 以上 前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<!-- 悬浮窗权限 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

<application
	...
	<!-- 读取本地文件需要 -->
	 android:requestLegacyExternalStorage="true" >
	
	<!-- 自定义的视频浮窗 Service -->
	<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
        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<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

当然也可以用下面这段代码:

//检查悬浮窗权限,小于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.浮窗的页面贴一下

<?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: 二、简单的音乐播放器
下一篇: 酝酿中…

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-11 12:49:24  更:2021-11-11 12:51:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:42:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码