這篇文章作爲自己自學的總結,主要是借鑑任玉剛的《Android開發藝術探索》。希望大家能喜歡。
前言
Window表示一個窗口的概念,我們在日常開發中直接接觸Window的機會並不多,但是在某些特殊時候我們需要在桌面上顯示一個類似懸浮窗的東西,那麼這種效果就需要用到Window來實現。簡單來說就是在桌面顯示我們想要的東西,就需要用到Window來實現。
1、Window、WindowManager和WindowManagerService三者簡單的關係
Window是一個抽象類,它的具體實現是PhoneWindow。創建一個Window對象只需要通過WindowManager即可實現。WindowManager是外界訪問Window的入口,Window的具體實現位於WindowManagerService中,WindowManager和WindowManagerService的交互是一個IPC過程。
需要注意的是:在Android中所有界面都是通過Window來呈現的,不論是Activity、還是Dialog以及Toast,它們都依附在Window上。所以Window實際上是View的直接管理者。
2、掌握如何使用WindowManager添加一個Window
WindowManager windowManager = (WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);//獲取WindowManager實例
Button button = new Button(this);//實例一個按鈕
button.setText("我是測試");//爲按鈕添加內容
button.setOnClickListener(new View.OnClickListener() {//事件監聽也能夠實現
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,"我是測試",Toast.LENGTH_SHORT).show();
}
});
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,0,0, PixelFormat.TRANSPARENT);//獲取佈局參數
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; //添加flags
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;//添加type
layoutParams.gravity = Gravity.START | Gravity.TOP; //設置Window的對齊方式
layoutParams.x = 100;//設置Window在桌面顯示的x方面的位置
layoutParams.y = 300;//設置Window在桌面顯示的y方面的位置
windowManager.addView(button,layoutParams);//最後是將Window進行添加
3、瞭解掌握WindowManager.LayoutParams中的flags和type兩個參數
3.1、flags參數
Flags參數表示Window的屬性。我們可以通過設置 Flags的值來控制Window的顯示特性。我們這裏就只講解在上文代碼中用到的三種比較常用的值。查看官方文檔
FLAG_NOT_FOCUSABLE表示Window不需要獲取焦點,也不需要接收各種輸入事件,此標記會同時啓用FLAG_NOT_TOUCH_MODEL,最終事件會直接傳遞給下層的具有焦點的Window。
FLAG_NOT_TOUCH_MODEL在此模式下,系統會將當前Window區域以外的單擊事件傳遞給底層的Window,當前Window區域以內的單擊事件則自己處理。這個標記很重要,一般來首都需要開啓此標記,否則其他Window將無法收到單擊事件。
例如:我們將上文中的代碼FLAG_NOT_FOCUSABLE以及FLAG_NOT_TOUCH_MODEL兩個值去掉,會發現我們無論點擊何處,點擊事件只能被button層的Window所消耗,其它不能接收到事件。
FLAG_SHOW_WHEN_LOCKED開啓此模式可以讓Window顯示在鎖屏的界面上。
3.2、type參數
Type參數表示Window的類別。Window有三種類型,分別是應用Window
、子Window
以及系統Window
。Window是分層的,每一個Window都有對應的z-ordered,層級大的會覆蓋在層級小的Window的上面。這些層級範圍對應着WindowManager.LayoutParams的type參數。
應用類Window
對應着一個Activity。(層級範圍1~99)子Window
不能單獨存在,它需要附屬在特定的父Window之中,例如Dialog和PopWindow。(層級範圍1000~1999)系統Window
是需要聲明權限才能創建的Window,例如Toast和系統狀態欄。(層級範圍2000~2999)- 需要注意的是:
通過WindowManager給Window添加控件時,需要給LayoutParams指定type。
//表示系統層級
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
並且需要在AndroidManifest.xml中申請權限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
在6.0以上的系統需要自己去實現權限的申請。不然會報的異常
android.view.WindowManager$InvalidDisplayException: Unable to add window android.view.ViewRootImpl$W@1acab724 -- the specified window type is not valid
at android.view.ViewRootImpl.setView(ViewRootImpl.java:712)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:299)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
........
上述代碼可以將一個Button添加到屏幕 座標爲(100,300)的位置上。
4、瞭解WindowManager主要的三個方法
查看Window源碼可以看到,WindowManager接口是繼承ViewManager的,而ViewManager中提供了三種方法讓我們可以來操作View,一般我們用這三個功能就ok了。如下
public interface ViewManager
{
//添加View
public void addView(View view, ViewGroup.LayoutParams params);
//刷新View
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
//移除View
public void removeView(View view);
}
我們也可以將我們上面的例子實現拖動效果。代碼如下:
WindowManager windowManager;
Button button;
WindowManager.LayoutParams layoutParams;
/**
* 在WindowManager中添加一個Window
*/
private void windowManagerAddWindow() {
windowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);//獲取WindowManager實例
button = new Button(this);//實例一個按鈕
button.setText("我是測試");//爲按鈕添加內容
button.setOnTouchListener(this);
layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);//獲取佈局參數
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
layoutParams.gravity = Gravity.START | Gravity.TOP;
layoutParams.x = 100;
layoutParams.y = 350;
windowManager.addView(button, layoutParams);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();//相對於屏幕而言
int rawY = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
layoutParams.x = rawX;
layoutParams.y = rawY;
windowManager.updateViewLayout(button, layoutParams);
break;
default:
break;
}
return false;
}
參考
1、Android 8.0系統上使用WindowManager添加view的一個問題
2、《Android開發藝術探索》第八章