安卓-如何用正確的姿式監聽Android屏幕旋轉

做者

你們好,我叫大聖;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設備碎⽚化嚴重,除了劉海,在屏幕的下邊緣還有虛擬導航欄,在不一樣的系統設置下,這個 導航欄不顯示狀態會不⼀樣。那麼這時候在懸浮球貼邊這個需求中就不只僅要考慮劉海了,還得考慮 導航欄。更有甚者,在旋轉過程當中,虛擬導航欄會⼀直保持在⼀個⽅向,和劉海疊加。那麼要清楚的 算位置,第⼀步就是要監聽屏幕的旋轉了。

相關文章
相關標籤/搜索