search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

神級程序員教你打造炫酷的懸浮音樂盒

1.前言

說到懸浮窗大家一定會想到這倆貨

記得上一篇講的是用RotateDrawable實現網易雲音樂唱片機效果,而今天我要講的是如何用WindowManager去實現一個懸浮窗迷你音樂盒。

2

效果

怎麼樣,是否打動你繼續往下看呢?

3

了解WindowManager

在Android應用開發中,其實整個Android的窗口機制是基於一個叫做WindowManager的一個系統服務介面,WindowManager可以添加view到屏幕,也可以從屏幕刪除view。它面向的對象一端是屏幕,另一端就是View,其實就連我們常用的Activity和Diolog的底層實現都是通過WindowManager, WindowManager是全局的,整個系統就只用一個Windowmanager服務,我們需要向系統獲取服務才能調用它,而它就是顯示View的最底層。

其實WindowManager用起來非常方便,就三個方法:

添加View

從方法中我們可以看到,addView需要兩個參數,view簡單,就是我們要向窗口中去添加的對象,至於params,就是給窗口設置的顯示策略,包括窗口的大小、透明度等等,這個也是今天文章的重點,在後文會有所介紹。

移除View

既然能夠向窗口去添加View,當然也就能夠從窗口上移除View,這個很簡單view就是你要從窗口中移除的對象。

刷新View

同樣窗口刷新也需要兩個參數,和添加View一樣view是需要更新的對象,而params就是更新后的策略屬性。

這篇文章分享之前我還是要推薦下我自己的前端群:657*137*906,不管你是小白還是大牛,小編我都挺歡迎,不定期分享乾貨,包括我自己整理的一份2017最新的前端資料和零基礎入門教程,歡迎初學和進階中的小夥伴。。

WindowManager.LayoutParams

相比於WindowManager,WindowManager.LayoutParams可就要複雜好多了。WindowManager.LayoutParams是 WindowManager 介面的嵌套類,在窗口管理中扮演著重要的角色。它繼承於ViewGroup.LayoutParams,它用於向WindowManager描述窗口的管理策略;WindowManager.LayoutParams可以直接new WindowManager.LayoutParams新建,也可以從對窗口的getAttributes得到其WindowManager.LayoutParams對象。WindowManager.LayoutParams常用的有以下主要常量成員:

flag

  • WindowManager.LayoutParams.FLAG_SECURE 不允許截屏;設置了這個屬性的窗口,在窗口可見的情況下,是會禁用系統的截圖功能的。那麼問題來了:假如有一天,你的公司要求寫一個類似於『閱后即焚』功能的頁面的話,不妨在activity中獲得WindowManager.LayoutParams並添加該屬性,輕輕鬆鬆搞定。

  • WindowManager.LayoutParams.FLAG_BLUR_BEHIND 背景模糊;假如你的窗口設置了這個屬性,並且這個窗口可見,在這窗口之後的所有背景都會被模糊化,但我還沒有發現一個屬性是可以控制模糊程度的。

  • WindowManager.LayoutParams.FLAG_DIM_BEHIND 背景變暗;設置這個效果的窗口,在窗口可見的情況下,窗口後方的背景會相應的變暗,這個屬性需要配合參數dimAmount一起使用,dimAmount會在後文中介紹。

  • WindowManager.LayoutParams.FLAG_FULLSCREEN 設置全屏;這個屬性也許是大家接觸的最多的一個屬性,很多應用開發過程中會要求有些頁面需要動態設置Activity為全屏,而我們只需要獲得Activity的WindowManager.LayoutParams並設置WindowManager.LayoutParams.FLAG_FULLSCREEN屬性就行。

  • WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 設備常亮;設置這個屬性的窗口,在窗口可見的情況下,整個屏幕會處於常亮並且高亮度的狀態,並且不受待機時間的約束。

  • WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS 布局不受限制;設置這個屬性的窗口,將不再受設備顯示範圍邊界 的約束,通俗點講,就是窗口可以出設備之外,然後移除部分不可見。具體會在坐標參數中講到。

  • WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 不設置聚焦;關於焦點獲得我有必要說明一下,如果窗口獲得焦點的話,只要窗口處於可視化狀態,當前設備的物理按鍵點擊事件都會被這個窗口接收,但是如果不設置窗口的焦點的話,直接傳遞到之後窗口進行接收。這就導致一個問題,如果你的需求要求你寫的懸浮窗點擊返回鍵能夠關閉或是進行其他操作的話,你就必須讓你的窗口獲得焦點,並為當前View設置按鍵監聽事件。

  • WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 取消觸摸事件; 設置這個屬性的窗口將不再處理任何Touch事件,就算顯示的View設置了onTouch事件,那麼這個窗口就會是一個殭屍窗口。

  • WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 不知道怎麼去歸納,這個屬性還是比較有意思的,設置這個屬性的窗口,在窗口可見的情況下,就算窗口沒有設置屬性FLAG_NOT_FOCUSABLE,也就是在窗口獲得焦點的情況下,當觸摸事件是在窗口之外區域的時候,窗口不在攔截觸摸事件,而是將事件往下傳遞,也算是解決聚焦后的事件攔截問題吧。

  • WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER 顯示壁紙;官方文檔說明是在窗口之後顯示系統壁紙,但是我親測,似乎並沒有這個想效果,還是這個屬性需要配合其他的屬性設置一起使用,希望有設置成功的小夥伴能夠在評論區分享你的結果。

  • WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED 鎖屏顯示;關於這個屬性官方文檔給出的說明是在鎖屏的時候顯示的窗口,但是,實在慚愧,在下還是沒有能夠有一個實驗結果,不知道是需要給許可權呢還是需要同時進行其他設置。同樣,還是很希望有知道的小夥伴能夠在評論區向大家分享。

  • WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON 點亮屏幕;設置這個屬性的窗口,當窗口顯示的時候,如果設備處於待機狀態,會點亮設備。這個應該在很多鎖屏窗口中用的比較多,比如收到消息點亮屏幕。

  • WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 這個也不知道怎麼去歸納,也是一個比較有意思的屬性,之前我們說到FLAG_NOT_TOUCH_MODAL,在窗口獲得焦點的情況下,當觸摸事件是在窗口之外區域的時候,窗口不在攔截觸摸事件,而是將事件往下傳遞,而如果再設置這個屬性,窗口能在MotionEvent.ACTION_OUTSIDE中收穫窗口之外的點擊事件,遺憾的是不能進行屏蔽,也就是說事件依然會向下傳遞。

以上的也是最常用到的幾個flag屬性了吧,其他還有很多,也希望大家空閑之餘能夠去研究研究,歡迎再評論區補充。

type

type主要用於表示window的類型。我們可以通過WindowManager.LayoutParams的type變數對窗口類型直接進行設置。常用的窗口類型也就以下兩種:

  • WindowManager.LayoutParams.TYPE_APPLICATION_PANEL 我在之前文章中介紹過的PopupWindow,我也翻閱過PopupWindow的源碼,PopupWindow用的就是TYPE_APPLICATION_PANEL這個屬性類型。這種類型的窗口在顯示寄生於宿主窗口,並顯示與宿主窗口之上,因此這種類型的窗口會隨著宿主窗口的關閉而關閉,顯然不能滿足我們懸浮窗的要求。

  • WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 系統提示窗口,常見的比如內存不夠的警告、低電量警告。它總是出現在應用程序窗口之上,而這一點,正合我們做一個能夠顯示在任何應用之上的懸浮迷你音樂盒的要求。

screenBrightness、buttonBrightness

其中screenBrightness表示屏幕的亮度,而buttonBrightness表示一般按鍵和鍵盤按鍵的亮度。它們都擁有以下三個系統屬性:

  • WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF 最低屏幕亮度。

  • WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE 默認屏幕亮度。

  • WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL 最高屏幕亮度。

dimAmount

講flag屬性的時候有提到過,這個參數是要和WindowManager.LayoutParams.FLAG_DIM_BEHIND這個flag屬性一起使用,dimAmount的取值在0.0f~1.0f之間,取值越大背景的變暗程度越高,默認取值1.0f。

width、height

這裡的width、height其實和View中的width、height一樣的理解,就是控制窗口視圖的大小,可以具體取值,也可以使用系統屬性:

  • WindowManager.LayoutParams.WRAP_CONTENT 自適應大小

  • WindowManager.LayoutParams.MATCH_PARENT 填滿整個布局

gravity

窗口的對齊方式,一般在創建窗口的時候,都會設置gravity為左上角對齊,也就是Gravity.LEFT | Gravity.TOP,因為窗口的坐標設置,是基於gravity來進行計算的,設置gravity左上角,剛好是和系統的坐標相對應,方便計算。

x、y

x和y用於控制窗口的坐標位置,如果有設置gravity的話,x和y設置的就是在gravity這個基礎上的一個偏移量。不設置gravity的話,x和y就是一個絕對坐標。因此,將gravity設置為Gravity.LEFT | Gravity.TOP是最易於開發的。需要注意的一點是:設置y的時候常常需要考慮狀態欄的高度。正常情況下,就算x和y的坐標已經在設備之外,也會貼邊顯示。而如果設置屬性FLAG_LAYOUT_NO_LIMITS則相對於系統的坐標如果x和y超出設備,那麼超出部分將無法顯示。

windowAnimations

windowAnimations控制的是窗口出現和消失的動畫效果,設置的是要系統自帶的動畫效果(android.R.style之下的動畫效果),因為窗口管理器是不能訪問應用資源的。

format

format可以理解為最後窗口生成的點陣圖是什麼格式,默認背景是黑色的。一般我們都設置為PixelFormat.RGBA_8888,這樣我們的窗口就會有一個透明的背景。

alpha

這個不難理解,設置窗口的透明度。

其實WindowManager.LayoutParams的屬性有很多,全介紹一遍恐怕要講到天亮,而且還有一些我本人也沒有試過,要是還有什麼比較實用或是比較有趣的屬性,也歡迎小夥伴們在評論區留言!! 不勝感激!!!!

4

實現

其實實現這麼一個懸浮迷你音樂盒的功能也並不難,首先需要兩個布局一個就是像效果圖中展示的可以四處拖動的View的布局我們暫且稱它為floatView,它的實現很簡單其實就是我之前講過的用RotateDrawable實現網易雲音樂唱片機效果的迷你版。另一個就是點擊floatView后跳出的歌曲控制菜單的布局,我們可以叫它playerView

floatView.xml

playerView.xml

布局寫好,接著就是寫一個類來控制這兩個View之間的轉換以及一些屬性的設置,代碼確實有點,全貼出來的話,相信不少人都會看厭、看煩,我還是選擇其中比較有代表性的代碼,再進行一番說明,可能更有助於大家理解我寫這個Demo時思考的角度,老規矩在文章上方我會附上源碼下載地址,有興趣的小夥伴可以下載研究。

floatView的設置

首先,floatView的設置非常簡單。至於setContentView(mFloatView),是因為懸浮窗默認首先顯示的是小窗口,所以在設置MenuView的時候,就將窗口的布局設置為floatView。

playerView的設置

menuView的設置相對於floatView的設置複雜一些,大家可以看到,我為添加進來的MenuView包裹了一層BackgroundView,而這一點正是從PopupWindow的源碼中借鑒而來,BackgroundView是一個自定義內部類,繼承至RelativeLayout,並且重寫按鍵點擊監聽事件和觸摸事件,主要是用於PlayerView的返回鍵關閉功能和外部點擊關閉功能。

內部類BackgroundView

ContentView的初始化準備

由於我們要實現的懸浮迷你音樂盒,是需要在兩個視圖之間進行切換的,所以在每次視圖切換之前,我都會對先要載入的視圖進行一個準備,由代碼contentView.measure(View.MeasureSpec.UNSPECIFIED,View.MeasureSpec.UNSPECIFIED)可以看到,主動計算出當前View的寬高信息,還有就是getStatusBarHeight(getContext)計算設備狀態欄高度的方法,因為在窗口移動的時候,默認x、y指定的是窗口左上角的坐標,這樣就涉及到一個偏移量的問題,在排除偏移量問題后,我們的坐標就會精確指定窗口的正中心。而且在代碼中能夠明顯看出,我對contentView設置了一個onTouch觸摸事件,主要是實現窗口的滑動,具體實現在後面會有介紹。

WindowManager.LayoutParams初始化

WindowManager.LayoutParams控制著窗口顯示的策略,因此在窗口顯示之前,我們要完成對WindowManager.LayoutParams的配置。由上面的代碼看出,窗口類型是系統提示窗口,也就上篇文章中說到的可以顯示在任何視圖之上的那種窗口類型,窗口的寬高是自適應。對齊方式是左上角對齊。窗口的透明度為1.0,也就是不透明。雖然窗口並沒有設置屬性FLAG_NOT_FOCUSABLE,也就是說我們的窗口默認是設置焦點的,但是由於我設置FLAG_WATCH_OUTSIDE_TOUCH屬性的緣故,窗口外的區域,還是能夠自由接收點擊觸摸事件的。

視圖切換

從代碼中可以看出,視圖切換用到的上文中介紹到的視圖的移除和視圖的添加,是的,想要進行視圖新的切換,首先要移除原有的視圖,並將需要顯示的視圖添加進來。

窗口位置更新

在視圖切換中用到了窗口的移除和窗口的添加,而在位置更新中用到的是WindowManger三個方法中的第三個方法窗口更新,通過傳入我們需要重新設置WindowManager.LayoutParams中的x、y位置坐標,然後更新窗口,就能實現窗口位置的更新操作。

contentView的觸摸事件

floatView的觸摸監聽事件非常簡單,就是根據手指觸摸的坐標位置實時刷新窗口的坐標位置,當up坐標和dowm坐標的區域在預先設置好的合理範圍內,觸摸事件超過1秒鐘則視為長按事件,小於1秒鐘則視為點擊事件,移除floatView,添加playerView。注意區別MotionEvent的getX、getY和getRawX、getRawY方法,getX、getY點擊位置在視圖內的坐標,getRawX、getRawY則是點擊位置相對於整個屏幕的坐標

打開PlayerView

打開playerView后,我設置的的窗口大小是佔滿整個屏幕,這樣就能完全捕捉到BackgroundView中設置的觸摸事件,實現點擊窗口外部關閉的效果。oldX、oldY則用於記錄floatView移除前的坐標,方便在重新顯示的時候定位。

關閉PlayerView

關閉PlayerView的方法也很簡單,就是重新將試圖切換為floatView,設置窗口大小為自適應大小,並定位在之前視圖記錄的原有坐標。

到這裡炫酷的懸浮音樂盒的實現也就實現的差不多了,其實幾個在效果圖中能夠看到的樣子沒有介紹,例如:自定義SeekBar樣式的使用,懸浮窗移動時,手指離開屏幕,懸浮穿自動依附到屏幕兩側的過渡動畫,還有就是在3秒鐘左右懸浮窗沒有任何操作是變得透明的效果的實現。想要了解這些是怎麼實現的小夥伴,可以下載源碼進行研究哦!!



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦