文章目錄
WindowManager(窗口管理服務),它是顯示View的最底層,Toast,Activity,Dialog的底層都有用到了這個WindowManager。WindowManager裏面主要是addView,removeView,updateViewLayout這幾個方法來顯示View,以及通過WindowManager.LayoutParams這個API來設置相關的屬性。下文講詳細介紹。
WindowsManager
1、WindowsManager使用方法
1
//獲取WindowManager對象
WindowManager wManager = getApplicationContext().getSystemService(Context.WINDOW_ SERVICE);
//獲得WindowManager.LayoutParams對象,爲後續操作作準備
WindowManager.LayoutParams wmParams=new WindowManager.LayoutParams();
//...WindowManager.LayoutParams一頓設置
wManager.addView(textView, wmParams);
2
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button floatingButton = new Button(this);
floatingButton.setText("button");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0,
PixelFormat.TRANSPARENT
);
// flag 設置 Window 屬性
layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
// type 設置 Window 類別(層級)
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
layoutParams.gravity = Gravity.CENTER;
WindowManager windowManager = getWindowManager();
windowManager.addView(floatingButton, layoutParams);
}
}
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
3全屏設置
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//去除標題欄
requestWindowFeature(Window.FEATURE_NO_TITLE);
//去除狀態欄
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);//要放到加載佈局文件代碼之前
initView();
initData();
initListener();
}
2、WindowManager的關聯類
2.1、 WindowManager
先來看看WindowManager裏面有什麼。
Android_8_0_r36_\frameworks\base\core\java\android\view\WindowManager.java
public interface WindowManager extends ViewManager {
int DOCKED_INVALID = -1;
int DOCKED_LEFT = 1;
int DOCKED_TOP = 2;
int DOCKED_RIGHT = 3;
int DOCKED_BOTTOM = 4;
final static String INPUT_CONSUMER_PIP = "pip_input_consumer";
final static String INPUT_CONSUMER_NAVIGATION = "nav_input_consumer";
final static String INPUT_CONSUMER_WALLPAPER = "wallpaper_input_consumer";
public static class BadTokenException extends RuntimeException {
public BadTokenException() {}
public BadTokenException(String name) { super(name);}
}
public static class InvalidDisplayException extends RuntimeException {
public InvalidDisplayException() {}
public InvalidDisplayException(String name) { super(name);}
}
public Display getDefaultDisplay();
public void removeViewImmediate(View view);
public interface KeyboardShortcutsReceiver {
void onKeyboardShortcutsReceived(List<KeyboardShortcutGroup> result);
}
final int TAKE_SCREENSHOT_FULLSCREEN = 1;
final int TAKE_SCREENSHOT_SELECTED_REGION = 2;
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
......
}
......
}
WindowManager是一個接口,繼承ViewManager。裏面有一些實例屬性和方法,還有一個LayoutParams佔據着很大一部分。LayoutParams是一個序列化類。而且WindowManager裏面的大部分都是隱藏的屬性和方法,只給系統調用。
2.2、ViewManager
WindowManager繼承ViewManager。ViewManager裏面實際上只有三個方法:addView
、updateViewLayout
、removeView
,分別對應着添加view、更新viewlayout、移走view。
\frameworks\base\core\java\android\view\ViewManager.java
package android.view;
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
2.3、WindowManagerImpl
那麼WindowManager的實現類是哪個?是WindowManagerImpl。
Android_8_0_r36_\frameworks\base\core\java\android\view\WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
private IBinder mDefaultToken;
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
return new WindowManagerImpl(displayContext, mParentWindow);
}
public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
// Only use the default token if we don't have a parent window.
if (mDefaultToken != null && mParentWindow == null) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// Only use the default token if we don't already have a token.
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
@Override
public void requestAppKeyboardShortcuts(
final KeyboardShortcutsReceiver receiver, int deviceId) {
IResultReceiver resultReceiver = new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
List<KeyboardShortcutGroup> result =
resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY);
receiver.onKeyboardShortcutsReceived(result);
}
};
try {
WindowManagerGlobal.getWindowManagerService()
.requestAppKeyboardShortcuts(resultReceiver, deviceId);
} catch (RemoteException e) {
}
}
@Override
public Display getDefaultDisplay() {
return mContext.getDisplay();
}
@Override
public Region getCurrentImeTouchRegion() {
try {
return WindowManagerGlobal.getWindowManagerService().getCurrentImeTouchRegion();
} catch (RemoteException e) {
}
return null;
}
}
可以看到實現類裏面主要的參數是下面這幾個。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
private IBinder mDefaultToken;
裏面最主要的是WindowManagerGlobal了,view的增刪改都是由它完成的。
WindowManagerGlobal裏面主要管理是一下一些參數。view的相關控制用ArrayList裝載着。
Android_8_0_r36_\frameworks\base\core\java\android\view\WindowManagerGlobal.java
private static WindowManagerGlobal sDefaultWindowManager;
private static IWindowManager sWindowManagerService;
private static IWindowSession sWindowSession;
private final Object mLock = new Object();
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
private Runnable mSystemPropertyUpdater;
addView
Android_8_0_r36_\frameworks\base\core\java\android\view\WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
updateViewLayout
Android_8_0_r36_\frameworks\base\core\java\android\view\WindowManagerGlobal.java
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
removeView
Android_8_0_r36_\frameworks\base\core\java\android\view\WindowManagerGlobal.java
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
3、WindowManager 的一些屬性
WindowManager.LayoutParams屬性裏面主要有Type屬性和Flags屬性。
Type表示Window的類型,Window有三種類型,分別是應用窗口、子窗口和系統窗口。
\frameworks\base\core\java\android\view\ViewManager.java
/**
* Start of window types that represent normal application windows.
*/
public static final int FIRST_APPLICATION_WINDOW = 1;
public static final int TYPE_BASE_APPLICATION = 1;//一個應用程序窗口,作爲“基礎”窗口
public static final int TYPE_APPLICATION = 2;//正常的應用程序窗口
public static final int TYPE_APPLICATION_STARTING = 3;//特殊應用程序窗口顯示
public static final int TYPE_DRAWN_APPLICATION = 4;//顯示前繪畫
public static final int LAST_APPLICATION_WINDOW = 99;//End of types of application windows.
//子窗口
public static final int FIRST_SUB_WINDOW = 1000;
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;//應用面板
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;//窗口顯示媒體(如視頻)
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;//應用程序窗口的頂部
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;//類似TYPE_APPLICATION_PANEL,佈局爲頂級窗口
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;//顯示媒體層疊窗口
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;//上面一個sub-panel之上的應用程序的窗口
public static final int LAST_SUB_WINDOW = 1999;//子系統結尾
/**
* Start of system-specific window types.
*/
public static final int FIRST_SYSTEM_WINDOW = 2000;//These are not normally created by applications.
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;//狀態欄。只能有一個狀態欄
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;//搜索欄。只能有一個搜索欄
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;//電話
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;//系統窗口,如低功率警覺
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;//鍵盤守衛窗口
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;//臨時通知。
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;//系統覆蓋窗口,它需要顯示出來
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;//優先電話界面,需要即使顯示
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;//從狀態欄面板幻燈片
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;//鍵盤守衛顯示對話
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;//內部系統錯誤窗口,出現在上面
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;//內部輸入法窗口,上面出現
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;//內部輸入方法對話框窗口,上面出現
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;//壁紙窗口,放在任何想要的窗口
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;//面板幻燈片從狀態欄
public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;//安全系統覆蓋窗口
public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16;//拖動圖層,放在最上面
public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;//從狀態欄下面板,幻燈片
public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;//(鼠標)指針
public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;//導航欄(有別於狀態欄時)
public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;//音量級別覆蓋/對話框顯示
public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;//引導進度對話框,在所有窗口之上
public static final int TYPE_INPUT_CONSUMER = FIRST_SYSTEM_WINDOW+22;//當系統UI隱藏時候輸入事件
public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;//屏保窗口
public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;//導航欄面板
public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;//顯示窗口覆蓋
public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;//放大窗口覆蓋
public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;//私有虛擬顯示在頂部的描述
public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;//聲音交互
public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;
public static final int TYPE_DOCK_DIVIDER = FIRST_SYSTEM_WINDOW+34;
public static final int TYPE_QS_DIALOG = FIRST_SYSTEM_WINDOW+35;
public static final int TYPE_SCREENSHOT = FIRST_SYSTEM_WINDOW + 36;
public static final int TYPE_PRESENTATION = FIRST_SYSTEM_WINDOW + 37;
public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
public static final int LAST_SYSTEM_WINDOW = 2999;//系統窗口結尾
應用窗口從1開始,到99。子窗口範圍爲1000 ~ 1999 ,系統窗口範圍爲2000~2999。子窗口表示依賴於應用窗口的窗口,比如PopupWindow就是依賴於應用的子窗口。Android系統有個X,Y,Z軸的座標體系。X表示橫軸,Y表示豎軸,Z表示垂直於平面的軸。這些窗口數值對應的事Z軸。數字越大表示越靠近用戶。從上面的數值可以看出,系統窗口是最靠近用戶的,其次是子窗口,最後是應用窗口。
Flags參數表示Window的屬性,控制着Window的顯示特性。
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
FLAG_DIM_BEHIND
FLAG_NOT_FOCUSABLE//不獲取焦點,不接收各種輸入事件,由後面的窗口得到焦點
FLAG_NOT_TOUCHABLE
FLAG_NOT_TOUCH_MODAL//不獲取觸摸事件,由後面的窗口得到觸摸事件
FLAG_TOUCHABLE_WHEN_WAKING
FLAG_KEEP_SCREEN_ON
FLAG_LAYOUT_IN_SCREEN
FLAG_LAYOUT_NO_LIMITS
FLAG_FULLSCREEN
FLAG_FORCE_NOT_FULLSCREEN
FLAG_SECURE
FLAG_SCALED
FLAG_IGNORE_CHEEK_PRESSES
FLAG_LAYOUT_INSET_DECOR
FLAG_ALT_FOCUSABLE_IM
FLAG_WATCH_OUTSIDE_TOUCH
FLAG_SHOW_WHEN_LOCKED //讓Window顯示在鎖屏的界面上
FLAG_SHOW_WALLPAPER
FLAG_TURN_SCREEN_ON
FLAG_DISMISS_KEYGUARD
FLAG_SPLIT_TOUCH
FLAG_HARDWARE_ACCELERATED
FLAG_LOCAL_FOCUS_MODE
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
Flags標識按位來增加,比如下面這這些。當幾個功能疊加的時候可以用或運算。
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
public static final int FLAG_DIM_BEHIND = 0x00000002;
public static final int FLAG_BLUR_BEHIND = 0x00000004;
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
......
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
......
不止這兩個屬性設置,還有softInputMode,gravity,horizontalMargin,verticalMargin,screenBrightness,buttonBrightness,rotationAnimation等等的參數設置。WindowManager.LayoutParams裏面有addFlags
、clearFlags
、setColorMode
等來控制這些屬性的設置。
softInputMode是軟鍵盤設置,可以看下它的幾個參數。
SOFT_INPUT_STATE_UNSPECIFIED,
SOFT_INPUT_STATE_UNCHANGED,
SOFT_INPUT_STATE_HIDDEN,
SOFT_INPUT_STATE_ALWAYS_HIDDEN,
SOFT_INPUT_STATE_VISIBLE,
SOFT_INPUT_STATE_ALWAYS_VISIBLE,
SOFT_INPUT_ADJUST_UNSPECIFIED,
SOFT_INPUT_ADJUST_RESIZE,
SOFT_INPUT_ADJUST_PAN,
SOFT_INPUT_ADJUST_NOTHING,
SOFT_INPUT_IS_FORWARD_NAVIGATION,