思考像微信QQ视频通话最小化后悬浮展现实现思路

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