Android學習之理解Window和WindowManager(一)

這篇文章作爲自己自學的總結,主要是借鑑任玉剛的《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開發藝術探索》第八章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章