Android仿优酷视频的悬浮窗播放效果
作者:三非_程序猿 发布时间:2022-04-30 20:44:51
之前接了需求要让视频播放时可以像优酷视频那样在悬浮窗里播放,并且悬浮窗和主播放页面之间要实现无缝切换,项目中使用的是自封装的ijkplayer
这个要求就代表不能在悬浮窗中新建视频控件,所以需要在悬浮窗中复用主页面的视频控件,以达到无缝衔接的效果。
主页面对应的视频控件的父view
<FrameLayout
android:id="@+id/vw_live"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"/>
用FrameLayout作为添加视频控件的ParentView,通过addview方法将新建的播放器控件添加到父控件内部
vw_live = new IjkVideoView(this);
video_frame = findViewById(R.id.vw_live);
video_frame.addView(vw_live);
主播放界面的启动模式
播放主界面的activity的启动模式不能为默认,因为我们要保证播放主界面在显示悬浮窗的时候退到后台,但是整个的应用不能退到后台,所以activity的启动模式改为singleInstance
android:launchMode="singleInstance"
退到后台我们通过moveTaskToBack(true)方法;
moveTaskToBack(true);
可以让播放界面退到后台而整个应用不会退回后台
权限请求
要使用悬浮窗需要申请权限
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT);
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 2);
}
悬浮窗
@SuppressLint("ClickableViewAccessibility")
public void showFloatingWindowView(IjkVideoView view) {
// 悬浮窗显示视图
LayoutInflater layoutInflater = LayoutInflater.from(activity);
mShowView = layoutInflater.inflate(R.layout.video_floating_window_layout, null);;
// 获取系统窗口管理服务
mWindowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
// 悬浮窗口参数设置及返回
mFloatParams = getParams();
//floatingWindow内部控件实例
init(view);
// 设置窗口触摸移动事件
mShowView.setOnTouchListener(new FloatViewMoveListener());
// 悬浮窗生成
mWindowManager.addView(mShowView, mFloatParams);
}
private void init(IjkVideoView viewGroup){
videoLayout = mShowView.findViewById(R.id.floating_video);
videoLayout.removeAllViews();
if (viewGroup != null){
ijkVideoView = viewGroup;
videoLayout.addView(ijkVideoView,new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT
,ViewGroup.LayoutParams.MATCH_PARENT));
}
mBtnCloseFloatingWindow = mShowView.findViewById(R.id.close_floating_view);
mBtnCloseFloatingWindow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
mBtnBackFloatingWindow = (ImageView)mShowView.findViewById(R.id.back_floating_view);
mBtnBackFloatingWindow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
private WindowManager.LayoutParams getParams() {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
//设置悬浮窗口类型
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
//设置悬浮窗口属性
layoutParams.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;
//设置悬浮窗口透明
layoutParams.format = PixelFormat.TRANSLUCENT;
//设置悬浮窗口长宽数据
layoutParams.width = 500;
layoutParams.height = 340;
//设置悬浮窗显示位置
layoutParams.gravity = Gravity.START | Gravity.TOP;
layoutParams.x = 100;
layoutParams.y = 100;
return layoutParams;
}
悬浮窗的xml,可通过自定义获得自己想要的效果
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/floating_video_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/floating_video"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:id="@+id/close_floating_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="end"
android:padding="10dp"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
<ImageView
android:id="@+id/back_floating_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:padding="10dp"
android:src="@android:drawable/ic_menu_revert" />
</FrameLayout>
悬浮窗的滑动,我们可以通过自定义点击监听实现
/**
* 浮窗移动/点击监听
*/
private class FloatViewMoveListener implements View.OnTouchListener {
//开始触控的坐标,移动时的坐标(相对于屏幕左上角的坐标)
private int mTouchStartX;
private int mTouchStartY;
//开始时的坐标和结束时的坐标(相对于自身控件的坐标)
private int mStartX, mStartY;
//判断悬浮窗口是否移动,这里做个标记,防止移动后松手触发了点击事件
private boolean isMove;
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int action = motionEvent.getAction();
int x = (int) motionEvent.getX();
int y = (int) motionEvent.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
isMove = false;
mTouchStartX = (int) motionEvent.getRawX();
mTouchStartY = (int) motionEvent.getRawY();
mStartX = x;
mStartY = y;
break;
case MotionEvent.ACTION_MOVE:
int mTouchCurrentX = (int) motionEvent.getRawX();
int mTouchCurrentY = (int) motionEvent.getRawY();
mFloatParams.x += mTouchCurrentX - mTouchStartX;
mFloatParams.y += mTouchCurrentY - mTouchStartY;
mWindowManager.updateViewLayout(mShowView, mFloatParams);
mTouchStartX = mTouchCurrentX;
mTouchStartY = mTouchCurrentY;
float deltaX = x - mStartX;
float deltaY = y - mStartY;
if (Math.abs(deltaX) >= 5 || Math.abs(deltaY) >= 5) {
isMove = true;
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
//如果是移动事件不触发OnClick事件,防止移动的时候一放手形成点击事件
return isMove;
}
}
悬浮窗的消失,在这里调用videoLayout.removeAllViews()是为了将复用的视频控件的父View清空,返回主播放activity的时候调用addview方法不会再报 child view has Parent,you have to call removeView()的错
public void dismiss() {
if (mWindowManager != null && mShowView != null) {
videoLayout.removeAllViews();
if (mShowView.getParent() != null){
mWindowManager.removeView(mShowView);
}
}
}
启动悬浮窗
public videoFloatingWindow(Context context){
super(context);
this.activity = context;
}
对于悬浮窗的调用
用hasBind来记录是否调用了悬浮窗
private void startFloatingWindow(){
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "当前无权限,请授权", Toast.LENGTH_SHORT);
startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 2);
} else {
video_frame.removeView(vw_live);
videoFloatingWindow.getInstance(this).showFloatingWindowView(vw_live);
hasBind = true;
moveTaskToBack(true);
}
}
注意
一.由于主界面activity使用了singleInstance启动模式,所以从悬浮窗返回主界面activity时,要添加flag
Intent intent = new Intent(activity, activity.getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(intent);
二.当主界面的activity退回后台,再重新进入主界面的时候,注意,不再调用onCreate方法,而是调用onNewIntent,所以重写onNewIntent方法,重新进入主界面,悬浮窗消失
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d("RemoteView", "重新显示了");
//不显示悬浮框
if (hasBind){
videoFloatingWindow.getInstance(this).dismiss();
video_frame.removeAllViews();
if (vw_live != null){
video_frame.addView(vw_live);
}
hasBind = false;
}
}
来源:https://blog.csdn.net/why931022/article/details/107229849
猜你喜欢
- 本例子演示如何添加一个简单的单页导航,在此基础上,再演示如何在第2个页面中显示第1个页面中拨打过的所有电话号码。(1)通过该例子理解Andr
- 使用流读取、写入文件使用流把文件读取到字节数组://FileMode.Create, FileMode.Append //FileAcces
- C#接口的学习,在编程中,我们经常会用到接口,那什么是接口呢?接口描述的是可属于任何类或结构的一组相关功能,所以实现接口的类或结构必须实现接
- 前言 最近项目有一个节点进度条的小需求,完成后,想分享出来希望可以帮到有需要的同学。真机效果图自定义View完整代码开箱即用~,注释已经炒鸡
- @FeignClient()注解的使用由于SpringCloud采用分布式微服务架构,难免在各个子模块下存在模块方法互相调用的情况。比如se
- 静态方法可以不用创建对象就调用,非静态方法必须有了对象的实例才能调用。因此想在静态方法中直接引用非静态方法是不可能的,因为不知道调用哪个对象
- /// <summary> /// 安装的excel的版本,0为没有安装,大于1说明安装了多个. /// </summar
- 前言此文适合了解了es相关概念以及基础知识的同学阅读elasticsearch简介Elasticsearch是一个基于Lucene的搜索服务
- 目录一、新建简单窗口二、编写窗口中的按键三、简单的按键运行1.流布局管理器:2.静态文本框:四、窗口画图五、窗口鼠标响应六、总结好了,sto
- 本文将通过AOP的方式实现一个相对更加简易灵活的API安全认证服务。我们先看实现,然后介绍和分析AOP基本原理和常用术语。一、Authori
- 本文实例讲述了Java实现的模糊匹配某文件夹下的文件并删除功能。分享给大家供大家参考,具体如下:package com.wyebd.gis;
- 什么是Java NIO?同步非阻塞io模式,拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,
- 本文实例讲述了Android授权访问网页的实现方法,即使用Webview显示OAuth Version 2.a ImplicitGrant方
- 支付宝今年推出了新的转账接口alipay.fund.trans.uni.transfer(升级后安全性更高,功能更加强大) ,老转账接口al
- 前言学习Java和Android将近一年的时间了,期间的成果应该就是独立完成了一个Android客户端,并且保证了其在主线版本的稳定性。期间
- 命名空间提供了一种从逻辑上组织类的方式,防止命名冲突。几种常见语言C++命名空间是可以嵌套的嵌套的命名空间是指定义在其他命名空间中的命名空间
- 一、区别和联系异步和多线程有什么区别?其实,异步是目的,而多线程是实现这个目的的方法。异步是说,A发起一个操作后(一般都是比较耗时的操作,如
- Servlet简介servlet是Server Applet的简称,翻译过来就是服务程序.好吧,这么说你可能还是不太懂,简单的讲,这个ser
- 方法如下:在窗体的Load事件注册滚动事件,并增加对应的方法private void FormSample_Load(object send
- 单链表:每个数据是以节点的形式存在的每个节点分为数据域和指针域数据域中保存该节点的数据指针域中保存指向下一个节点的指针实现思路:节点类Sin