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版权协议,转载请附上原文出处链接和本声明。