我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android悬浮窗视频

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android悬浮窗视频

类似微信视频通话需求。
思路:
1.申请悬浮窗权限
2.windowManager实现悬浮窗;
3.moveToback退出全屏,显示悬浮窗;

当用户正在NewsActivity看文字,视频通话来了,接听(VideoActivity),然后缩至悬浮窗,此时应回到NewsActivity,悬浮窗出现时该如何回到电话前的页面?finish掉VideoActivity吗?finish后自然回退到栈内上一个Activity,页面逻辑上符合需求,但是VideoActivity中写了很多通话逻辑,销毁不太好,怎么办?(有人说,为何会在页面写逻辑,明显是没抽象好嘛,这都是后话,不解决当前版本问题)

于是想到干脆起两个任务栈,一个单独放VideoActivity,另一个放剩余的Activity,然后点击要悬浮时,直接后置VideoActivity所在任务栈即可。
实现两个任务栈也很简单,只要给VideoActivity设置android:launchMode="singleInstance"即可。
想要退后该任务栈直接调用 moveTaskToBack(true);

然而singleInstance的坑不是一般的多。

一 . 首先 singleInstance会导致onRequestPermissionsResult( ),onActivityResult( )不回调。可是视频必须要申请RECORD_AUDIO,CAMERA,WRITE_EXTERNAL_STORAGE权限,以及悬浮窗ACTION_MANAGE_OVERLAY_PERMISSION。
无奈写个视频的前置页面标准启动模式,专门用来申请相机,录音,读写等权限,全部授权后 再真正进入singleInstance页面。
问题一算是迎刃而解。(躲过去了)

二. 长按home或者菜单键 查看Android最近任务列表时,一个APP竟然两个最近任务,解决方案:需要添加一个属性 保证 不在最近任务列表中显示当前activity所在的应用

android:launchMode= "singleInstance"  //开启新的应用任务栈
android:excludeFromRecents= "true"   //不在最近任务列表中显示当前activity所在的应用

三 选择TYPE_TOAST,如果期间有toast弹出,在android7.1.1会崩溃,加上版本判断吧。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
    wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}

下面是悬浮窗的实现



public class FloatVideoWindowService extends Service {
    private WindowManager mWindowManager;
    private WindowManager.LayoutParams wmParams;
    private View mFloatingLayout;
    private RelativeLayout mPreviewLayout;
    private TutorialsManager mInstance = null;
    private SurfaceView remoteView = null;
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }
    public class MyBinder extends Binder {
//        public FloatVideoWindowService getService() {
//            return FloatVideoWindowService.this;
//        }
    }
    @Override
    public void onCreate() {
        super.onCreate();
        initWindow();//设置悬浮窗基本参数(位置、宽高等)
        initFloating();//悬浮框点击事件的处理
        mInstance = TutorialsManager.getInstance(this);
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int uid = intent.getIntExtra(VideoChatViewActivity.KEY_C_USER_ID, -1);
        if (uid != -1) {
            remoteView = mInstance.getRemoteVideo(uid);
            ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            remoteView.setLayoutParams(lp);
            mPreviewLayout.addView(remoteView);
        } else {
            stopSelf();
        }
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        if (null != mPreviewLayout) {
            mPreviewLayout.removeAllViews();
        }
        if (mFloatingLayout != null) {
            // 移除悬浮窗口
            mWindowManager.removeView(mFloatingLayout);
        }
    }
    
    private void initWindow() {
        mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
        wmParams = getParams();
        // 悬浮窗默认显示以左上角为起始坐标
        wmParams.gravity = Gravity.RIGHT | Gravity.TOP;
        //悬浮窗的开始位置,因为设置的是从左上角开始,所以屏幕左上角是x=0;y=0
        wmParams.x = DensityUtil.dip2px(10);
        wmParams.y = 110;
        // 获取浮动窗口视图所在布局
        mFloatingLayout = LayoutInflater.from(getApplicationContext()).inflate(R.layout.view_videochat_services_float_layout, null);
        // 添加悬浮窗的视图
        mWindowManager.addView(mFloatingLayout, wmParams);
    }
    private WindowManager.LayoutParams getParams() {
        wmParams = new WindowManager.LayoutParams();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        //设置可以显示在状态栏上
        wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams
                .FLAG_NOT_TOUCH_MODAL |
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
        int fullCropWidth = DeviceUtils.getScreenWidth();
        int cropHeight = fullCropWidth * 16 / 9;
        //设置悬浮窗口长宽数据
        wmParams.width = (int) (fullCropWidth * 0.26);
        wmParams.height = (int) (cropHeight * 0.26);
        return wmParams;
    }
    private void initFloating() {
        mPreviewLayout = mFloatingLayout.findViewById(R.id.small_size_preview);
        //悬浮框点击事件
        mPreviewLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FloatVideoWindowService.this, VideoChatViewActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
                mPreviewLayout.removeView(remoteView);
                stopSelf();
            }
        });
        //悬浮框触摸事件,设置悬浮框可拖动
        mPreviewLayout.setOnTouchListener(new FloatingListener());
    }
    
    private int mTouchStartX;
    private int mTouchStartY;
    
    private int mStartX;
    private int mStartY;
    
    private boolean isMove;
    private class FloatingListener implements View.OnTouchListener {
        int slop = 1;//滑动距离,区分点击
        public FloatingListener() {
            slop = ViewConfiguration.get(getApplicationContext()).getScaledTouchSlop();
        }
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int action = event.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    isMove = false;
                    mTouchStartX = (int) event.getRawX();
                    mTouchStartY = (int) event.getRawY();
                    mStartX = (int) event.getX();
                    mStartY = (int) event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int mTouchCurrentX = (int) event.getRawX();
                    int mTouchCurrentY = (int) event.getRawY();
                    wmParams.x -= mTouchCurrentX - mTouchStartX;
                    wmParams.y += mTouchCurrentY - mTouchStartY;
                    mWindowManager.updateViewLayout(mFloatingLayout, wmParams);
                    mTouchStartX = mTouchCurrentX;
                    mTouchStartY = mTouchCurrentY;
                    break;
                case MotionEvent.ACTION_UP:
                    int mStopX = (int) event.getX();
                    int mStopY = (int) event.getY();
                    if (Math.abs(mStartX - mStopX) >= slop || Math.abs(mStartY - mStopY) >= slop) {
                        isMove = true;
                    }
                    break;
                default:
                    break;
            }
            //如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
            return isMove;
        }
    }
}

其中VideoActivity中代码:

    private int PermissionRequestCode = 10;
    private void onClickFloatBtn() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), PermissionRequestCode);
        } else {
            showFloatView();
        }
    }
    private void showFloatView() {
        if (mUid != -1) {
            moveTaskToBack(true);
            Intent intent = new Intent(this, FloatVideoWindowService.class);
            intent.putExtra(KEY_C_USER_ID, mUid);
            startService(intent);
        }
    }
    @Override
    protected void onActivityResult(final int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PermissionRequestCode) {
            if (mHandler == null) {
                mHandler = new Handler(Looper.getMainLooper());
            }
            //此处特意延时500ms,否则回调值不准确,   https://blog.csdn.net/qq_24179679/article/details/84139408
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        if (Settings.canDrawOverlays(mContext)) {//开启
                            showFloatView();
                        } else {//关闭
                            ToastUtil.show(mContext, "您未授权悬浮窗");
                        }
                    }
                }
            }, 500);
        }
    }

等等,你刚才坑一里不是说,onActivityResult不回调吗?咋还在singleInstance的页面重写这个方法。经测试,onRequestPermissionsResult的确不回调,但是onActivityResult还是有回调的,延时500ms,回调值更准确哦。

是不是很开森,万事大吉了。人生就像登山,往上走即使一小步也有新高度;然而编程就像玩俄罗斯套娃,打破一小层,还有更大层等着你。走不完的套路,爬不完的坑。

问题描述:当singleInstance页面在onResume时,按下home键,再次点击app的桌面icon,进入后,发现不是刚才的singleInstance页面。我太难啦!!!

有人说可以设置:alwaysRetainTaskState = true

咦~ 这是什么属性?来看看谷歌官方文档怎么说的:

android:alwaysRetainTaskState
系统是否始终保持 Activity 所在任务的状态 —“true”表示是,“false”表示允许系统在特定情况下将任务重置到其初始状态。
默认值为“false”。该属性只对任务的根Activity 有意义;所有其他 Activity 均可忽略该属性。
正常情况下,当用户从主屏幕重新选择某个任务时,系统会在特定情况下清除该任务(从根 Activity 上的堆栈中移除所有Activity)。通常,如果用户在一段时间(如 30 分钟)内未访问任务,系统会执行此操作。
不过,如果该属性的值是“true”,则无论用户如何返回任务,该任务始终会显示最后一次的状态。例如,该属性非常适用于网络浏览器这类应用,因为其中存在大量用户不愿丢失的状态(如多个打开的标签)。

看完最后一句,“该属性非常适用于网络浏览器这类应用,因为其中存在大量用户不愿丢失的状态(如多个打开的标签)”,好像有点那个意思哦,赶紧加上试试,RUN。。。。。满怀期待。。。。虔诚等待。。。。

等了那么久,然并卵。显然是singleInstance在作祟,烦屎了。。。。

剩下我知识储量的最后一招了,纯属无奈之举!
监听app从后台切回前台时机,切回前台时,如果VideoActivity尚在且在通话中,二话不说,直接startActivity之。
怎么监听APP从后台切到前台了呢?注册Application.ActivityLifecycleCallbacks
记得在MyApplication的onCreate中调用注册方法

registerActivityLifecycleCallback(new AppActivityLifecycleCallbacks());

看看AppActivityLifecycleCallbacks实现类的代码:

    @Override
    public void onActivityStarted(Activity activity) {
            //视频面试时APP切换至后台,然后由后台切换至前台,视频面试中,且不是悬浮窗显示时 要回到视频面试页面
            if (onActivityStoppedFlag && BackgroundUtils.isForeground(activity)
                    && ActivityStackHelper.isActivityRunning(VideoChatViewActivity.class)
                    && !SystemUtils.isServiceRunning(activity, FloatVideoWindowService.class.getName())) {
                activity.startActivity(new Intent(activity, VideoChatViewActivity.class));
            }
            onActivityStoppedFlag = !BackgroundUtils.isForeground(activity);
    }
    
    private boolean onActivityStoppedFlag = false;
    @Override
    public void onActivityStopped(Activity activity) {
        onActivityStoppedFlag = !BackgroundUtils.isForeground(activity);
    }

功能算是“顺利”实现了,但是监听app后台切前台纯属临时方案;恳请有爱的大神帮忙支招,在我的知识盲区指津。

参考文献:
https://www.jianshu.com/p/3786653f9c9b
https://blog.csdn.net/qq_24179679/article/details/84139408


作者:iblade


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android悬浮窗视频

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android悬浮窗视频

类似微信视频通话需求。 思路: 1.申请悬浮窗权限 2.windowManager实现悬浮窗; 3.moveToback退出全屏,显示悬浮窗; 当用户正在NewsActivity看文字,视频通话来了,接听(VideoActivity),然后
2022-06-06

Android视频悬浮窗口实现的示例代码

前言 本文例子实现了点击显示悬浮窗口,同时窗口可播放视频,拖动位置,点击关闭及返回 APP 页面,通过例子来讲述悬浮窗口实现原理及细节处理,效果图如下所示:悬浮窗口.gif 原理 WindowManager 对 View 视图进行添加、移
2022-06-06

Android悬浮窗如何实现

小编给大家分享一下Android悬浮窗如何实现,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!效果如下:显示浮窗原生ViewManager接口提供了向窗口添加并操纵
2023-06-04

Android手机悬浮窗口小案例

本文实例为大家分享了Android九宫格图片展示的具体代码,供大家参考,具体内容如下 –主页面——–//布局中就一个Button public class MainActivity extends Activity {@Overridepr
2022-06-06

Android开发悬浮窗踩坑解决

这篇文章主要为大家介绍了Android悬浮窗踩坑解决示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-19

Android悬浮窗效果怎么实现

要实现Android的悬浮窗效果,可以采用以下几种方法:使用系统提供的WindowManager类来创建一个悬浮窗口。可以通过以下步骤实现:在AndroidManifest.xml文件中添加SYSTEM_ALERT_WINDOW权限。创建
2023-10-22

Android WindowManger实现桌面悬浮窗功能

这篇文章主要介绍了Android WindowManger实现桌面悬浮窗功能,他们基本都是在Activity之上显示的,如果想实现在桌面显示的悬浮窗效果,需要用到WindowManager来实现了,需要的朋友可以参考下
2023-05-18

Android悬浮窗屏蔽悬浮窗外部所有的点击事件的实例代码

Android可以在所有应用上方添加View,就是给WindowManager添加一个View,在创建的View的时候可以给这个View设置LayoutParams(android.view.WindowManager.LayoutPara
2022-06-06

Android可拖动悬浮窗怎么实现

要实现在Android中可拖动的悬浮窗,可以按照以下步骤进行:1. 创建一个自定义的`FloatingView`类来实现悬浮窗的视图。在该类中,你可以添加任何你想要显示的视图元素,如文字、图像等。2. 在`FloatingView`类中,重
2023-08-16

Kotlin如何实现Android系统悬浮窗

本篇内容介绍了“Kotlin如何实现Android系统悬浮窗”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Android 弹窗浅谈我们知道
2023-06-22

Android应用内悬浮窗Activity如何实现

这篇文章主要介绍Android应用内悬浮窗Activity如何实现,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!缩放方法缩放activity需要使用WindowManager.LayoutParams,控制windo
2023-06-22

Android 项目必备(四十四)-->Android 实现悬浮窗

前言 悬浮窗是一种比较常见的需求。例如把视频通话界面缩小成一个悬浮窗,然后用户可以在其他界面上处理事情。 本文将讲解悬浮窗实现步骤、原理、实例代码等 实现原理 1. WindowMananger 接口 Android 的界面绘制,都是通过
2023-08-16

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录