代碼:https://github.com/zackLangChina/AutoMonitorPlayer
效果:


布局和拖拽功能介紹

將AutoMonitorPlayer作為控件時(shí),layout圖層關(guān)系如上圖。AutoMonitorPlayer內(nèi)部包含一個(gè)FrameLayout,將SurfaceView包含在內(nèi)。

當(dāng)切換到小窗模式時(shí),AutoMonitorPlayer將容器Framelayout刪除出子節(jié)點(diǎn),外部的根節(jié)點(diǎn)android.R.id.content再將容器FrameLayout加為子節(jié)點(diǎn):
//父節(jié)點(diǎn)變了,需要從老的父節(jié)點(diǎn)處刪掉容器
this.removeView(mContainer);
//android.R.id.content是MainActivity布局最外面的一層FrameLayout
ViewGroup contentView?=(ViewGroup)NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);
FrameLayout.LayoutParams params?=mConfig.getTinyWindowLayoutParams();contentView.addView(mContainer,params);
關(guān)于小窗的拖拽,自定義繼承自FrameLayout的類(DragFrameLayout)作為SurfaceView容器類,接收到onTouch回調(diào)的移動消息后,調(diào)用layout修改自己的位置,接收到up消息后,調(diào)用setLayoutParams修改自身窗口參數(shù):
//臨時(shí)修改視圖顯示
layout(oriLeft,oriTop,oriRight,oriBottom);
//這里修改gravity,使用left和top為Margins的定位參數(shù),只需要給這兩個(gè)值就可以了((LayoutParams)oldParams).gravity = Gravity.LEFT | Gravity.TOP;((LayoutParams)oldParams).setMargins(oriLeft,oriTop,0,0);setLayoutParams(oldParams);
必須要說明的是,目前這種實(shí)現(xiàn)方式,小窗還是和頁面綁定的。如果要全局窗口,需要新建全局彈窗,再將FrameLayout作為該全局彈窗的視圖子節(jié)點(diǎn)。

全局窗口需要使用AlertDialog(TinyDialog):
getWindow().getAttributes().type = ?WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
使用AlertDialog要注意權(quán)限的申請,除了AndroidManifest.xml中申明權(quán)限,如果是android6.0以上的SDK,還需要在代碼中動態(tài)申請權(quán)限:
context.startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,????????Uri.parse("package:" + context.getPackageName())), 0);
創(chuàng)建了全局窗口,我們就要把FrameLayout容器放入這個(gè)窗口:
mTinyDialg.addContentView(mContainer,new FrameLayout.LayoutParams(????????ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
如圖,和頁面內(nèi)小窗比較類似,不同的是AlertDiaolg不再屬于應(yīng)用中的布局,即使切出應(yīng)用也可見。
關(guān)于全局小窗的拖拽。頁面小窗其實(shí)改變的是Framelayout的窗口位置,而全局小窗改變的是AlertDialog的窗口位置。
有一點(diǎn)很重要,我們知道Android有一套觸摸消息傳遞機(jī)制,默認(rèn)情況下會先傳遞給子布局,如果消息沒有被消費(fèi)掉,再一層層往上傳遞。AlertDialog作為FrameLayout的父節(jié)點(diǎn),要想優(yōu)先消費(fèi)觸摸消息,必須進(jìn)行消息攔截,要覆寫dispatchTouchEvent方法:
@Overridepublic boolean dispatchTouchEvent(@NonNull MotionEvent ev) {????return onTouchEvent(ev);}
然后根據(jù)觸摸坐標(biāo)確定要移動到哪里,再通過WindowManager改變自己的位置:
mWindowManager.updateViewLayout(getWindow().getDecorView(), layoutParams);

全屏?xí)r,同樣是AutoMonitorPlayer將容器Framelayout刪除出子節(jié)點(diǎn),外部的根節(jié)點(diǎn)android.R.id.content再將容器FrameLayout加為子節(jié)點(diǎn),同時(shí),需要隱藏ActionBar、狀態(tài)欄,并根據(jù)需要設(shè)置屏幕方向。
接口介紹
接口名參數(shù)說明
