編輯:關於Android編程
最近在做一個關於屏幕鎖屏懸浮窗的功能,於是在網上搜索了很多安卓屏幕鎖屏的相關資料,鑒於網上的資料比較零碎,所以我在這裡進行整理總結。本文將從以下兩點對屏幕鎖屏進行解析:
1. 如何監聽系統屏幕鎖屏
2. 如何在鎖屏界面彈出懸浮窗
經過總結,監聽系統的鎖屏可以通過以下兩種方式:
1) 代碼直接判定
2) 接收廣播
1) 代碼直接判定
代碼判斷方式,也有兩種方法:
a) 通過PowerManager的isScreenOn方法,代碼如下:
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); //如果為true,則表示屏幕“亮”了,否則屏幕“暗”了。 boolean isScreenOn = pm.isScreenOn();
這裡需要解釋一下:
屏幕“亮”,表示有兩種狀態:a、未鎖屏 b、目前正處於解鎖狀態 。這兩種狀態屏幕都是亮的;
屏幕“暗”,表示目前屏幕是黑的 。
b) 通過KeyguardManager的inKeyguardRestrictedInputMode方法,代碼如下:
KeyguardManager mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); boolean flag = mKeyguardManager.inKeyguardRestrictedInputMode();
對flag進行一下說明,經過試驗,總結為:
如果flag為true,表示有兩種狀態:a、屏幕是黑的 b、目前正處於鎖屏狀態 。
如果flag為false,表示目前未鎖屏
注明:上面的兩種方法,也可以通過反射機制來調用。
反射代碼如下:
private static Method mReflectScreenState; try { mReflectScreenState = PowerManager.class.getMethod(isScreenOn, new Class[] {}); PowerManager pm = (PowerManager) context.getSystemService(Activity.POWER_SERVICE); boolean isScreenOn= (Boolean) mReflectScreenState.invoke(pm); } catch (Exception e) { e.printStackTrace() }
2) 接收廣播
當安卓系統鎖屏或者屏幕亮起,或是屏幕解鎖的時候,系統內部都會發送相應的廣播,我們只需要對廣播進行監聽就可以了
注冊廣播的偽代碼如下:
private ScreenBroadcastReceiver mScreenReceiver; private class ScreenBroadcastReceiver extends BroadcastReceiver { private String action = null; @Override public void onReceive(Context context, Intent intent) { action = intent.getAction(); if (Intent.ACTION_SCREEN_ON.equals(action)) { // 開屏 } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 鎖屏 } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // 解鎖 } } } private void startScreenBroadcastReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_USER_PRESENT); context.registerReceiver(mScreenReceiver, filter); }
竟然知道了對於系統屏幕監聽的方法,那麼接下來就是要在屏幕鎖屏的時候,彈出懸浮框了,這個的實現方式有兩種:
1) 使用WindowManager
2) 使用Activity
目前情況是,使用這兩種方式在真機上都可以實現,如果網友們發現有問題,可以在博客中留言
1) 使用WindowManager
代碼如下:
private void init(Context mContext) { this.mContext = mContext; mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); // 更新浮動窗口位置參數 靠邊 DisplayMetrics dm = new DisplayMetrics(); // 獲取屏幕信息 mWindowManager.getDefaultDisplay().getMetrics(dm); mScreenWidth = dm.widthPixels; mScreenHeight = dm.heightPixels; this.mWmParams = new WindowManager.LayoutParams(); // 設置window type if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mWmParams.type = WindowManager.LayoutParams.TYPE_TOAST; } else { mWmParams.type = WindowManager.LayoutParams.TYPE_PHONE; } // 設置圖片格式,效果為背景透明 mWmParams.format = PixelFormat.RGBA_8888; // 設置浮動窗口不可聚焦(實現操作除浮動窗口外的其他可見窗口的操作) mWmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 調整懸浮窗顯示的停靠位置為左側置?? mWmParams.gravity = Gravity.LEFT | Gravity.TOP; mScreenHeight = mWindowManager.getDefaultDisplay().getHeight(); // 以屏幕左上角為原點,設置x、y初始值,相對於gravity mWmParams.x = 0; mWmParams.y = mScreenHeight / 2; // 設置懸浮窗口長寬數據 mWmParams.width = LayoutParams.WRAP_CONTENT; mWmParams.height = LayoutParams.WRAP_CONTENT; addView(createView(mContext)); mWindowManager.addView(this, mWmParams); mTimer = new Timer(); hide(); }
WindowManager的主要配置就是上面的那些代碼,這裡需要說明一下,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; 內部輸入法對話框,顯示於當前輸入法窗口之上。 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 LAST_SYSTEM_WINDOW = 2999;
如果想讓懸浮窗在所以鎖屏之上,使用TYPE_SYSTEM_ERROR,因為它顯示在所有內容之上。
2) 使用Activity
Activity的設置
Activity需要進行以下設置,才可以在鎖屏狀態下彈窗。
首先是onCreate方法,需要添加4個標志,如下:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Window win = getWindow(); win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); // 自己的代碼 }
四個標志位顧名思義,分別是鎖屏狀態下顯示,解鎖,保持屏幕長亮,打開屏幕。這樣當Activity啟動的時候,它會解鎖並亮屏顯示。
然後在AndroidManifest.xml文件當中,對該activity的聲明需要加上以下屬性:
而對於布局文件,要顯示的view居中,背景透明。由於上面已經設置了背景為壁紙的背景,所以顯示的是桌面的背景。如果背景設為默認的白色,則導致彈窗後面是一片白色,看起來很丑。如果背景設置為透明,則彈窗後面會顯示出解鎖後的界面(即使有鎖屏密碼,也是會顯示解鎖後的界面的),一樣很影響視覺效果。
在廣播中啟動鎖屏彈窗
我們設置的是鎖屏下才彈窗的,非鎖屏下就不適合彈出這個窗口了(你可以試一下,效果會很怪)。一般是注冊一個廣播接收器,在接收到指定廣播之後判斷是否需要彈窗,所以在BroadcastReceiver的接收代碼中需要先判斷是否為鎖屏狀態下:
@Override public void onReceive(Context context, Intent intent) { Log.d(LOG_TAG, intent.getAction()); KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); if (km.inKeyguardRestrictedInputMode()) { Intent alarmIntent = new Intent(context, AlarmActivity.class); alarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(alarmIntent); } }
這裡用到的是KeyguardManager類,用來管理鎖屏的,4.1之後該類的API新增了一個isKeyguardLocked()的方法判斷是否鎖屏,但在4.1之前,我們只能用inKeyguardRestrictedInputMode()方法,如果為true,即為鎖屏狀態。需要注意的是,在廣播中啟動Activity的context可能不是Activity對象,所以需要添加NEW_TASK的標志,否則啟動時可能會報錯。我們就可以結合之前的系統發送廣播後進行相應的懸浮窗的彈出處理。
復寫onNewIntent方法
再次亮起屏幕,如果該Activity並未退出,但是被手動按了鎖屏鍵,當前面的廣播接收器再次去啟動它的時候,屏幕並不會被喚起,所以我們需要在activity當中添加喚醒屏幕的代碼,這裡用的是電源鎖。可以添加在onNewIntent(Intent intent),因為它會被調用。也可以添加在其他合適的生命周期方法。添加代碼如下:
PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE); if (!pm.isScreenOn()) { PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright"); wl.acquire(); wl.release(); }
最後,是添加如下權限
第一條是解鎖屏幕需要的,第二條是申請電源鎖需要的。
大家在網上購物時都有這樣一個體驗,在確認訂單選擇收貨人以及地址時,會跳轉頁面到我們存入網站內的所有收貨信息(包含收貨地址,收貨人)的界面供我們選擇,一旦我們點擊其中某一條
先讓大家看看效果圖吧,相信很多Android初學者都想知道這中效果是怎麼實現的,來上圖: 想實現上面這張圖中的自定義加載樣式,其實很簡單,首先我們需要的布局組件有Proc
從1月份Google發布的16個關於性能優化的系列視頻起,這段時間在各大社區都有看到很多優秀的關於優化系列的文章。有分析了性能產生的原因、有分享如何優化我們的應用、有介紹
上一篇,我們從源碼的角度分析了View的事件分發過程,這篇我們從實例的角度來看看不同實例下具體的輸出會是什麼樣子的呢?好的,我們開始吧!同樣我們的測試布局文件: