原文地址:http://www.jianshu.com/p/988021487b5b
公司做的IM软件基于webrtc实现了音视频通话功能基础功能,新需求是要求通话的同时也可以处理别的东西,即在通话页面点击最小化按钮后视频通话页面变成了一个不大的窗口悬浮在窗口上且是全局的,即使回到home页面依然存在;
这种需求非常合理,符合大家一贯的使用习惯,体验性也很棒;
刚拿到需求在技术实现上并没有头绪,因为原本的视频通话展示逻辑是用的Activity,一开始想着通过改变Activity的尺寸来实现,但是基于Activity本身的生命周期特性决定了这样并不能达成缩小后的view界面悬浮在所有其他界面之上的要求;而安卓本身能够干这个的事情已知的是一个叫悬浮窗的东东,可以验证下;
好在这个功能像微信优酷都有实现,我们不妨试着看看他们是怎么做的。以QQ为例,正常视频通话点击最小化通话界面会缩小成一个很小的区域放在界面顶部
去系统那查看权限发现应用显示悬浮窗权限是允许的,我们把改权限设置为不允许,再次视频通话后点击最小化,显示悬浮窗权限未获取的对话框!果然!
那接下来的事情就好办了,将webrtc连接控制和流控制的逻辑抽取出来写成一个工具类,activity界面只做展示逻辑,点击最小化关闭activity,跳转到悬浮窗逻辑。悬浮窗逻辑这为了逻辑清晰我们可以放在一个service里创建,代码如下
public class VoipFloatService extends Service {
private static finalStringTAG="FloatService";
private WindowManager mWindowManager;
private WindowManager.LayoutParams mLayoutParams;
/**
* float的布局view
*/
private ViewmFloatView;
private GLSurfaceView glSurfaceView;
private intmFloatWinWidth,mFloatWinHeight;//悬浮窗的宽高
private intmFloatWinMarginTop,mFloatWinMarginRight;
private intmLastX=0,mLastY=0;
private intmStartX=0,mStartY=0;
@Override
public void onCreate() {
super.onCreate();
LogEx.d(TAG,"onCreate: ");
createWindowManager();
createFloatView();
}
@Override
public void onDestroy() {
super.onDestroy();
LogEx.d(TAG,"onDestroy: ");
removeFloatView();
}
private void createWindowManager() {
LogEx.d(TAG,"createWindowManager: ");
// 取得系统窗体
mWindowManager= (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
//计算得出悬浮窗口的宽高
DisplayMetrics metric =newDisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(metric);
intscreenWidth = metric.widthPixels;
mFloatWinWidth = (int) (screenWidth *0.8/3);
mFloatWinHeight=mFloatWinWidth*4/3;
mFloatWinMarginTop= (int)this.getResources().getDimension(R.dimen.rkcloud_av_floatwin_margintop);
mFloatWinMarginRight= (int)this.getResources().getDimension(R.dimen.rkcloud_av_floatwin_marginright);
// 窗体的布局样式
// 获取LayoutParams对象
mLayoutParams=newWindowManager.LayoutParams();
// 确定爱悬浮窗类型,表示在所有应用程序之上,但在状态栏之下
//TODO? 在android2.3以上可以使用TYPE_TOAST规避权限问题
mLayoutParams.type= WindowManager.LayoutParams.TYPE_TOAST;//TYPE_PHONE
mLayoutParams.format= PixelFormat.RGBA_8888;
mLayoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
// 悬浮窗的对齐方式
mLayoutParams.gravity= Gravity.RIGHT| Gravity.TOP;
// 悬浮窗的位置
mLayoutParams.x=mFloatWinMarginRight;
mLayoutParams.y=mFloatWinMarginTop;
mLayoutParams.width=mFloatWinWidth;
mLayoutParams.height=mFloatWinHeight;
}
/**
* 创建悬浮窗
*/
private void createFloatView() {
LogEx.d(TAG,"createFloatView: ");
LayoutInflater inflater = LayoutInflater.from(VoipFloatService.this);
mFloatView= inflater.inflate(R.layout.voip_float_layout, null);
glSurfaceView= (GLSurfaceView)mFloatView.findViewById(R.id.float_gl_surface_view);
glSurfaceView.setPreserveEGLContextOnPause(true);
glSurfaceView.setKeepScreenOn(true);
mWindowManager.addView(mFloatView,mLayoutParams);
mFloatView.setOnTouchListener(newView.OnTouchListener() {
@Override
public booleanonTouch(View v,MotionEvent event) {
intaction = event.getAction();
if(MotionEvent.ACTION_DOWN== action) {
mStartX=mLastX= (int) event.getRawX();
mStartY=mLastY= (int) event.getRawY();
}else if(MotionEvent.ACTION_UP== action) {
intdx = (int) event.getRawX() -mStartX;
intdy = (int) event.getRawY() -mStartY;
if(Math.abs(dx) >5|| Math.abs(dy) >5) {
return true;
}
}else if(MotionEvent.ACTION_MOVE== action) {
intdx = (int) event.getRawX() -mLastX;
intdy = (int) event.getRawY() -mLastY;
mLayoutParams.x=mLayoutParams.x- dx;
mLayoutParams.y=mLayoutParams.y+ dy;
mWindowManager.updateViewLayout(mFloatView,mLayoutParams);
mLastX= (int) event.getRawX();
mLastY= (int) event.getRawY();
}
return false;
}
});
mFloatView.setOnClickListener(newView.OnClickListener() {
@Override
public void onClick(View v) {
maxZoom2WebRtcActivity();
VoipFloatService.this.stopSelf();
}
});
VideoRendererGui.setView(glSurfaceView, newRunnable() {
@Override
public void run() {
LogEx.d(TAG,"createFloatView: VideoRendererGui.setView localVideo run: ");
if(WebRtcHelperEx.getInstance().isWebRtcChanelAlive()) {
WebRtcHelperEx.getInstance().updateVideoUI(WebRtcHelperEx.latestLocalVideoSize,WebRtcHelperEx.latestRemoteVideoSize);
}
}
});
if(WebRtcHelperEx.getInstance().isWebRtcChanelAlive()) {
LogEx.d(TAG,"createFloatView: webrtc instance is alive and we will call resetRenders");
WebRtcHelperEx.getInstance().resetRenders();
WebRtcHelperEx.getInstance().updateVideoUI(WebRtcHelperEx.VIDEOSIZE_SMALL,WebRtcHelperEx.VIDEOSIZE_BIG);
}
}
private void removeFloatView() {
LogEx.d(TAG,"removeFloatView: ");
if(mFloatView!=null&&mWindowManager!=null) {
mWindowManager.removeView(mFloatView);
}
}
/**
* 单击后回到@WebRTCActivity以切换为大尺寸页面
*/
private void maxZoom2WebRtcActivity() {
//TODO? 跳转到Activity
}
}
当点击最小化后关闭了视频通话中的WebRtcActivity,并创建FloatService,在其中创建悬浮窗口并设置Touch事件使其可以随手指滑动,将webrtc流渲染到悬浮窗口内的glSurfaceView上。
当点击该窗口时,关闭floatService,移除悬浮窗口,跳转打开WebRtcActivity。
以上就是Activity视频页面和悬浮窗口页面互相切换的逻辑,效果如下图