你們好,我叫大聖;java
本人於2018年5月加入37手遊安卓團隊,曾經就任於愛拍等互聯網公司;android
目前是37手遊安卓團隊的國內負責人,主要負責相關業務開發和一些平常業務統籌等。markdown
關於我的,前段時間因爲業務太忙,因此一直沒有來得及思考而且沉澱點東西;同時組內一個個都在業務上能有本身的思考和總結,在這樣的氛圍下,不禁自主的驅使週末開始寫點東西,但願本身除了平常忙於業務,能夠沉澱點東西,加上本身的成長..ide
關於切入點,最近在作應⽤內懸浮球功能時,須要監聽屏幕旋轉事件來對懸浮球的位置進⾏調整,發現有些狀況下並不能收到系統回調,思考了⼀翻,作了⼀個屏幕旋轉的模擬監聽,基本上能達到⽬的。測試
懸浮球在停⽌拖拽後,須要貼邊到⼿機屏幕的左右兩側。ui
在豎屏狀態下,x座標爲0即爲左邊緣,x坐 標爲屏幕寬度即爲右邊緣。this
可是在橫屏狀態下,狀況就⽐較複雜了。如今⼤部分Android⼿機都是劉 海屏的設計,在全屏狀態下,懸浮球貼邊時不能收到劉海下⾯去,否則就點不到了。spa
因此此時須要算 出劉海的寬度,以此寬度做爲懸浮球左邊的起始位置,這樣懸浮球貼邊的時候就不會躲到劉海下⾯ 去。 以下圖所示 可是在屏幕旋轉以後,劉海到了右邊,左邊就不該該以劉海的寬度做爲懸浮球的起點了。 這樣的話就須要監聽屏幕的旋轉了,配合屏幕⽅向的⻆度,就能正確判斷。監聽屏幕的旋轉只須要重 寫Activity的onConfiguratuonChanged⽣命週期。設計
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
Log.i(TAG, "on configuration changed")
}
複製代碼
在AndroidManifest中配置3d
android:configChanges="orientation|screenSize"
複製代碼
此時發現了⼀個問題,當把Activity的screenOrientation設置成sensorLandscape時,即便屏幕旋轉 也收不到這個回調(這個和以前的理解有點不⼀樣)。因而將screenOrientation設置成sensor,屏 幕旋轉就能正常回調到這⾥,多試⼏次發現,只有在橫屏和豎屏之間切換時才能收到回調,若是直接 將橫屏倒過來,就是橫屏狀態不變,⽅向調轉,此時也不會收到回調。
既然onConfigurationChanged收不到回調,還有另外⼀個辦法,就是監聽屏幕⽅向度數,代碼以下
mOrientationEventListener = object : OrientationEventListener(this) {
override fun onOrientationChanged(orientation: Int) {
Log.i(TAG, "on orientation changed angle is $orientation")
if (orientation > 340 || orientation < 20) {
//0
} else if (orientation in 71..109) {
//90
} else if (orientation in 161..199) {
//180
} else if (orientation in 251..289) {
//270
}
}
}
複製代碼
經過度數來判斷劉海是在左邊仍是在右邊,即270度時在左邊,90度時在右邊。這種⽅式看起來能夠 解決問題,可是多旋轉⼏次就發現⼜有其餘問題。按照正常思惟,屏幕的顯示⽅嚮應該和這個度數⼀ 致纔對,即屏幕的顯示應該是⾃上⽽下的。可是下圖就不是這樣。
此時度數爲90,屏幕卻倒⽴着顯示的,並無旋轉成正⽴狀態,可是按照上⾯的代碼,會將90度斷定 爲正常90度正⽴顯示的狀態,此時去修改懸浮球的位置就是錯誤的。
那若是在收到onOrientationChanged這個回調時能判斷⼀下屏幕顯示的⽅向呢,就是在度數達到90 度範圍時,同時判斷屏幕的顯示⽅向,即兩個條件同時滿⾜才斷定成屏幕旋轉了。
⽤下⾯的代碼斷定屏幕顯示⽅向
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as
WindowManager
val rotation = windowManager.defaultDisplay?.rotation
//rotation爲常量0、一、二、3,分別表示屏幕的四個⽅向
複製代碼
經過這樣的判斷基本上能將屏幕旋轉事件監聽準確了,onOrientationChanged這個回調很靈敏,⼿ 機屏幕稍微動⼀下就會回調。那我但願模擬正常的屏幕旋轉事件來修改懸浮球的位置,總不能很頻繁 的刷新吧。這⾥作⼀下控制就好,所有代碼以下:
object ScreenOrientationHelper {
val ORIENTATION_TYPE_0 = 0
val ORIENTATION_TYPE_90 = 90
val ORIENTATION_TYPE_180 = 180
val ORIENTATION_TYPE_270 = 270
private var mOrientationEventListener: OrientationEventListener? = null
private var mScreenOrientationChangeListener:
ScreenOrientationChangeListener? = null
private var currentType = ORIENTATION_TYPE_0
fun init(context: Context, listener: ScreenOrientationChangeListener) {
mScreenOrientationChangeListener = listener
mOrientationEventListener = object :
OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
if (mScreenOrientationChangeListener == null) {
return
}
if (orientation > 340 || orientation < 20) {
//0
if (currentType == 0) {
return
}
if (getScreenRotation(context) == Surface.ROTATION_0) {
mScreenOrientationChangeListener!!.onChange(ORIENTATION_TYPE_0)
currentType = ORIENTATION_TYPE_0
}
} else if (orientation in 71..109) {
//90
if (currentType == 90) {
return
}
val angle = getScreenRotation(context)
if (angle == Surface.ROTATION_270) {
mScreenOrientationChangeListener!!.onChange(ORIENTATION_TYPE_90)
currentType = ORIENTATION_TYPE_90
}
} else if (orientation in 161..199) {
//180
if (currentType == 180) {
return
}
val angle = getScreenRotation(context)
if (angle == Surface.ROTATION_180) {
mScreenOrientationChangeListener!!.onChange(ORIENTATION_TYPE_180)
currentType = ORIENTATION_TYPE_180
}
} else if (orientation in 251..289) {
//270
if (currentType == 270) {
return
}
val angle = getScreenRotation(context)
if (angle == Surface.ROTATION_90) {
mScreenOrientationChangeListener!!.onChange(ORIENTATION_TYPE_270)
currentType = ORIENTATION_TYPE_270
}
}
}
}
register()
}
private fun getScreenRotation(context: Context): Int {
val windowManager =
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
return windowManager.defaultDisplay?.rotation ?: 0
}
fun register() {
if (mOrientationEventListener != null) {
mOrientationEventListener!!.enable()
}
}
fun unRegister() {
if (mOrientationEventListener != null) {
mOrientationEventListener!!.disable()
}
}
interface ScreenOrientationChangeListener {
/** * * @param orientation */
fun onChange(orientation: Int)
}
}
複製代碼
使⽤的話,直接這樣:
ScreenOrientationHelper.init(this, object :
ScreenOrientationHelper.ScreenOrientationChangeListener {
override fun onChange(orientation: Int) {
when(orientation) {
ScreenOrientationHelper.ORIENTATION_TYPE_0 -> {}
ScreenOrientationHelper.ORIENTATION_TYPE_90 -> {}
ScreenOrientationHelper.ORIENTATION_TYPE_180 -> {}
ScreenOrientationHelper.ORIENTATION_TYPE_270 -> {}
}
}
})
複製代碼
經過上⾯的代碼發現,在onOrientationChanged回調90度範圍內時,斷定屏幕顯示⽅向是和 Surface.ROTATION_270⽐較的,⽽270範圍內時是和Surface.ROTATION_90⽐較的。看得出來⻆度 是順時針遞增的,⽽屏幕⽅向是逆時針計算度數的。
在測試過程當中,上⾯的⽅案還存在另外⼀個問題,雖然onOrientationChanged這個回調很靈敏,但 是也有度數不變⽽屏幕⽅向旋轉的狀況發⽣,即保持屏幕⽅向不變,⽽是增長屏幕的坡度(將⼿機⼀ 邊貼在桌⾯,慢慢⽴起來),在坡度達到⼀定時,屏幕會發⽣旋轉,此時onOrientationChanged是 不會回調的,由於沒有變化。這樣就收不到屏幕旋轉的回調了,可是在實際⽤⼿機的場景中,這種情 況是⽐較少的,能夠親身試試看。
在平時開發中,要區分是哪一種狀態橫屏的場景⽐較少,不然我認爲Android會給出準確的回調的。 Android設備碎⽚化嚴重,除了劉海,在屏幕的下邊緣還有虛擬導航欄,在不一樣的系統設置下,這個 導航欄不顯示狀態會不⼀樣。那麼這時候在懸浮球貼邊這個需求中就不只僅要考慮劉海了,還得考慮 導航欄。更有甚者,在旋轉過程當中,虛擬導航欄會⼀直保持在⼀個⽅向,和劉海疊加。那麼要清楚的 算位置,第⼀步就是要監聽屏幕的旋轉了。