悬浮窗原理

Android 悬浮窗原理

Posted by pxf on February 12, 2018

直播,视屏悬浮窗

悬浮窗原理

通过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);