直播,视屏悬浮窗
悬浮窗原理
通过WindowManager这个类来实现的,调用这个类的addView方法用于添加一个悬浮窗,updateViewLayout方法用于更新悬浮窗的参数,removeView用于移除悬浮窗。其中悬浮窗的参数有必要详细说明一下。
WindowManager.LayoutParams这个类用于提供悬浮窗所需的参数,其中有几个经常会用到的变量:
type值用于确定悬浮窗的类型,一般设为2002,表示在所有应用程序之上,但在状态栏之下。
flags值用于确定悬浮窗的行为,比如说不可聚焦,非模态对话框等等,属性非常多,大家可以查看文档。
gravity值用于确定悬浮窗的对齐方式,一般设为左上角对齐,这样当拖动悬浮窗的时候方便计算坐标。
x值用于确定悬浮窗的位置,如果要横向移动悬浮窗,就需要改变这个值。
y值用于确定悬浮窗的位置,如果要纵向移动悬浮窗,就需要改变这个值。
width值用于指定悬浮窗的宽度。
height值用于指定悬浮窗的高度。
权限适配
创建悬浮窗这种窗体需要向用户申请权限才可以的,因此还需要在AndroidManifest.xml中加入 `
` Android 悬浮窗权限各机型各系统适配大全 这博客讲的很详细,适配了各个机型,可以参考。
代码实现
封装悬浮窗控件(解决滑动冲突)
封装一个FloatView悬浮窗控件(解决滑动冲突),通过FloatView addView()方法添加悬浮的View。
1) 初始化window参数
private void initWindow(Context context) {
mWindowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
mParams = new WindowManager.LayoutParams();
mParams.packageName = context.getPackageName();
// 设置window type
mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; // 系统提示。它总是出现在应用程序窗口之上 // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // mParams.type = WindowManager.LayoutParams.TYPE_TOAST; // } else { // mParams.type = WindowManager.LayoutParams.TYPE_PHONE; // } // mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//系统内部错误提示,显示于所有内容之上
// 设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作) mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
// 设置图片格式,效果为背景透明
mParams.format = PixelFormat.RGBA_8888;
// 调整悬浮窗显示的停靠位置为
mParams.gravity = Gravity.START | Gravity.TOP;
// 设置悬浮窗口长宽数据
mParams.width = ScreenUtils.dip2px(125);
mParams.height = ScreenUtils.dip2px(223);
mParams.x = floatX;
mParams.y = floatY;
}
2)添加至Window
public boolean addToWindow() {
if (mWindowManager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (!isAttachedToWindow()) {
refreshFloatXY();
mWindowManager.addView(this, mParams);
return true; } else {
return false;
}
} else {
try {
if (getParent() == null) {
refreshFloatXY();
mWindowManager.addView(this, mParams);
}
return true;
} catch (Exception e) {
return false;
}
}
} else {
return false;
}
}
3)从Window移除
public boolean removeFromWindow() {
if (mWindowManager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (isAttachedToWindow()) {
mWindowManager.removeViewImmediate(this);
return true; } else {
return false;
}
} else {
try {
if (getParent() != null) {
mWindowManager.removeViewImmediate(this);
}
return true;
} catch (Exception e) {
return false;
}
}
} else {
return false;
}
}
实现直播,视屏悬浮窗
VideoView播放器的全局单例
很简单就是将直播播放VideoView通过FloatView.addView添加至悬浮窗。但是要保证全屏直播到小浮窗状态的切换流畅,所以VideoView必须是同一个才能保证进度的完全同步。所以封装了全局VideoView管理工具类FloatViewManager,统一用FloatViewManager中的VideoView保证使用的是同一个VideoView。
public class FloatViewManager {
private static volatile FloatViewManager instance; public PlayBackTextureView mPlayBackTextureView;
public StreamingPullTextureView mStreamingPullTextureView; private FloatViewManager() {
}
public static FloatViewManager getInstance() {
if (instance == null) {
synchronized (FloatViewManager.class) {
if (instance == null) {
instance = new FloatViewManager();
}
}
}
return instance;
}
public PlayBackTextureView initPlayBackTextureView(Context context) {
mPlayBackTextureView = new PlayBackTextureView(context);
return mPlayBackTextureView;
}
public StreamingPullTextureView initStreamingPullTextureView(Context context) {
mStreamingPullTextureView = new StreamingPullTextureView(context);
return mStreamingPullTextureView;
}
/**
* 释放PlayBackTextureView资源 */
public void stopPlayBackTextureView() {
if (null != mPlayBackTextureView) {
mPlayBackTextureView.stopFloatWindow();
mPlayBackTextureView = null;
}
instance = null;
}
/**
* 释放StreamingPullTextureView资源 */
public void stopStreamingPullTextureView() {
if (null != mStreamingPullTextureView) {
mStreamingPullTextureView.stopFloatWindow();
mStreamingPullTextureView = null;
}
instance = null;
}
/**
* 应用进入后台 关闭悬浮窗 */
public void stopFloatWindow() {
if (null != mPlayBackTextureView && mPlayBackTextureView.getIsFloat()) {
mPlayBackTextureView.stopFloatWindow();
mPlayBackTextureView = null;
}
if (null != mStreamingPullTextureView && mStreamingPullTextureView.getIsFloat()) {
mStreamingPullTextureView.stopFloatWindow();
mStreamingPullTextureView = null;
}
instance = null;
}
}
Activity中使用同一个VideoView
mVideoView = FloatViewManager.getInstance().mStreamingPullTextureView; if (null == mVideoView) {
mVideoView = FloatViewManager.getInstance().initStreamingPullTextureView(mContext.getApplicationContext()); } else {
ViewGroup parent = (ViewGroup) mVideoView.getParent();
if (parent != null) {
parent.removeView(mVideoView);
}
}
mVideoFrameLayout.addView(mVideoView);