1. 程式人生 > >Android 懸浮窗基本使用

Android 懸浮窗基本使用

很多 iPhone 使用者都喜歡開啟一項設定,那就是 AssistiveTouch ,我們俗稱小白點,它位於整個螢幕之上,就像是漂浮在所有的 App 之上。Android 手機上也有很多應用有這樣的東西,比如 360 或者其他主要是手機管家之類的軟體。


在之前的公司專案中也有用到這樣的懸浮窗,雖然許可權有些敏感,但是還算是比較常用,所以在這裡記錄一下它的基本使用。

1、基本使用

它的基本使用步驟是不會變的,只是有時候我們可能根據不同需求會改變它樣式的引數,寫一個 demo ,首先記得在 manifests.xml 清單檔案中新增許可權(但是這個許可權好像並不一定所有的手機都會在程式執行的時候詢問是否允許新增懸浮窗,這是需要注意的地方):

    <!-- 允許程式新增懸浮窗 -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

然後在 Activity 中新增一個懸浮窗:

public class MainActivity extends AppCompatActivity {

    private WindowManager mWindowManager;
    private View floatWindowView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //Activity中的方法,得到視窗管理器
        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        //設定懸浮窗佈局屬性
        WindowManager.LayoutParams mWindowLayoutParams = new WindowManager.LayoutParams();
        //設定型別,具體有哪些值可取在後面附上
        mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        //設定行為選項,具體有哪些值可取在後面附上
        mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        //設定懸浮窗的顯示位置
        mWindowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
//        //設定懸浮窗的橫豎屏,會影響螢幕方向,只要懸浮窗不消失,螢幕方向就會一直保持,可以強制螢幕橫屏或豎屏
//        mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
        //設定 x 軸的偏移量
        mWindowLayoutParams.x = 0;
        //設定 y 軸的偏移量
        mWindowLayoutParams.y = 0;
        //如果懸浮窗圖片為透明圖片,需要設定該引數為 PixelFormat.RGBA_8888
        mWindowLayoutParams.format = PixelFormat.RGBA_8888;
        //設定懸浮窗的寬度
        mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        //設定懸浮窗的高度
        mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        //設定懸浮窗的佈局
        floatWindowView = LayoutInflater.from(this).inflate(R.layout.float_window, null);
        //載入顯示懸浮窗
        mWindowManager.addView(floatWindowView, mWindowLayoutParams);
    }

    @Override
    protected void onDestroy() {
        super.onStop();
        mWindowManager.removeView(floatWindowView);
    }

}

註釋中提到懸浮窗只要不消失,螢幕方向就會一直保持,如果設定懸浮窗橫屏,將程式退到後臺時,則手機桌面也會橫屏,看起來就會很不舒服,所以如果不是特定需求(比如我之前做的專案需要在某些時刻讓手機桌面也橫屏),我們可以在 onStart() 的時候新增懸浮窗,然後在 onStop() 方法的時候呼叫 WindowManager 的 removeView() 方法讓其消失,就不會影響螢幕方向了。需要注意的時候不能重複新增同一個懸浮窗,否則會報 java.lang.IllegalStateException: View android.widget.LinearLayout{3284dae1 V.E..... ......I. 0,0-0,0} has already been added to the window manager
的異常,所以在新增懸浮窗之前最好先判斷一下該懸浮窗是否已新增,具體怎麼做我們可以自己設定一個 boolean 型別的標識量預設為 false ,在 addView() 之後設為 true ,removeView() 之後設為 false ,新增之前則先進行判斷,true 則不新增,為 false 的時候才新增。

2、type 和 flags 可取值

2.1 type 可取值

應用程式視窗。
public static final int FIRST_APPLICATION_WINDOW = 1;

所有程式視窗的“基地”視窗,其他應用程式視窗都顯示在它上面。     
public static final int TYPE_BASE_APPLICATION =1;
    
普通應用功能程式視窗。token必須設定為Activity的token,以指出該視窗屬誰。
public static final int TYPE_APPLICATION = 2;

用於應用程式啟動時所顯示的視窗。應用本身不要使用這種型別。
它用於讓系統顯示些資訊,直到應用程式可以開啟自己的視窗。   
public static final int TYPE_APPLICATION_STARTING = 3; 
     
應用程式視窗結束。
public static final int LAST_APPLICATION_WINDOW = 99;

子視窗。子視窗的Z序和座標空間都依賴於他們的宿主視窗。
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;

應用程式視窗的子面板。顯示於所有面板視窗的上層。(GUI的一般規律,越“子”越靠上)
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW +2;

對話方塊。類似於面板視窗,繪製類似於頂層視窗,而不是宿主的子視窗。
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW +3;

媒體資訊。顯示在媒體層和程式視窗之間,需要實現透明(半透明)效果。(例如顯示字幕)
public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW +4;

子視窗結束。( End of types of sub-windows )
public static final int LAST_SUB_WINDOW = 1999;

系統視窗。非應用程式建立。
public static final int FIRST_SYSTEM_WINDOW = 2000;

狀態列。只能有一個狀態列;它位於螢幕頂端,其他視窗都位於它下方。
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;

資訊視窗。用於顯示toast。
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;

內部輸入法視窗,顯示於普通UI之上。應用程式可重新佈局以免被此視窗覆蓋。
public static final int TYPE_INPUT_METHOD =  FIRST_SYSTEM_WINDOW +11;

這個值我也不是每個都試過,所以具體效果會有什麼不同不能妄言,除了常用的 TYPE_PHONE 外我還使用過一個 TYPE_TOAST,因為剛才有說這個許可權不是所有的手機都會在程式第一次執行的時候去詢問是否允許該許可權,所以使用者不知道有這個許可權的存在,不允許該許可權的話,有些效果又出不來,當設定為 TYPE_TOAST 時,部分手機可以在不允許該許可權的情況下也能新增懸浮窗,但僅限於部分手機。

2.2 flags 可取值

視窗之後的內容變暗。  
public static final int FLAG_DIM_BEHIND = 0x00000002; 
  
視窗之後的內容變模糊。  
public static final int FLAG_BLUR_BEHIND = 0x00000004;
  
不許獲得焦點。不能獲得按鍵輸入焦點,所以不能向它傳送按鍵或按鈕事件。那些時間將傳送給它後面的可以獲得焦點的視窗。此選項還會設定FLAG_NOT_TOUCH_MODAL選項。設定此選項,意味著視窗不能與軟輸入法進行互動,所以它的Z序獨立於任何活動的輸入法(換句話說,它可以全屏顯示,如果需要的話,可覆蓋輸入法視窗)。要修改這一行為,可參考FLAG_ALT_FOCUSALBE_IM選項。  
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
  
不接受觸控式螢幕事件。  
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
  
當視窗可以獲得焦點(沒有設定 FLAG_NOT_FOCUSALBE 選項)時,仍然將視窗範圍之外的點裝置事件(滑鼠、觸控式螢幕)傳送給後面的視窗處理。否則它將獨佔所有的點裝置事件,而不管它們是不是發生在視窗範圍內。  
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_LAYOUT_NO_LIMITS =0x00000200;
  
視窗顯示時,隱藏所有的螢幕裝飾(例如狀態條)。使窗口占用整個顯示區域。  
public static final int FLAG_FULLSCREEN = 0x00000400;
  
此選項將覆蓋FLAG_FULLSCREEN選項,並強制螢幕裝飾(如狀態條)彈出。  
public static final int FLAG_FORCE_NOT_FULLSCREEN =0x00000800;
  
抖動。指 對半透明的顯示方法。又稱“點透”。圖形處理較差的裝置往往用“點透”替代Alpha混合。  
public static final int FLAG_DITHER = 0x00001000;
 
不允許螢幕截圖。  
public static final int FLAG_SECURE = 0x00002000;
  
一種特殊模式,佈局引數用於指示顯示比例。  
public static final int FLAG_SCALED = 0x00004000;
  
當螢幕有可能貼著臉時,這一選項可防止面頰對螢幕造成誤操作。  
public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;
  
當請求佈局時,你的視窗可能出現在狀態列的上面或下面,從而造成遮擋。當設定這一選項後,視窗管理器將確保視窗內容不會被裝飾條(狀態列)蓋住。  
public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
  
反轉FLAG_NOT_FOCUSABLE選項。如果同時設定了FLAG_NOT_FOCUSABLE選項和本選項,視窗將能夠與輸入法互動,允許輸入法視窗覆蓋;如果FLAG_NOT_FOCUSABLE沒有設定而設定了本選項,視窗不能與輸入法互動,可以覆蓋輸入法視窗。  
public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
  
如果你設定了FLAG_NOT_TOUCH_MODAL,那麼當觸屏事件發生在視窗之外事,可以通過設定此標誌接收到一個MotionEvent.ACTION_OUTSIDE事件。注意,你不會收到完整的down/move/up事件,只有第一次down事件時可以收到ACTION_OUTSIDE。  
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
  
當螢幕鎖定時,視窗可以被看到。這使得應用程式視窗優先於鎖屏介面。可配合FLAG_KEEP_SCREEN_ON選項點亮螢幕並直接顯示在鎖屏介面之前。可使用FLAG_DISMISS_KEYGUARD選項直接解除非加鎖的鎖屏狀態。此選項只用於最頂層的全螢幕視窗。  
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
  
請求系統牆紙顯示在你的視窗後面。視窗必須是半透明的。  
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
  
視窗一旦顯示出來,系統將點亮螢幕,正如使用者喚醒裝置那樣。  
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
  
解除鎖屏。只有鎖屏介面不是加密的才能解鎖。如果鎖屏介面是加密的,那麼使用者解鎖之後才能看到此視窗,除非設定了FLAG_SHOW_WHEN_LOCKED選項。  
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
  
鎖屏介面淡出時,繼續執行它的動畫。  
public static final int FLAG_KEEP_SURFACE_WHILE_ANIMATING =0x10000000;
  
以原始尺寸顯示視窗。用於在相容模式下執行程式。  
public static final int FLAG_COMPATIBLE_WINDOW = 0x20000000;
  
用於系統對話方塊。設定此選項的視窗將無條件獲得焦點。  
public static final int FLAG_SYSTEM_ERROR = 0x40000000;

3、小結

最近待業中,找工作的同時在寫一個小時候玩過的遊戲——魔塔,準備用 Android 寫一個,方便無聊的時候在手機上玩玩,是準備完全移植從前網頁上的那個小遊戲,所以最近也沒有寫部落格。但需要用到懸浮窗的時候才發現雖然之前寫過,但是也不能直接敲出來程式碼,還得複製,說明用過的東西並不一定會啊,好記性不如寫部落格,這次決定記錄下來,下次再用的時候就不用到處去百度了,無聊的時候看看,爭取默熟於心,今後不復制也能自己敲出來。