浅谈 Android 自定义锁屏页的发车姿势
?
作者:blowUp?,原文鏈接:http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653577446&idx=2&sn=940cfe45f8da91277d1046d90368d440&scene=4#wechat_redirect 鎖屏作為一種黑白屏?xí)r代就存在的手機(jī)功能,至今仍發(fā)揮著巨大作用,特別是觸屏?xí)r代的到來,鎖屏的功用被發(fā)揮到了極致。多少人曾經(jīng)在無聊的時(shí)候每隔幾分鐘劃開鎖屏再關(guān)上,孜孜不倦,其酸爽程度不亞于捏氣泡膜。確實(shí),一款漂亮的鎖屏能為手機(jī)增色不少,但鎖屏存在的核心目的主要是三個(gè):保護(hù)自己手機(jī)的隱私,防止誤操作,在不關(guān)閉系統(tǒng)軟件的情況下節(jié)省電量。 —— 由?騰訊Bugly?分享#一、為什么需要自定義鎖屏頁 鎖屏作為一種黑白屏?xí)r代就存在的手機(jī)功能,至今仍發(fā)揮著巨大作用,特別是觸屏?xí)r代的到來,鎖屏的功用被發(fā)揮到了極致。多少人曾經(jīng)在無聊的時(shí)候每隔幾分鐘劃開鎖屏再關(guān)上,孜孜不倦,其酸爽程度不亞于捏氣泡膜。確實(shí),一款漂亮的鎖屏能為手機(jī)增色不少,但鎖屏存在的核心目的主要是三個(gè):保護(hù)自己手機(jī)的隱私,防止誤操作,在不關(guān)閉系統(tǒng)軟件的情況下節(jié)省電量。
當(dāng)下,各個(gè)款式的手機(jī)自帶的系統(tǒng)鎖屏完全能夠滿足這些需求,而且美觀程度非凡,那么開發(fā)者為什么仍然需要構(gòu)建自定義鎖屏呢?讓我們?cè)囅胍粋€(gè)場(chǎng)景,一位正在使用音樂播放器聽歌的美女用戶,在沒有播放器自定義鎖屏的情況下,切換一首歌需要幾步(參考自同類文章):
這時(shí)的她估計(jì)已經(jīng)被廣場(chǎng)舞的歌曲騷擾了有10秒,續(xù)了10次命,這是我們程序員不愿意看到的,所以有必要依靠我們靈活的雙手構(gòu)建出自定義的音樂鎖屏頁,將切歌過程被壓縮為兩步:點(diǎn)亮屏幕和切歌,順便可以看看歌詞。如果再加個(gè)開啟和關(guān)閉自定義鎖屏的開關(guān),就能完美解決用戶的痛點(diǎn)。
#二、自定義鎖屏頁的基本原理 然而,要實(shí)現(xiàn)一個(gè)自定義鎖屏是一件繁瑣的事情,因?yàn)橄到y(tǒng)有100種方法讓這個(gè)非本地的鎖屏待不下去。但是,人類的智慧是無限的,程序員需要逆流而上。
Android系統(tǒng)實(shí)現(xiàn)自定義鎖屏頁的思路很簡(jiǎn)單,即在App啟動(dòng)時(shí)開啟一個(gè)service,在Service中時(shí)刻監(jiān)聽系統(tǒng)SCREEN_OFF的廣播,當(dāng)屏幕熄滅時(shí),Service監(jiān)聽到廣播,開啟一個(gè)鎖屏頁Activity在屏幕最上層顯示,該Activity創(chuàng)建的同時(shí)會(huì)去掉系統(tǒng)鎖屏(當(dāng)然如果有密碼是禁不掉的)。示意圖如下:
道理很簡(jiǎn)單,我們這里需要討論的是細(xì)節(jié)。
##1. 廣播注冊(cè) Service是普通的Service,在應(yīng)用啟動(dòng)時(shí)直接startService,與應(yīng)用同一個(gè)進(jìn)程即可。此外,SCREEN_OFF廣播監(jiān)聽必須是動(dòng)態(tài)注冊(cè)的,如果在AndroidManifest.xml中靜態(tài)注冊(cè)將無法接收到SCREEN_OFF廣播,這點(diǎn)在Android官方文檔中有明確說明,即需要通過如下代碼注冊(cè):
IntentFilter mScreenOffFilter = new IntentFilter(); mScreenOffFilter.addAction(Intent.ACTION_SCREEN_OFF); registerReceiver(mScreenOffReceiver, mScreenOffFilter); 對(duì)應(yīng)的BroadcastReceiver定義如下:private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {關(guān)于啟動(dòng)Activity時(shí)Intent的Flag問題,如果不添加FLAG_ACTIVITY_NEW_TASK的標(biāo)志位,會(huì)出現(xiàn)“Calling startActivity() from outside of an Activity”的運(yùn)行時(shí)異常,畢竟我們是從Service啟動(dòng)的Activity。Activity要存在于activity的棧中,而Service在啟動(dòng)activity時(shí)必然不存在一個(gè)activity的棧,所以要新起一個(gè)棧,并裝入啟動(dòng)的activity。使用該標(biāo)志位時(shí),也需要在AndroidManifest中聲明taskAffinity,即新task的名稱,否則鎖屏Activity實(shí)質(zhì)上還是在建立在原來App的task棧中。
標(biāo)志位FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,是為了避免在最近使用程序列表出現(xiàn)Service所啟動(dòng)的Activity,但這個(gè)標(biāo)志位不是必須的,其使用依情況而定。
##2. Activity設(shè)置 鎖屏的activity內(nèi)部也要做相應(yīng)的配置,讓activity在鎖屏?xí)r也能夠顯示,同時(shí)去掉系統(tǒng)鎖屏。當(dāng)然如果設(shè)置了系統(tǒng)鎖屏密碼,系統(tǒng)鎖屏是沒有辦法去掉的,這里考慮沒有設(shè)置密碼的情況。
典型的去掉系統(tǒng)鎖屏頁的方法是使用KeyguardManager,具體代碼如下:
KeyguardManager mKeyguardManager = (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE); KeyguardManager.KeyguardLock mKeyguardLock = mKeyguardManager.newKeyguardLock("CustomLockScreen"); mKeyguardLock.disableKeyguard();其中,KeyguardManager是鎖屏管理類,我們通過getSystemService()的方式獲取實(shí)例對(duì)象mKeyguardManager,調(diào)用該對(duì)象的newKeyguardLock()方法獲取KeyguardManager的內(nèi)部類KeyguardLock的實(shí)例mKeyguardLock,該方法傳入的字符串參數(shù)用于標(biāo)識(shí)是誰隱藏了系統(tǒng)鎖屏,最后調(diào)用mKeyguardLock的disableKeyguard()方法可以取消系統(tǒng)鎖屏。
上述方法已經(jīng)不推薦使用,可以使用更好的方法來替代。我們?cè)谧远x鎖屏Activity的onCreate()方法里設(shè)定以下標(biāo)志位就能完全實(shí)現(xiàn)相同的功能:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);FLAG_DISMISS_KEYGUARD用于去掉系統(tǒng)鎖屏頁,FLAG_SHOW_WHEN_LOCKED使Activity在鎖屏?xí)r仍然能夠顯示。當(dāng)然,不要忘記在Manifest中加入適當(dāng)?shù)臋?quán)限:
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>##3. 屏蔽按鍵 當(dāng)自定義鎖屏頁最終出現(xiàn)在手機(jī)上時(shí),我們總希望它像系統(tǒng)鎖屏頁那樣屹立不倒,所有的按鍵都不能觸動(dòng)它,只有通過劃瓶或者指紋才能解鎖,因此有必要對(duì)按鍵進(jìn)行一定程度上的屏蔽。針對(duì)只有虛擬按鍵的手機(jī),我們可以通過隱藏虛擬按鍵的方式部分解決這個(gè)問題,具體方法在后文會(huì)介紹。但是當(dāng)用戶在鎖屏頁底部滑動(dòng),隱藏后的虛擬按鍵還是會(huì)滑出,而且如果用戶是物理按鍵的話就必須進(jìn)行屏蔽了。
Back鍵和Menu鍵可以通過重寫onKeyDown()方法進(jìn)行屏蔽:
public boolean onKeyDown(int keyCode, KeyEvent event) { int key = event.getKeyCode(); switch (key) { case KeyEvent.KEYCODE_BACK: { return true; } case KeyEvent.KEYCODE_MENU:{ return true; } } return super.onKeyDown(keyCode, event); }Home鍵與Recent鍵(調(diào)出最近打開應(yīng)用的按鍵)的點(diǎn)擊事件是在framework層進(jìn)行處理的,因此onKeyDown與dispatchKeyEvent都捕獲不到點(diǎn)擊事件。關(guān)于這兩個(gè)按鍵的屏蔽方法,網(wǎng)上相關(guān)的資料有很多,有的用到了反射,有的通過改變Window的標(biāo)志位和Type等,總的來說這些方法只對(duì)部分android版本有效,有的則完全無法編譯通過。其實(shí),這么做的目的無非是為了實(shí)現(xiàn)一個(gè)純粹的鎖屏頁,但是這種做法有些畫蛇添足,容易造成鎖屏頁的異常崩潰,我們要滿足的是用戶在鎖屏頁的快捷操作,Home鍵和Recent鍵無關(guān)痛癢,完全可以不管,少一些套路,多一點(diǎn)真誠(chéng)嘛。
##4. 劃屏解鎖 做完以上幾步,當(dāng)屏幕熄滅后,再打開屏幕就能夠看到我們的自定義鎖屏頁了,但是這時(shí)候,就算劃破手指也無法解鎖。所以,接下來要實(shí)現(xiàn)劃屏解鎖。
劃瓶解鎖的基本思路很簡(jiǎn)單,當(dāng)手指在屏幕上滑動(dòng)時(shí),攔截并處理滑動(dòng)事件,使鎖屏頁面隨著手指運(yùn)動(dòng),當(dāng)運(yùn)動(dòng)到達(dá)一定的閥值時(shí),用戶手指松開手指,鎖屏頁自動(dòng)滑動(dòng)到屏幕邊界消失,如果沒有達(dá)到運(yùn)動(dòng)閥值,就會(huì)自動(dòng)滑動(dòng)到起始位置,重新覆蓋屏幕。
為了將劃屏邏輯與頁面內(nèi)容隔離開來,我們?cè)阪i屏頁面布局中添加一個(gè)自定義的UnderView,這個(gè)UnderView填充整個(gè)屏幕,位于鎖屏內(nèi)容View(將其引用稱之為mMoveView,并傳入到UnderView中)的下方,所有劃屏相關(guān)的事件都在這里攔截并處理。
mMoveView是鎖屏頁的顯示內(nèi)容,除了處理一些簡(jiǎn)單的點(diǎn)擊事件,其他非點(diǎn)擊事件序列都由底層的UnderView進(jìn)行處理。只需要重寫UnderView的onTouchEvent方法就能夠?qū)崿F(xiàn):
Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); final float nx = event.getX(); switch (action) { case MotionEvent.ACTION_DOWN: mStartX = nx; onAnimationEnd(); case MotionEvent.ACTION_MOVE: handleMoveView(nx); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: doTriggerEvent(nx); break; } return true; }其中,mStartX記錄滑動(dòng)操作起始的x坐標(biāo),handleMoveView方法控制mMoveView隨手指的移動(dòng),doTriggerEvent處理手指離開后mMoveView的移動(dòng)動(dòng)畫。兩個(gè)方法的定義如下:
private void handleMoveView(float x) { float movex = x - mStartX; if (movex < 0) movex = 0; mMoveView.setTranslationX(movex); float mWidthFloat = (float) mWidth;//屏幕顯示寬度 if(getBackground()!=null){ getBackground().setAlpha((int) ((mWidthFloat - mMoveView.getTranslationX()) / mWidthFloat * 200));//初始透明度的值為200 } }在handleMoveView()中,首先計(jì)算當(dāng)前觸點(diǎn)x坐標(biāo)與初始x坐標(biāo)mStartX的差值movex,然后調(diào)用mMoveView的setTranslationX方法移動(dòng)。值得注意的是,目前setTranslationX方法只能在Android 3.0以上版本使用,如果采用動(dòng)畫兼容庫nineoldandroid中ViewHelper類提供的setTranslation方法,則沒有這個(gè)問題。scrollTo與scrollBy也可以實(shí)現(xiàn)移動(dòng),但是只是移動(dòng)View的內(nèi)容,并不能移動(dòng)View本身。另外就是通過修改布局參數(shù)LayoutParams實(shí)現(xiàn)移動(dòng),雖然沒有版本的限制,用起來相對(duì)復(fù)雜。這里我們采用setTranslationX,為了簡(jiǎn)潔,也是為了能夠與后續(xù)使用的屬性動(dòng)畫相統(tǒng)一。
此外,我們可以通過getBackground()獲取UnderView的背景,并根據(jù)已劃開屏幕占整個(gè)屏幕的百分比調(diào)用setAlpha方法改變背景的透明度,做出抽屜拉開時(shí)的光影變化效果。
private void doTriggerEvent(float x) { float movex = x - mStartX; if (movex > (mWidth * 0.4)) { moveMoveView(mWidth-mMoveView.getLeft(),true);//自動(dòng)移動(dòng)到屏幕右邊界之外,并finish掉 } else { moveMoveView(-mMoveView.getLeft(),false);//自動(dòng)移動(dòng)回初始位置,重新覆蓋 } } private void moveMoveView(float to,boolean exit){ ObjectAnimator animator = ObjectAnimator.ofFloat(mMoveView, "translationX", to); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {當(dāng)手指離開屏幕,doTraiggerEvent方法會(huì)對(duì)滑動(dòng)的距離與閥值進(jìn)行一個(gè)比較,此處的閥值為0.4*屏幕寬度,如果低于閥值,則通過ObjectAnimator在0.25s將mMoveView移動(dòng)到初始位置,同時(shí)在ObjectAnimator的AnimatorUpdateListener的onAnimationUpdate方法中更新背景透明度;如果低于閥值,以同樣的方式將mMoveView移出屏幕右邊界,然后將Activity干掉,具體做法是為animator增加一個(gè)AnimatorListenerAdapter的監(jiān)聽器,在該監(jiān)聽器的onAnimationEnd方法中使用在Activity中定義的mHandler發(fā)送finish消息,完成解鎖,效果如下圖:
#三、透明欄與沉浸模式 沉浸模式與透明欄是兩個(gè)不同的概念,由于某些原因,國(guó)內(nèi)一些開發(fā)或產(chǎn)品會(huì)把這兩個(gè)概念混淆。不過沒關(guān)系,在接下來的內(nèi)容我們會(huì)對(duì)這兩個(gè)概念進(jìn)行詳細(xì)的解釋和區(qū)分,并應(yīng)用這兩種不同的模式進(jìn)一步完善已經(jīng)初具模樣的鎖屏頁。
##1. 沉浸模式 什么是沉浸模式?從4.4開始,Android 為 “setSystemUiVisibility()”方法提供了新的標(biāo)記 “SYSTEM_UI_FLAG_IMMERSIVE”以及”SYSTEM_UI_FLAG_IMMERSIVE_STIKY”,就是我們所談的沉浸模式,全稱為 “Immersive Full-Screen Mode”,它可以使你的app隱藏狀態(tài)欄和導(dǎo)航欄,實(shí)現(xiàn)真正意義上的全屏體驗(yàn)。
之前 Android 也是有全屏模式的,主要通過”setSystemUiVisibility()”添加兩個(gè)Flag,即”SYSTEM_UI_FLAG_FULLSCREEN”,”SYSTEM_UI_FLAG_HIDE_NAVIGATION”(僅適用于使用導(dǎo)航欄的設(shè)備,即虛擬按鍵)。
這兩個(gè)標(biāo)記都存在一些問題,例如使用第一個(gè)標(biāo)記的時(shí)候,除非 App 提供暫時(shí)退出全屏模式的功能(例如部分電子書軟件中點(diǎn)擊一次屏幕中央位置),用戶是一直都沒法看見狀態(tài)欄的。這樣,如果用戶想去看看通知中心有什么通知,那就必須點(diǎn)擊一次屏幕,顯示狀態(tài)欄,然后才能調(diào)出通知中心。
而第二個(gè)標(biāo)記的問題在于,Google 認(rèn)為導(dǎo)航欄對(duì)于用戶來說是十分重要的,所以只會(huì)短暫隱藏導(dǎo)航欄。一旦用戶做其他操作,例如點(diǎn)擊一次屏幕,導(dǎo)航欄就會(huì)馬上被重新調(diào)出。這樣的設(shè)定對(duì)于看圖軟件,視頻軟件等等沒什么大問題,但是對(duì)于游戲之類用戶需要經(jīng)常點(diǎn)擊屏幕的 App,那就幾乎是悲劇了——這也是為什么你在 Android 4.4 之前找不到什么全屏模式會(huì)自動(dòng)隱藏導(dǎo)航欄的應(yīng)用。
Android 4.4 之后加入的Immersive Full-Screen Mode 允許用戶在應(yīng)用全屏的情況下,通過在原有的狀態(tài)欄/導(dǎo)航欄區(qū)域內(nèi)做向內(nèi)滑動(dòng)的手勢(shì)來實(shí)現(xiàn)短暫調(diào)出狀態(tài)欄和導(dǎo)航欄的操作,且不會(huì)影響應(yīng)用的正常全屏,短暫調(diào)出的狀態(tài)欄和導(dǎo)航欄會(huì)呈半透明狀態(tài),并且在一段時(shí)間內(nèi)或者用戶與應(yīng)用內(nèi)元素進(jìn)行互動(dòng)的情況下自動(dòng)隱藏,沉浸模式的四種狀態(tài)如下圖。(參考http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0616/3047.html)
狀態(tài)1代表沒有進(jìn)入沉浸模式時(shí)頁面的狀態(tài),仍然可以看到Status Bar和Navigation Bar;狀態(tài)2代表用戶第一次進(jìn)入沉浸模式時(shí),系統(tǒng)的提示彈窗,告訴用戶如何在沉浸模式下呼出Status Bar和Navigation Bar;狀態(tài)3代表沉浸模式,可以看到Status Bar和Navigation Bar都被隱藏;狀態(tài)4代表用戶在Sticky沉浸模式下呼出Status Bar和Navigation Bar,可以看到兩個(gè)Bar重新出現(xiàn),但是過一段時(shí)間能夠自動(dòng)隱藏。
一般來說,沉浸模式的標(biāo)記與其他Full Screen相關(guān)的Flag搭配起來才能達(dá)到我們想要的效果,即通過沉浸模式標(biāo)記規(guī)定狀態(tài)欄status bar和導(dǎo)航欄navigation bar顯示和隱藏的運(yùn)轉(zhuǎn)邏輯,通過其他標(biāo)簽設(shè)定狀態(tài)欄和導(dǎo)航欄顯示或隱藏,以及顯示或隱藏的樣子。這些常見的Flag及相應(yīng)功能如下表:
如此多的標(biāo)簽,看起來非常亂,但用起來卻非常簡(jiǎn)單和明確,感興趣的開發(fā)者可以自由搭配來測(cè)試一下。下面,我們通過一個(gè)例子,將這些標(biāo)簽應(yīng)用于鎖屏頁,實(shí)現(xiàn)對(duì)Navigation Bar的自動(dòng)隱藏,同時(shí)保留Status Bar。代碼非常簡(jiǎn)單,在Activity的onCreate()方法中使用:
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION );總共用到了5個(gè)Flag:SYSTEM_UI_FLAG_LAYOUT_STABLE保持整個(gè)View穩(wěn)定,使View不會(huì)因?yàn)镾ystemUI的變化而做layout;SYSTEM_UI_FLAG_IMMERSIVE_STIKY,能夠在隱藏的bar被呼出時(shí)(比如從屏幕下邊緣開始向上做滑動(dòng)手勢(shì)),使bar在無相關(guān)操作的情況下自動(dòng)再次隱藏;對(duì)于SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,開發(fā)者容易被其中的HIDE_NAVIGATION所迷惑,其實(shí)這個(gè)Flag沒有隱藏導(dǎo)航欄的功能,只是控制導(dǎo)航欄浮在屏幕上層,不占據(jù)屏幕布局空間;SYSTEM_UI_FLAG_HIDE_NAVIGATION,才是能夠隱藏導(dǎo)航欄的Flag;SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,由上面可知,也不能隱藏狀態(tài)欄,只是使?fàn)顟B(tài)欄浮在屏幕上層。
需要注意的是,這段代碼除了需要加在Activity的OnCreate()方法中,也要加在重寫的onWindowFocusChanged()方法中,在窗口獲取焦點(diǎn)時(shí)再將Flag設(shè)置一遍,否則可能導(dǎo)致無法達(dá)到預(yù)想的效果。
Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if(hasFocus){ getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION ); } }此外,有個(gè)部份要稍微留意一下,如果不希望界面的內(nèi)容被上拉到狀態(tài)欄(Status bar)的話,要記得在界面(Layout)XML文件中,在最外層Layout中將fitsSystemWindows屬性設(shè)置為true。如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true"> <!-- Content --> </RelativeLayout>設(shè)置了前文的5個(gè)Flag之后,鎖屏頁效果圖如下:
手指在屏幕底端上劃,Navigation Bar會(huì)彈出,懸浮于鎖屏頁底部,隨后自動(dòng)消失。Status Bar也按照我們預(yù)期的那樣,懸浮在上方,沒有隱藏。
##2. 透明欄 什么是透明欄?Google 在 Android 4.4 的 API 描述頁面里提到了“Translucent system UI styling”,即半透明化的系統(tǒng)UI風(fēng)格。這個(gè)“半透明化”包括了狀態(tài)欄和通知欄,當(dāng)開發(fā)者讓應(yīng)用支持這個(gè)新特性的時(shí)候,狀態(tài)欄和導(dǎo)航欄可以單獨(dú)/同時(shí)變?yōu)闈u變的半透明樣式,如下圖:
在 Android 5.0 之后引入了 Material Design,狀態(tài)欄和導(dǎo)航欄也玩出了更多花樣。現(xiàn)在除了原有的“半透明”模式以外,還有“全透明”以及“變色”模式,一種會(huì)完全隱藏背景,另一種可以取色作為背景顏色,多種樣式的透明欄如下圖(上圖為透明狀態(tài)欄,下圖為透明導(dǎo)航欄):
所以,透明欄只是能夠改變狀態(tài)欄和導(dǎo)航欄的顏色,并不像沉浸模式那樣隱藏狀態(tài)欄和導(dǎo)航欄,兩者是有本質(zhì)區(qū)別的。
對(duì)于Android 4.4以上5.0以下的版本,設(shè)置透明狀態(tài)欄的方式如下:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){Window window = getWindow();window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); }對(duì)于Android 5.0及以上版本,設(shè)置透明狀態(tài)欄的方法如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {Window window = getWindow();window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(0); }除了要清理掉4.4的FLAG_TRANSLUCENT_STATUS外,還要配合SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和SYSTEM_UI_FLAG_LAYOUT_STABLE,添加標(biāo)志位FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,并調(diào)用setStatusBarColor設(shè)置狀態(tài)欄的顏色為透明。
在綜合運(yùn)用了沉浸模式和透明欄之后,鎖屏頁效果如下:
#四、指紋解鎖 到這里,我們的鎖屏頁已經(jīng)基本完工,完全能夠非常優(yōu)雅地解決用戶的痛點(diǎn),但是跟當(dāng)下App自定義鎖屏頁的區(qū)別并不明顯。接下來對(duì)新型號(hào)手機(jī)普遍具備的指紋解鎖功能的考慮,則能夠?yàn)殒i屏頁增色不少。
##1. 指紋識(shí)別無法解鎖自定義鎖屏頁的問題 持有指紋解鎖手機(jī)的用戶在使用App自定義鎖屏頁時(shí)會(huì)出現(xiàn)一種困惑,當(dāng)你點(diǎn)亮屏幕,能夠看到自定義鎖屏頁,在使用指紋解鎖成功之后(部分機(jī)型指紋解鎖操作只能在系統(tǒng)鎖屏頁進(jìn)行),自定義鎖屏頁依然存在,你還是需要?jiǎng)濋_自定義鎖屏頁,才能看到手機(jī)主界面。
解決這一問題的方案是一種取巧的方法,那就是在鎖屏頁的service中監(jiān)聽ACTION_USER_PRESENT廣播。ACTION_USER_PRESENT廣播是系統(tǒng)鎖屏解鎖廣播,當(dāng)系統(tǒng)鎖屏頁解鎖時(shí)就會(huì)觸發(fā)。如果在接收到這一廣播時(shí),將自定義鎖屏頁finish掉,就能避免在指紋解鎖成功后自定義鎖屏頁仍然顯示的問題。但是細(xì)心的讀者會(huì)發(fā)現(xiàn)這種解法在邏輯上還存在問題,因?yàn)樵谟脩魶]有設(shè)置鎖屏密碼的情況下,前文自定義鎖屏頁在onCreate()時(shí)設(shè)置的FLAG_DISMISS_KEYGUARD標(biāo)志位能夠輕易解鎖系統(tǒng)的鎖屏頁,并觸發(fā)ACTION_USER_PRESENT廣播,此時(shí)自定義鎖屏頁的Service接收到這一廣播后,發(fā)finish廣播給自定義鎖屏頁,導(dǎo)致自定義鎖屏頁剛create就finish掉了,永遠(yuǎn)不可能出現(xiàn)。 因此,我們必須對(duì)場(chǎng)景進(jìn)行區(qū)分,只在有鎖屏密碼的情況下,才對(duì)接收到的ACTION_USER_PRESENT廣播進(jìn)行處理,finish自定義鎖屏頁。即在BroadcastReceiver的onReceive()方法中加入如下代碼:
if(intent.getAction().equals(Intent.ACTION_USER_PRESENT)) {if (VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {if (km.isKeyguardSecure()) {MLog.d(TAG, "KeyguardSecure!");Intent i = new Intent(NOTIFY_USER_PRESENT); context.sendBroadcast(i); } } }這里KeyguardManager對(duì)象km的isKeyguardSecure()方法就是用來判斷是否設(shè)置了鎖屏密碼。NOTIFY_USER_PRESENT是自定義廣播,用來通知鎖屏頁Activity調(diào)用finish方法。 這種做法是合理的,因?yàn)槿绻麤]有設(shè)置鎖屏密碼,FLAG_DISMISS_KEYGUARD標(biāo)志位解鎖系統(tǒng)鎖屏之后,到達(dá)上述代碼塊,isKeyguardSecure()返回為false,不會(huì)導(dǎo)致自定義鎖屏頁Activity的finish操作。而如果設(shè)置了鎖屏密碼,FLAG_DISMISS_KEYGUARD必然無法解鎖系統(tǒng)鎖屏,到達(dá)不了上述代碼塊,也不會(huì)finish。這樣就避免了自定義鎖屏頁剛創(chuàng)建出來就將自己finish掉的困境。另一方面,其他非FLAG_DISMISS_KEYGUARD方式觸發(fā)的解鎖,比如指紋解鎖,都會(huì)使Activity消失,滿足了需求。
##2. 自定義鎖屏頁下指紋識(shí)別無法使用的問題 此外,有些手機(jī)型號(hào),比如小米,在自定義鎖屏頁罩在系統(tǒng)鎖屏頁之上時(shí)(設(shè)置有鎖屏密碼),指紋解鎖是無效的,也就是必須要?jiǎng)濋_自定義鎖屏頁,在系統(tǒng)鎖屏頁上才能進(jìn)行指紋解鎖。為了改善這種體驗(yàn),我們可以在Activity中引入指紋解鎖API,識(shí)別指紋并解鎖,具體代碼如下:
private void startFingerPrintListening() { if (!isFingerprintAuthAvailable()) { return; } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED) { mFingerprintManager.authenticate(null, mCancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {當(dāng)然,不要忘記在Manifest中加入適當(dāng)?shù)臋?quán)限:
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>在調(diào)用指紋識(shí)別功能之前,我們需要判斷指紋識(shí)別功能是否可用,以及APP是否有相應(yīng)的權(quán)限。這一過程體現(xiàn)在isFingerprintAuthAvailable()中,第一步是獲取KeyguardManager對(duì)象,調(diào)用isKeyguardSecure()判斷是否設(shè)置有鎖屏密碼,如果有,則需進(jìn)一步判斷。checkSelfPermission用來判斷APP是否有指紋識(shí)別的權(quán)限(SDK 23要求),如果有則獲取FingerprintManager對(duì)象,調(diào)用該對(duì)象的isHardwareDetected()方法判斷指紋識(shí)別硬件是否可用,調(diào)用hasEnrolledFingerprints()判斷是否有事先錄入好的指紋,只有以上條件都滿足,接下來才能調(diào)用指紋識(shí)別功能。
指紋識(shí)別的調(diào)用體現(xiàn)在startFingerPrintListening()方法中,主要就是調(diào)用FingerprintManager的方法
authenticate(FingerprintManager.CryptoObject crypto, CancellationSignal cancel, int flags, FingerprintManager.AuthenticationCallback callback, Handler handler)其中,crypto參數(shù)代表Android6.0中crypto objects的wrapper class,可以通過該對(duì)象使authenticate過程更加安全,也可以不使用,這里我們將其設(shè)為null;cancel用來取消anthenticate(),我們new出一個(gè)對(duì)象傳入就可以;flags是標(biāo)志位,設(shè)置為0;callback為指紋識(shí)別回調(diào),包含指紋識(shí)別的核心方法:onAuthenticationError()是指紋匹配連續(xù)失敗后的回調(diào)(幾十秒后才能繼續(xù)匹配),onAuthenticationSucceeded()是指紋匹配成功的回調(diào),onAuthenticationFailed()是指紋匹配失敗時(shí)的回調(diào)。我們?cè)谶@幾個(gè)方法中做相應(yīng)的處理即可,在onAuthenticationSucceeded()方法中調(diào)用finish(),就能夠在指紋識(shí)別成功后關(guān)閉Activity。
#五、總結(jié) 通過以上內(nèi)容的分享,本鵝希望能夠?qū)Υ蠹业拈_發(fā)有所幫助,如果內(nèi)容有問題,也希望大家指點(diǎn)。綜上所述,在Android上實(shí)現(xiàn)自定義鎖屏頁并不是一件復(fù)雜的事情,關(guān)鍵是對(duì)一些技術(shù)點(diǎn)的把握要比較清楚。Service中啟動(dòng)Activity的正確方法,廣播靜態(tài)注冊(cè)與動(dòng)態(tài)注冊(cè)的差別,touch事件的分發(fā)傳播機(jī)制,透明欄與沉浸模式的綜合運(yùn)用,以及指紋識(shí)別新技術(shù)的應(yīng)用,都有很多值得推敲的地方。筆者當(dāng)初實(shí)現(xiàn)自定義鎖屏頁時(shí),沒有太多思考,有時(shí)照搬前人的做法,有時(shí)各種flag隨便添加,有時(shí)新舊API混淆,雖然實(shí)現(xiàn)了需求,但是代碼不夠簡(jiǎn)潔,可讀性也差。因此,在今后的開發(fā)過程中,除了要快速實(shí)現(xiàn)需求,還要在隨后的維護(hù)中,多多思考和研究,使代碼能夠達(dá)到“少一行不行,多一行難受”的境界。
轉(zhuǎn)載于:https://www.cnblogs.com/qianyukun/p/5855880.html
與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的浅谈 Android 自定义锁屏页的发车姿势的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php每天一题:strlen()与mb_
- 下一篇: 3.Android 优化布局(解决Tex