Android 实现视频的悬浮窗

Android 实现视频的悬浮窗

如微信视频或者斗鱼直播一样,在应用切换到后台后,手机桌面还可以显示一个可以移动的小窗口,播放正在播放的内容。利用的就是android里面的WindowManager,原理逻辑就是在视频界面切换到后台后,调用实现悬浮窗的service服务,在判断了权限后,就可以悬浮在其余应用之上了。先来看效果,点击悬浮窗进入对应的视频播放界面,触摸悬浮窗会移动,点击右键关闭会关闭悬浮窗。
在这里插入图片描述

构建悬浮窗的服务

下面的代码就是悬浮窗服务的全部代码。在这个服务中,onCreate定义了这个悬浮窗口的大小、类型等等。onStartCommand接收了该服务启动时接收的播放器路径啊,视频id等等参数。showFloatingWindow是自定义的悬浮窗口,里面包含了我们自己定义的layout布局。布局中用的是七牛的播放控件,因为我的项目是用的七牛的PLVideoTextureView ,这里就根据大家的自己需求来使用自己的合适的播放控件。我这里自定义实现了点击事件View.OnClickListener的一个FloatingOnClickListener;然后也触摸事件View.OnTouchListener的一个FloatingOnTouchListener 。在点击事件中实现了悬浮窗的点击关闭功能,在触摸事件中实现了窗口的移动功能,并且为了解决同一个控件触摸事件与点击事件的冲突,直接在触摸事件的MotionEvent.ACTION_UP中,去处理了窗口的点击事件,这样就不会存在事件的冲突了。

public class FloatingWindowService extends Service {

    private WindowManager windowManager;
    private WindowManager.LayoutParams layoutParams;
    private View display;
    private PLVideoTextureView plVideoTextureView;
    private ImageView icon_little_close;
    private FrameLayout fl_play;

    private boolean show=true;
    private int qiehuan=0;

    private long startTime=0;
    private long endTime=0;

    private boolean isclick=false;

    private String videoPath; //播放路径
    private int videoId;     //直播间id


    public FloatingWindowService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        layoutParams = new WindowManager.LayoutParams();

        // 设置图片格式,效果为背景透明
        layoutParams.format = PixelFormat.RGB_565;

        G.look("悬浮窗 Build.VERSION.SDK_INT" + Build.VERSION.SDK_INT);
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
            // android 8.0及以后使用
            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            // android 8.0以前使用
            layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        layoutParams.gravity = Gravity.LEFT | Gravity.CENTER;
        //该flags描述的是窗口的模式,是否可以触摸,可以聚焦等
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // 设置视频的播放窗口大小
        layoutParams.width = Utils.getWindowScreenWidth(FloatingWindowService.this)/3;
        layoutParams.height = Utils.getWindowScreenWidth(FloatingWindowService.this)/3;
        layoutParams.x = 700;
        layoutParams.y = 0;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (show) {
            videoPath = intent.getStringExtra("littlePlayUrl");
            videoId = intent.getIntExtra("recordId", 0);

            showFloatingWindow();

        }
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return null;
    }

    @SuppressLint("NewApi")
    private void showFloatingWindow(){
        if (Settings.canDrawOverlays(FloatingWindowService.this)) {
            LayoutInflater layoutInflater = LayoutInflater.from(FloatingWindowService.this);
            display = layoutInflater.inflate(R.layout.layout_little_video_window, null);
            plVideoTextureView = display.findViewById(R.id.PLVideoTextureView);
            icon_little_close = display.findViewById(R.id.icon_little_close);
            fl_play = display.findViewById(R.id.fl_play);
            plVideoTextureView.setDisplayAspectRatio(PLVideoView.ASPECT_RATIO_PAVED_PARENT);
            G.look("悬浮窗播放路径:"+videoPath);
            plVideoTextureView.setVideoPath(videoPath);
            plVideoTextureView.start();
            windowManager.addView(display, layoutParams);
            display.setOnTouchListener(new FloatingOnTouchListener());
            display.setOnClickListener(new FloatingOnClickListener());
            icon_little_close.setOnClickListener(new FloatingOnClickListener());
            show=false;
        }
    }

    private class FloatingOnClickListener implements View.OnClickListener{

        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.icon_little_close:
                    stopLittlePlay();
                    break;
            }
        }
    }



    // touch移动视频窗口 | 事件拦截
    private class FloatingOnTouchListener implements View.OnTouchListener {
        private int x;
        private int y;
        @Override
        public boolean onTouch(View view, MotionEvent event) {
            G.look("移动的控件:"+view.getId());
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    x = (int) event.getRawX();
                    y = (int) event.getRawY();

                    isclick = false;//当按下的时候设置isclick为false

                    startTime = System.currentTimeMillis();
                    break;

                case MotionEvent.ACTION_MOVE:
                    isclick = true;//当按钮被移动的时候设置isclick为true
                    int nowX = (int) event.getRawX();
                    int nowY = (int) event.getRawY();
                    int movedX = nowX - x;
                    int movedY = nowY - y;
                    G.look("悬浮窗 movedX = " + movedX + ", movedY =" + movedY);
                    x = nowX;
                    y = nowY;
                    layoutParams.x = layoutParams.x + movedX;
                    layoutParams.y = layoutParams.y + movedY;
                    windowManager.updateViewLayout(view, layoutParams);
                    break;
                case MotionEvent.ACTION_UP:
                    endTime = System.currentTimeMillis();
                    //当从点击到弹起小于半秒的时候,则判断为点击,如果超过则不响应点击事件
                    if ((endTime - startTime) > 0.1 * 1000L) {
                        isclick = true;
                        G.look("tag 拖动");
                    } else {
                        isclick = false;
                        stopLittlePlay();
                        startActivity(new Intent(FloatingWindowService.this, LiveActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                                .putExtra("recordId",videoId));
                        G.look("tag 点击");
                    }
                    break;
            }
            return isclick;
        }
    }

    @Override
    public void onDestroy() {
        // 移除浮动框
        if (windowManager != null) {
            windowManager.removeView(display);
        }
        if (plVideoTextureView != null) {
            plVideoTextureView.stopPlayback();

        }
        super.onDestroy();
    }

    /**
     * 停止播放
     */
    private void stopLittlePlay(){
        if (plVideoTextureView != null) {
            plVideoTextureView.stopPlayback();

        }
        stopService(new Intent(FloatingWindowService.this, FloatingWindowService.class));
    }

}
在全局配置中申请悬浮窗的权限以及注册悬浮窗的服务

申请权限

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

注册服务

<service android:name="com.android.redwine.services.FloatingWindowService"/>
下面就是在合适的地方启动悬浮窗的服务了
//判断是否授权及开启服务
    @RequiresApi(api = Build.VERSION_CODES.M)
    public void startFloatingService() {
        if (Utils.isServiceWork(this, "com.android.redwine.services.FloatingWindowService")) {//防止重复启动
            return;
        }
        if (!Settings.canDrawOverlays(this)) {
            startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 0);
        } else {
            startService(new Intent(context, FloatingWindowService.class).putExtra("littlePlayUrl",littlePlayUrl).putExtra("recordId",littlePlayId));
        }
    }

    @SuppressLint("NewApi")
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // 判断获取权限是否成功
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 0) {
            if (!Settings.canDrawOverlays(this)) {
            } else {
                startService(new Intent(context, FloatingWindowService.class).putExtra("littlePlayUrl",littlePlayUrl).putExtra("recordId",littlePlayId));
            }
        }
    }
//兼容按返回键时退出播放界面时启动悬浮窗服务
 @Override
    public void onBackPressed() {
        super.onBackPressed();
        G.look("onBackPressed");
        if (!G.isEmpty(littlePlayUrl)) {
            startFloatingService();
        }
    }
    //兼容按home键切换应用到后台时启动悬浮窗服务
@Override
    protected void onPause() {
        super.onPause();
        if (!G.isEmpty(littlePlayUrl)) {
            startFloatingService();
        }
    }

//在onResume判断服务是否启动,如果已经启动,就要关闭该服务,因为当前界面就是视频播放界面,不需要悬浮窗
@Override
    protected void onResume() {
        super.onResume();
      
        G.look("onResume");
        if (Utils.isServiceWork(this, "com.android.redwine.services.FloatingWindowService")) {//防止重复启动
            stopService(new Intent(context, FloatingWindowService.class));
        }
    }
最后附上判断应用是否重复启动的方法
 /**
     * 判断某个服务是否正在运行的方法
     *
     * @param mContext
     * @param serviceName
     *            是包名+服务的类名(例如:net.loonggg.testbackstage.TestService)
     * @return true代表正在运行,false代表服务没有正在运行
     */
    public static boolean isServiceWork(Context mContext, String serviceName) {
        boolean isWork = false;
        ActivityManager myAM = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> myList = myAM.getRunningServices(40);
        if (myList.size() <= 0) {
            return false;
        }
        for (int i = 0; i < myList.size(); i++) {
            String mName = myList.get(i).service.getClassName().toString();
            if (mName.equals(serviceName)) {
                isWork = true;
                break;
            }
        }
        return isWork;
    }

版权声明:本文为u014714188原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。