Android鎖屏實(shí)現(xiàn)與總結(jié)(網(wǎng)易云閱讀)
一、自定義鎖屏基本原理
二官疲、重要步驟
1、廣播注冊
2庶艾、Activity設(shè)置
3、按鍵的屏蔽
4擎勘、滑屏解鎖
5咱揍、Event bus的使用
三、出現(xiàn)的問題
1棚饵、小米和魅族等手機(jī)鎖屏權(quán)限問題
2煤裙、透明欄與沉浸模式
3、手機(jī)適配
4噪漾、處理黑色閃屏
5硼砰、線控耳機(jī)
6、Android上的「安全音量」
一欣硼、自定義鎖屏基本原理
先上效果圖:
實(shí)現(xiàn)鎖屏的方式有多種(鎖屏應(yīng)用题翰、懸浮窗、普通Activity偽造鎖屏等等)诈胜。通過網(wǎng)絡(luò)查找資料與反編譯云音樂apk豹障,本項目使用了國內(nèi)比較主流并且被廣泛應(yīng)用的Activity偽造鎖屏方式。
Activity實(shí)現(xiàn)自定義鎖屏頁的思路很簡單焦匈,即在聽書模式開啟時血公,啟動一個service,在service中監(jiān)聽系統(tǒng)SCREEN_OFF的廣播缓熟。當(dāng)屏幕熄滅時service監(jiān)聽到廣播累魔,開啟一個鎖屏頁Activity在屏幕最上層顯示,該Activity創(chuàng)建的同時會去掉系統(tǒng)的鎖屏(如果有密碼是禁不掉的)够滑。示意圖如下:
二垦写、重要步驟
1、廣播注冊
LockScreenService是普通的Service彰触,在應(yīng)用啟動聽書模式時候startService(ReadBookActivity)梯澜,與應(yīng)用同一個進(jìn)程。
此外,SCREEN_OFF廣播監(jiān)聽必須是動態(tài)注冊的晚伙,如果在AndroidManifest.xml中靜態(tài)注冊將無法接收到SCREEN_OFF廣播吮龄。
標(biāo)志位FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,是為了避免在最近使用程序列表出現(xiàn)Service所啟動的Activity咆疗。
啟動Activity時Intent的Flag漓帚,如果不添加FLAG_ACTIVITY_NEW_TASK的標(biāo)志位,會出現(xiàn)“Calling startActivity() from outside of an Activity”的運(yùn)行時異常午磁,因?yàn)槲覀兪菑腟ervice啟動的Activity尝抖。Activity要存在于activity的棧中,而Service在啟動activity時必然不存在一個activity的棧迅皇,所以要新起一個棧昧辽,并裝入啟動的activity。使用該標(biāo)志位時登颓,也需要在AndroidManifest中聲明taskAffinity搅荞,即新task的名稱,否則鎖屏Activity實(shí)質(zhì)上還是在建立在原來App的task棧中框咙。
2咕痛、Activity設(shè)置
鎖屏的activity內(nèi)部也要做相應(yīng)的配置,讓activity在鎖屏?xí)r也能夠顯示喇嘱,同時去掉系統(tǒng)鎖屏茉贡。當(dāng)然如果設(shè)置了系統(tǒng)鎖屏密碼,系統(tǒng)鎖屏是沒有辦法去掉的者铜,這里考慮沒有設(shè)置密碼的情況腔丧。我們在自定義鎖屏Activity的onCreate()方法里設(shè)定以下標(biāo)志位就能完全實(shí)現(xiàn)相同的功能:
FLAG_DISMISS_KEYGUARD用于去掉系統(tǒng)鎖屏頁,F(xiàn)LAG_SHOW_WHEN_LOCKED使Activity在鎖屏?xí)r仍然能夠顯示作烟。另外需要在Manifest中加入適當(dāng)?shù)臋?quán)限:
3悔据、按鍵的屏蔽
當(dāng)自定義鎖屏頁最終出現(xiàn)在手機(jī)上時,我們希望它像系統(tǒng)鎖屏頁那樣屹立不倒俗壹,所有的按鍵都不能觸動它科汗,只有通過劃屏或者指紋才能解鎖,因此有必要對按鍵進(jìn)行一定程度上的屏蔽绷雏。針對只有虛擬按鍵的手機(jī)头滔,我們可以通過隱藏虛擬按鍵的方式部分解決這個問題。但是當(dāng)用戶在鎖屏頁底部滑動涎显,隱藏后的虛擬按鍵還是會滑出坤检,而且如果用戶是物理按鍵的話就必須進(jìn)行屏蔽了。需要重寫Activity的onBackPressed()方法即可期吓。
Home鍵 與Recent鍵的點(diǎn)擊事件是在framework層進(jìn)行處理的早歇,因此onKeyDown()與dispatchKeyEvent()都捕獲不到點(diǎn)擊事件。關(guān)于這兩個按鍵的屏蔽方法,網(wǎng)上相關(guān)的資料有很多箭跳,有的用到了反射晨另,有的通過改變Window的標(biāo)志位和Type等,總的來說這些方法只對部分android版本有效谱姓,有的則完全無法編譯通過借尿。其實(shí)這么做的目的無非是為了實(shí)現(xiàn)一個純粹的鎖屏頁,但是這種做法容易造成鎖屏頁的異常崩潰屉来,我們要滿足的是用戶在鎖屏頁的快捷操作路翻,Home鍵和Recent鍵無關(guān)痛癢,基本可以不管茄靠。
4茂契、滑屏解鎖
做完以上幾步,當(dāng)屏幕熄滅后慨绳,再打開屏幕就能夠看到我們的自定義鎖屏頁了掉冶。接下來要實(shí)現(xiàn)劃屏解鎖。劃瓶解鎖的基本思路很簡單儡蔓,當(dāng)手指在屏幕上滑動時郭蕉,攔截并處理滑動事件疼邀,使鎖屏頁面隨著手指運(yùn)動喂江,當(dāng)運(yùn)動到達(dá)一定的閾值時,用戶手指松開手指旁振,鎖屏頁自動滑動到屏幕邊界消失获询,如果沒有達(dá)到運(yùn)動閥值,就會自動滑動到起始位置拐袜,重新覆蓋屏幕。 為了將劃屏邏輯與頁面內(nèi)容隔離開來,我們在鎖屏頁面布局中添加一個自定義的UnderView轴或,這個UnderView填充整個屏幕匈庭,位于鎖屏內(nèi)容View(將其引用稱之為mMoveView,并傳入到UnderView中)的下方,所有劃屏相關(guān)的事件都在這里攔截并處理甜攀。
mMoveView是鎖屏頁的顯示內(nèi)容秋泄,除了處理一些簡單的點(diǎn)擊事件,其他非點(diǎn)擊事件序列都由底層的UnderView進(jìn)行處理规阀。只需要重寫UnderView的onTouchEvent方法就能夠?qū)崿F(xiàn)
其中恒序,mStartX記錄滑動操作起始的x坐標(biāo),handleMoveView方法控制mMoveView隨手指的移動谁撼,doTriggerEvent處理手指離開后mMoveView的移動動畫歧胁。兩個方法的定義如下:
在handleMoveView()中,首先計算當(dāng)前觸點(diǎn)x坐標(biāo)與初始x坐標(biāo)mStartX的差值movex,然后調(diào)用mMoveView的setTranslationX方法移動喊巍。此外屠缭,我們可以通過getBackground()獲取UnderView的背景,并根據(jù)已劃開屏幕占整個屏幕的百分比調(diào)用setAlpha方法改變背景的透明度玄糟,做出抽屜拉開時的光影變化效果勿她。
當(dāng)手指離開屏幕,doTraiggerEvent方法會對滑動的距離與閥值進(jìn)行一個比較阵翎,此處的閥值為0.4*屏幕寬度逢并,如果低于閥值,則通過ObjectAnimator在0.25s將mMoveView移動到初始位置郭卫,同時在ObjectAnimator的AnimatorUpdateListener的onAnimationUpdate方法中更新背景透明度砍聊;如果低于閥值,以同樣的方式將mMoveView移出屏幕右邊界贰军,然后將Activity干掉玻蝌,具體做法是為animator增加一個AnimatorListenerAdapter的監(jiān)聽器,在該監(jiān)聽器的onAnimationEnd方法中使用在Activity中定義的mHandler發(fā)送finish消息词疼,完成解鎖俯树。
5、Event bus的使用
鎖屏Activity中的Buttun贰盗,可以通過EventBus遠(yuǎn)程控制ReadBookActivity中的播放许饿、暫停、下一首等操作舵盈。
二陋率、出現(xiàn)的問題
1、小米和魅族等手機(jī)鎖屏權(quán)限問題
例如小米手機(jī)(miui 6.8.18開始)有鎖屏權(quán)限的問題秽晚,權(quán)限未開的情況下鎖屏?xí)谙到y(tǒng)鎖屏的下方瓦糟。目前只能手動打開(個別應(yīng)用在MIUI是默認(rèn)打開)。
在設(shè)置中:
2赴蝇、透明欄與沉浸模式
透明欄與沉浸模式總共用到了5個Flag菩浙,SYSTEM_UI_FLAG_LAYOUT_STABLE保持整個View穩(wěn)定,使View不會因?yàn)镾ystemUI的變化而做layout句伶。SYSTEM_UI_FLAG_IMMERSIVE_STIKY劲蜻,能夠在隱藏的bar被呼出時(比如從屏幕下邊緣開始向上做滑動手勢),使bar在無相關(guān)操作的情況下自動再次隱藏熄阻。對于SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION斋竞,我們?nèi)菀妆黄渲械腍IDE_NAVIGATION所迷惑,其實(shí)這個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)時再將Flag設(shè)置一遍哀九,否則可能導(dǎo)致無法達(dá)到預(yù)想的效果剿配。
在Android 5.0之后狀態(tài)欄和導(dǎo)航欄也有更多的特點(diǎn)。除了原有的“半透明”模式以外阅束,還有“全透明”以及“變色”模式呼胚,一種會完全隱藏背景,另一種可以取色作為背景顏色等息裸。對于Android 4.4以上5.0以下的版本蝇更,設(shè)置透明狀態(tài)欄的方式如下:
對于Android 5.0及以上版本,設(shè)置透明狀態(tài)欄的方法如下:
除了要清理掉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)欄的顏色為透明访圃。
3厨幻、手機(jī)適配
由于Android手機(jī)屏幕高度差異比較大,所以有可能會存在要顯示的控件高度比屏幕還高的問題挽荠。所以需要通過代碼對布局中某些控件寬高進(jìn)行等比例調(diào)整克胳。
例如在本項目中通過固定音量鍵以上的高度和最下方滑動解鎖的位置平绩,來動態(tài)等比例動態(tài)調(diào)整中間的專輯封面圖片圈匆。mMain.post(new Runnable()。
4捏雌、處理黑色閃屏
我們的鎖屏Activity在滑動”解鎖”之后,理論上是直接進(jìn)入下面的界面,但有時如果下面不是launcher,而是一個app,有可能會閃一下黑屏,這個其實(shí)是底下activity的入場動畫導(dǎo)致的,某些Android版本會對頂部activity透明時處理有些奇怪,不能保證其他的應(yīng)用不閃黑屏,但是對自己的的應(yīng)用還是可以的,只需要在主體activity的style中加上
5跃赚、線控耳機(jī)
我們只要定義一個廣播接收者來接收到這個廣播。這個廣播的意圖是android.intent.action.MEDIA_BUTTON性湿。注冊普通的廣播接收器有兩個常見的辦法纬傲,一種是在代碼中動態(tài)注冊,另一種是在項目Manifest里面注冊肤频。但是這個廣播叹括,要注冊兩遍、兩遍宵荒、兩遍汁雷,Manifest里一遍(常規(guī)辦法)净嘀,代碼中一遍(借助多媒體服務(wù)注冊),否則沒有效果侠讯。
因?yàn)閮煞N注冊方式缺一不可挖藏,所以解除了一種,它的監(jiān)聽作用也就失效了厢漩。
6膜眠、Android上的「安全音量」
當(dāng)Android設(shè)備插上耳機(jī),為了避免音量過高傷害用戶聽力溜嗜,會觸發(fā)其“安全音量”(Safe Media Volume)機(jī)制宵膨,如果在未經(jīng)用戶確認(rèn)允許使用大音量時,且這時設(shè)置音量newIndex超過其推薦閾值炸宵,則這段代碼執(zhí)行完你會發(fā)現(xiàn)毫無反應(yīng)柄驻,播放的聲音依然不會很大。
解決問題的關(guān)鍵在于被忽略的最后一個參數(shù)flags焙压。只要在設(shè)置音量后鸿脓,復(fù)查一次當(dāng)前值是否相當(dāng),如果比較小涯曲,則交由系統(tǒng)來顯示音量提示對話框野哭。而此時因欲設(shè)定的值超過推薦值,一般會觸發(fā)音量過高警告幻件,提示用戶用戶確認(rèn)后即可設(shè)置成功拨黔。