Android中的Window、WindowManager以及悬浮框视频播放的实现
摘要:近日看公司直播项目,其中有一个功能就是退出某房间之后,直播界面会以悬浮窗的形式出现,并且可以拖动悬浮窗到界面中任意位置,点击悬浮框之后,又可以回到房间中继续观看直播。现在这个功能在主流的直播或者视频类软件中都可以看到,比如:某鱼、某猫、某珠、某牙、某tube。当然了,某tobe当中的悬浮窗效果更佳炫酷,可以炫酷地从悬浮框中将视频主界面慢慢拖动出来,具体效果下载某tube就能看到。这篇文章就记录一下传统悬浮窗播放视频的原理,以及悬浮框涉及到的 Window 和 WindowManager 的相关知识。
- Window 和 WindowManager 概述
- 创建一个 Window
- 实现要点
- 后续代码前提
- 清单文件中的权限
- 检查权限并启动Service
- FloatWindowService
- FloatWindow
- 效果
- 相关知识点学习资料
Window 和 WindowManager 概述
Window
表示一个窗口的概念,在日常开发中直接接触到 Window
的机会并不多,但是在某些特殊的时候,我们需要在桌面上显示一个类似悬浮框的东西(360的小火箭、360手机助手最新版当中桌面上显示的枫叶),那么这种效果就需要用 Window
来实现。Window
是一个抽象类,它的具体实现类是 PhoneWindow
,创建一个 Window
跟简单,只需通过 WindowManager
即可完成。
WindowManager
是完结访问 Window
的入口,Window
的具体实现位于 WindowManagerService
中,
WindowManager 和 Window
打交道是一个 IPC
过程。
Android
中的所有视图都是通过 Window
来呈现的,不管是 Activity
、 Dialog
还是 Toast
,他们的实际视图都是附加在 Window
上的,因此,Window 实际是 View 的直接管理者。比如说,在事件分发的过程中,点击事件首先是由 Window
传递给 DecorView
,然后再由 DecorView
往子 View
分发,最终分发到能够消耗这个点击事件的 View
当中;并且 Activity
生命周期方法 onCreate
中经常调用的 setContentView
方法底层也是通过 Window
来完成的。
创建一个 Window
上面概述中提到,要想创建一个 Window
,只需通过 WindowManager
即可实现。
public void addWindow(){Button button = new Button(getApplicationContext());button.setText("动态添加");WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;layoutParams.gravity = Gravity.LEFT | Gravity.TOP;layoutParams.x= 600;layoutParams.y= 600;getWindowManager().addView(button, layoutParams);}
上面的代码就将一个 Button
添加到屏幕上 (600,600)
的位置,对 WindowMnager.Layoutparams
常用参数做说明:
public LayoutParams(int w, int h, int xpos, int ypos, int _type,int _flags, int _format) {
w ,h
表示Window
的宽高,可以通过构造方法传入,也可以在创建好WindowManager.Layoutparams
之后,直接给其width
,height
成员变量赋值。xpos,ypos
表示Window
在手机屏幕上的绝对位置,与w,h
一样,这两个值也可以在实例化WindowManager.Layoutparams
之后给x,y
成员变量属性赋值,要向更改悬浮窗的位置,就是改变的这两个参数_type
表示的是Window
的类型,Window
有三种类型:- 应用
Window
,这个Window
对应着一个Activity
,层级范围(1~99) - 子
Window
, 不能单独存在,它需要附属在一个特定的父Window
中,比如说Dialog
,层级范围(1000~1999) - 系统
Window
,这是需要申明权限才能够创建的Window
, 比如说常用的 Toast ,层级范围(2000~2999)。
- 应用
TYPE_SYSTEM_OVERLAY(2006),TYPE_TOAST(2005),TYPE_PHONE(2002)
Flags
参数表示Window
的属性,它可以有很多选项,通过这些选项可以控制Window
的显示特性,比较常用的有:1. FLAG_NOT_FOCUSABLE
表示Window
不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODE
,事件会直接传递给下层的具有焦点的Window
。
2. FLAG_NOT_TOUCH_MODE
此模式下,系统会将当前Window
区域以外的点击事件传递给底层的Window
,当前Window
区域以内的会自己处理,一般来说这个标记都需要开启,不然其他的Window
接收不到单击事件。
3. FLAG_SHOW_WHEN_LOCKED
让Window
显示在锁屏界面上。
WindowManager
常用的方法就三个:添加 View
,删除 View
,更新 View
。这三个方法定义在 ViewManager
中,WindowManager
继承了 ViewManager
。想做悬浮窗播放视频,就需要用到这三个方法,其中悬浮框随手指拖拽而移动就是在 onTouchEvent
回调中调用 updateView
的方法。
public interface ViewManager{public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params);public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params);public void removeView(View view);}
实现要点
WindowManager
和Window
相关,用于展示悬浮框。- 要实现悬浮框,那么就会涉及到权限问题,从
Andtoid 6.0
开始,需要在运行时去获取悬浮窗的权限。 - 启动悬浮窗的组件(
Activity
或者Fragment or else
)在启动了悬浮窗之后,自己本身肯定是要关闭的,所以这里悬浮框就很适合在Service
中管理。 - 悬浮窗一般是可以与用户交互的,那么这里就会涉及到触摸反馈。
后续代码前提
- 播放器播放需要一个
m3u8
url,公司自研播放器代码不贴出。 - 当前
WatchVideoActivity
正在全屏播放,此时点击了“悬浮窗播放”按钮。 - 这里的悬浮窗播放指的是点播,非直播情况
清单文件中的权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
检查权限并启动Service
//悬浮窗播放按钮final Button button_litter_player = (Button) findViewById(R.id.button_litter_player);button_litter_player.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//此处为检查用户是否已经授权我们的应用悬浮窗权限boolean check = ConstInfo.hasPermissionFloatWin(getApplicationContext());if (!check) {Toast.makeText(getApplication(), "悬浮窗权限未打开,请去打开应用悬浮窗权限", Toast.LENGTH_SHORT).show();} else {//FloatWindowService 就是用于管理悬浮窗的 ServiceIntent intent = new Intent(WatchVideoActivity.this, FloatWindowService.class);Bundle bundle = new Bundle();//当前播放视频的m3u8地址bundle.putString("m3u8Url", getCurrentUrl()); //主要是记录当前播放的位置,这样在悬浮窗出现后,可以接着之前全屏播放的点继续播放bundle.putInt(EXTRA_VIDEO_CURRENT_POSITION, mVideoView.getCurrentPosition());finish();}}});
其中检查权限的方法是发射调用:
/*** 判断是否开启浮窗权限,api未公开,使用反射调用* @return*/public static boolean hasPermissionFloatWin(Context context) {Log.d(ConstInfo.TAG, "hasAuthorFloatWin android.os.Build.VERSION.SDK_INT="+android.os.Build.VERSION.SDK_INT);if (android.os.Build.VERSION.SDK_INT < 19) {return true;}try {AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);Class c = appOps.getClass();Class[] cArg = new Class[3];cArg[0] = int.class;cArg[1] = int.class;cArg[2] = String.class;Method lMethod = c.getDeclaredMethod("checkOp", cArg);//24是浮窗权限的标记return (AppOpsManager.MODE_ALLOWED == (Integer) lMethod.invoke(appOps, 24, Binder.getCallingUid(), context.getPackageName()));}catch(Exception e){return false;}}
FloatWindowService
Service 详情戳 Android Developer # Service Guide
Service
的onCreate
方法只会在Service
首次创建的时候调用一次,所以在这个方法中创建悬浮框的实例比较适合,因为只支持一个悬浮窗onStartCommond
方法在每次调用startService
方法时都会调用,所以在这个方法中适合检查悬浮窗的状态,比如:是否需要退出悬浮窗,还是直接开始在悬浮窗中继续播放等等.onDestroy
方法中就直接销毁悬浮窗实例即可.
public class FloatWindowService extends Service {private static final String TAG = "==FloatWindowService==";public static final String ACTION_PLAY = "com.xxxx.testxxxsdk.FloatWindowService.ACTION_PLAY";public static final String ACTION_EXIT = "com.xxxx.testxxxsdk.FloatWindowService.ACTION_EXIT";public static final String PLAY_TYPE = "com.xxxx.testxxxsdk.FloatWindowService.PLAY_TYPE";public static final String EXTRA_VIDEO_LIST = "list";//用于标记当前悬浮窗时候已经显示public static boolean mIsFloatWindowShown = false;//悬浮窗实例private FloatWindow mFloatWindow;@Overridepublic void onCreate() {super.onCreate();LogUtil.d(TAG,"[onCreate] " + "FloatWindowService onCreate");//这里将Service本身传入悬浮窗,是为了实现点击悬浮窗重新进入WatchVideoActivity 全屏播放,且提供 Context,mFloatWindow = new FloatWindow(this);mFloatWindow.createFloatView();mIsFloatWindowShown = true;}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {LogUtil.d(TAG, "[onStartCommand] " + "FloatWindowService onStart");//此处为特殊逻辑处理,和项目需求相关,不做解释if (intent.hasExtra(ACTION_EXIT)) {stopSelf();} else {//在这里就拿到之前点击悬浮窗按钮时传递过来的数据,包括播放m3u8地址和当前播放位置等Bundle bundle = intent.getBundleExtra(ACTION_PLAY);if (bundle != null && mFloatWindow != null) {LogUtil.d(TAG,"[onStartCommand] " + "FloatWindowService onStart play bundle");//将bundle数据交给悬浮窗控件本身去处理mFloatWindow.play(bundle);}}return START_STICKY;}@Overridepublic void onDestroy() {super.onDestroy();LogUtil.d(TAG,"[onDestroy] " +"FloatWindowService onDestroy" );if (mFloatWindow != null) {mFloatWindow.destroy();}mIsFloatWindowShown = false;}@Nullable@Overridepublic IBinder onBind(Intent intent) {return null;}}
上述代码可以看到,Service 在这里就是管理了悬浮窗的生命周期,以及传递数据的作用.
FloatWindow
这是悬浮窗的实现类,之前的代码在”悬浮播放”这一功能来说,都是铺垫. 参照文章前面对 WindowManager
的描述,这里肯定也会涉及到悬浮窗参数和悬浮窗布局,以及悬浮窗的交互.
首先是布局,这列悬浮窗比较简单 top_window_player
:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout android:id="@+id/root_view"xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/top_window_player_bg"><ImageView
android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:padding="10dp"android:src="@drawable/logo"/><ProgressBar
android:id="@+id/progressbar_loading"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:indeterminateDrawable="@anim/loading_anim"android:visibility="gone"/><com.xxxxxx.xxxsdk.XXXVideoView
android:id="@+id/live_player_videoview"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_centerInParent="true"android:visibility="gone"/><ImageButton
android:id="@+id/lsq_closeButton"android:layout_width="20dp"android:layout_height="20dp"android:layout_alignParentRight="true"android:background="@drawable/close_small"android:paddingRight="5dp"android:paddingTop="5dp"/></RelativeLayout>
ImageView
是用于展示默认状态图,ImageButton
为右上角叉叉,XXXVideoView
为自研的播放器,这里就不贴出代码了.
构造方法
- 这里将
Service
本身传入悬浮窗,是为了实现点击悬浮窗重新进入WatchVideoActivity
全屏播放, - 提供
Context
- 绑定两者生命周期,即悬浮窗销毁时,服务就要停止
public FloatWindow(Service hostService){mHostService = hostService;mAppContext = mHostService.getApplication();}
createFloatView()
真正创建 Window
的方法
这个方法中做 3 件事 :
- 使用
WindowManager
创建Window
- 布局控件初始化
- 触摸反馈
public void createFloatView() {wmParams = new WindowManager.LayoutParams();mWindowManager = (WindowManager) mAppContext.getSystemService(mAppContext.WINDOW_SERVICE);//即使应用退出,悬浮窗也可以可以再桌面当中显示wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;wmParams.format = PixelFormat.RGBA_8888;//悬浮窗需要自己处理点击事件wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;//初始位置在屏幕左边的中间wmParams.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;// 悬浮窗的宽为手机屏幕宽度的三分之一, 4:3 高宽比wmParams.width = TestApplication.SCREEN_WIDTH / 3;wmParams.height = (wmParams.width / 3) * 4; //Service中的ContextLayoutInflater inflater = LayoutInflater.from(mAppContext);mFloatLayout = (RelativeLayout) inflater.inflate(R.layout.top_window_player, null);mWindowManager.addView(mFloatLayout, wmParams);progressbar_loading = (ProgressBar) mFloatLayout.findViewById(R.id.progressbar_loading);ImageButton closebutton = (ImageButton) mFloatLayout.findViewById(R.id.lsq_closeButton);closebutton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {mHostService.stopSelf();}});// 设置悬浮窗的Touch监听mFloatLayout.setOnTouchListener(new View.OnTouchListener() {int lastX, lastY;int paramX, paramY;public boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN://手指按下的位置lastX = (int) event.getRawX();lastY = (int) event.getRawY();//记录手指按下时,悬浮窗的位置paramX = wmParams.x;paramY = wmParams.y;break;case MotionEvent.ACTION_MOVE:int dx = (int) event.getRawX() - lastX;int dy = (int) event.getRawY() - lastY;wmParams.x = paramX + dx;wmParams.y = paramY + dy;// 更新悬浮窗位置mWindowManager.updateViewLayout(mFloatLayout, wmParams);break;case MotionEvent.ACTION_UP://当手指按下的位置和手指抬起来的位置距离小于5像素时,将此次触摸归结为点击事件,if (Math.abs(event.getRawX() - lastX) < 5 && Math.abs(event.getRawY() - lastY) < 5)mFloatLayout.callOnClick();break;}return true;}});//设置悬浮窗的点击监听mFloatLayout.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) { //点击悬浮窗重新跳转回 WatchVideoActivity 全屏播放Intent intent = new Intent(mHostService.getApplicationContext(), WatchVideoActivity.class);//同理播放器在WatchVideoActivity 中全屏播放也是需要播放地址和 悬浮窗已经播放到的无照顾intent.putExtra("m3u8Url", mUrl); intent.putExtra(EXTRA_VIDEO_CURRENT_POSITION, mVideoView.getCurrentPosition());//Service 中启动 Activityintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);mHostService.startActivity(intent);//销毁服务mHostService.stopSelf();}});mVideoView = (XXXVideoView) mFloatLayout.findViewById(R.id.live_player_videoview);//初始化播放器mVideoView.initialize();//监听播放器 播放器相关不贴出代码mVideoView.setListener(mVideoListener);}
开始播放的方法
public void play(Bundle param) {mBundleParam = param;if (mBundleParam == null)return;//拿到从 WatchVideoActivity 中传递过来的 播放地址mUrl = mBundleParam.getString("m3u8Url");//拿到从 WatchVideoActivity 中传递过来的当前播放位置,以便继续播放mCurrPositionFromWatchVod = mBundleParam.getInt(WatchVideoActivity.EXTRA_VIDEO_CURRENT_POSITION, -1);//播放器相关,省略部分代码stop_play();start_play();}
效果
相关知识点学习资料
- Android 悬浮窗参数权限的小结,这篇文章写得时间较早,其中有点内容在我测试机 红米Note 4X 当中并没有办法验证,索性,还是需要向用户申请悬浮窗权限.
- Service 官方文档
- 运行时权限官方文档 , 鸿洋大神—— Android 6.0 运行时权限处理完全解析
- Android 带你彻底理解 Window 和 WindowManager , 《Android 开发艺术探索》 08-理解Window和WindowManager
抄书系列
原文发布于:Android中的Window、WindowManager以及悬浮框视频播放的实现
Android中的Window、WindowManager以及悬浮框视频播放的实现相关推荐
- WindowManager 简单悬浮框的实现
参考: permission denied for window type 2003 WindowManager(窗口管理服务) 10.7 WindowManager(窗口管理服务) 权限: < ...
- android获取当前windows,Android 中的 Window
一台 Android 手机屏幕上显示的内容就是由一个个 Window 组合而成的.顶部的状态栏是一个 Window,底部的导航栏也是一个 Window,中间自己的应用显示区域也是一块大 Window, ...
- android综合资讯App、自定义悬浮框、屏幕助手、空灵音乐源码等
Android精选源码 综合资讯类APP android自定义悬浮窗 android实现支付宝信用界面动画 android一个类似点融投资的app源码 Android手机屏幕助手 android视频播 ...
- android类之间的关系,Android 中Activity,Window和View之间的关系
Activity是Android应用程序的载体,允许用户在其上创建一个用户界面,并提供用户处理事件的API,如 onKeyEvent, onTouchEvent等. 并维护应用程序的生命周期.Acti ...
- android 仿美团悬浮,类似美团悬浮框的效果
具体代码: ublic class MainActivity extends Activity implements OnScrollListener{ /** * 自定义的MyScrollView ...
- 用WindowManager实现Android悬浮框以及拖动事件
下面的App程序代码实现通过主Activity的启动按钮,启动一个Service,然后在Service中创建添加悬浮窗口:(话不多说,直接上代码) 在这里我们先看一下需要创建的类和布局和需要添加的权限 ...
- android 浮动文字提示,怎么在Android中实现一个自由拖动并显示文字的悬浮框
怎么在Android中实现一个自由拖动并显示文字的悬浮框 发布时间:2021-01-27 15:34:05 来源:亿速云 阅读:107 作者:Leah 今天就跟大家聊聊有关怎么在Android中实现一 ...
- WindowManager解析(二)Android悬浮框无法弹出输入法的原因和无需权限显示悬浮窗
Android悬浮框无法弹出输入法 最近要研究悬浮窗方面的东西,遇到一个问题,我的悬浮窗里面有一个输入框,但是不弹出输入法,后来找到一个方法: 在WindowManager的实例获取方式不对,之前是这 ...
- Android 悬浮框按钮
示例: 悬浮框的xml代码:(layout_float.xml) <?xml version="1.0" encoding="utf-8"?> &l ...
最新文章
- Eclipse创建struts.xml
- python类和对象详解_Python公开课 - 详解面向对象
- Redis基础知识之—— hset 和hsetnx 的区别
- SAP Spartacus page slot里的Component,对应的DOM节点是如何插入到DOM tree里的
- LINUX framebuffer
- css设置 ul的内外边距,9月3日学习CSS选择器,背景设置,及内外边距知识总结
- 探索Windows Azure 监控和自动伸缩系列1 - 连接中国区Azure
- 阿里再度开源重磅技术!95% 程序员都需要了解
- SpringCache @Cacheable 在同一个类中调用方法,导致缓存不生效的问题及解决办法...
- 接不住了,能撒手吗?
- MATLAB——zeros
- 【渝粤教育】电大中专跨境电子商务理论与实务答案作业 题库
- 编程及C/C++初学者FAQ
- adxl345取出值怎么算角度_ADXL345测量倾斜角度数据跳动
- Prometheus+Grafana搭建Jmeter性能监控平台
- linux-网卡名字说明_基本网络配置_修改MTU值
- 网贷查询接口开发 网贷黑名单查询 个人网贷黑名单查询
- 六大原则之依赖倒转(倒置)原则
- 数学问题1 - 两个圆圈,小圆贴着大圆外部转过一圈,问小圆转几圈
- 电子表格软件能解决什么问题?
热门文章
- Python爬虫系列之爬取某优选微信小程序全国店铺商品数据
- [附源码]计算机毕业设计JAVA哈金院食堂美食评价系统
- nodejs+vue+elementui校友会校友录社交网站系统-vscode
- 网站建设-通过链接策略建立排名:
- pbft共识机制 java实现_区块链开发:共识机制PBFT #C09
- 研究发现蝴蝶的翅膀颜色跟飞行速度有关
- 值得收藏——超详细 Spring Boot 知识清单
- 详解自动编码器(AE)
- unity3D防止破解插件:Anti-Cheat Toolkit的使用
- python字典值求平均值_如何用Python打印字典键值的平均值?