思考像微信QQ視頻通話最小化后懸浮展現實現思路
公司做的IM軟件基于webrtc實現了音視頻通話功能基礎功能,新需求是要求通話的同時也可以處理別的東西,即在通話頁面點擊最小化按鈕后視頻通話頁面變成了一個不大的窗口懸浮在窗口上且是全局的,即使回到home頁面依然存在;
這種需求非常合理,符合大家一貫的使用習慣,體驗性也很棒;
剛拿到需求在技術實現上并沒有頭緒,因為原本的視頻通話展示邏輯是用的Activity,一開始想著通過改變Activity的尺寸來實現,但是基于Activity本身的生命周期特性決定了這樣并不能達成縮小后的view界面懸浮在所有其他界面之上的要求;而安卓本身能夠干這個的事情已知的是一個叫懸浮窗的東東,可以驗證下;
好在這個功能像微信優酷都有實現,我們不妨試著看看他們是怎么做的。以QQ為例,正常視頻通話點擊最小化通話界面會縮小成一個很小的區域放在界面頂部
圖來自網絡
去系統那查看權限發現應用顯示懸浮窗權限是允許的,我們把改權限設置為不允許,再次視頻通話后點擊最小化,顯示懸浮窗權限未獲取的對話框!果然!
那接下來的事情就好辦了,將webrtc連接控制和流控制的邏輯抽取出來寫成一個工具類,activity界面只做展示邏輯,點擊最小化關閉activity,跳轉到懸浮窗邏輯。懸浮窗邏輯這為了邏輯清晰我們可以放在一個service里創建,代碼如下
public classVoipFloatServiceextendsService {
private static finalStringTAG="FloatService";
privateWindowManagermWindowManager;
privateWindowManager.LayoutParamsmLayoutParams;
/**
* 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 voidonCreate() {
super.onCreate();
LogEx.d(TAG,"onCreate: ");
createWindowManager();
createFloatView();
}
@Override
public voidonDestroy() {
super.onDestroy();
LogEx.d(TAG,"onDestroy: ");
removeFloatView();
}
private voidcreateWindowManager() {
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 voidcreateFloatView() {
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 voidonClick(View v) {
maxZoom2WebRtcActivity();
VoipFloatService.this.stopSelf();
}
});
VideoRendererGui.setView(glSurfaceView, newRunnable() {
@Override
public voidrun() {
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 voidremoveFloatView() {
LogEx.d(TAG,"removeFloatView: ");
if(mFloatView!=null&&mWindowManager!=null) {
mWindowManager.removeView(mFloatView);
}
}
/**
* 單擊后回到@WebRTCActivity以切換為大尺寸頁面
*/
private voidmaxZoom2WebRtcActivity() {
//TODO? 跳轉到Activity
}
}
當點擊最小化后關閉了視頻通話中的WebRtcActivity,并創建FloatService,在其中創建懸浮窗口并設置Touch事件使其可以隨手指滑動,將webrtc流渲染到懸浮窗口內的glSurfaceView上。
當點擊該窗口時,關閉floatService,移除懸浮窗口,跳轉打開WebRtcActivity。
以上就是Activity視頻頁面和懸浮窗口頁面互相切換的邏輯,效果如下圖
來自:http://www.jianshu.com/p/988021487b5b