TV屏使用遙控器控制,通過焦點操作界面,就跟電視投屏類似 一共兩個核心,焦點的處理,按鍵的監聽處理 按鍵原生提供了onKeyDown 來監聽,通過不同的 keyCode 區分不同的按鍵 一般如果沒有遙控器,可以通過電腦鍵盤測試,使用投屏軟體投屏後,對鍵盤按鍵效果跟遙控器類似 有時候沒有實體按鍵(比如 ...
TV屏使用遙控器控制,通過焦點操作界面,就跟電視投屏類似
一共兩個核心,焦點的處理,按鍵的監聽處理
按鍵原生提供了onKeyDown 來監聽,通過不同的 keyCode 區分不同的按鍵
一般如果沒有遙控器,可以通過電腦鍵盤測試,使用投屏軟體投屏後,對鍵盤按鍵效果跟遙控器類似
有時候沒有實體按鍵(比如電腦沒有返回鍵等),可以直接使用 adb 命令控制
adb shell input keyevent keyCode
至於長按事件,通過監聽的 KeyEvent 參數中 repeatCount 判斷,這裡最好等於某個數字時觸發,防止多次重覆觸發
主動獲取焦點使用方法 requestFocus(),但是可能會失敗,所以需要註意等待UI刷新完後在調用
並且在需要獲取焦點的view中設置屬性,否則沒法落焦
android:focusable="true"
android:focusableInTouchMode="true"
如果實在沒法控制刷新後的時機,那就只能延遲(postDelayed)獲取了,這個萬能鑰匙,但是儘量少用,影響效率,減低體驗
對於焦點,上面的只是基本操作,實際開發中吭比較多,比較如果界面複雜,焦點是很不好控制的,加上列表各種刷新,跨界面恢復焦點等,懂得都懂
所以為了更好的定位問題,需要藉助一些系統監聽來獲取焦點的狀態,才能知道問題在哪
比如可以重寫 requestChildFocus 方法,每次獲取焦點時列印信息,看看是否被其它 view 搶占焦點
override fun requestChildFocus(child: View?, focused: View?) { child?.let { val position = getChildViewHolder(child).absoluteAdapterPosition Logger.d("requestChildFocus $position ") } }
或者使用全局監聽 decorView.viewTreeObserver.addOnGlobalFocusChangeListener ,判斷哪些 view 獲取了焦點
針對獲取焦點無效怎麼處理?
估計很多時候會發現,調用了 requestFocus 方法沒反應,這是因為沒有對上個 view 的焦點進行 clear
你需要在監聽中,把獲取焦點的 view 賦值給你定義的變數 lastFocusView,然後每次調用 requestFocus 前先調用 lastFocusView.clearFocus()
當 tab 或者 列表 切換界面,這個時候操作遙控器子頁面自動搶占了焦點,怎麼處理?
可以通過對 onKeyDown 事件的攔截(return true),這樣子頁面是無法響應按鍵事件的,也就無法獲取焦點
針對一些個別 view 可以設置屏蔽焦點的屬性
android:focusable="false"
android:focusableInTouchMode="false"
焦點邊框樣式統一處理
可以通過全局監聽 addOnGlobalFocusChangeListener ,對 view 進行統一繪製邊框,或者一些邏輯控制等
window.decorView.viewTreeObserver .addOnGlobalFocusChangeListener { oldFocus, newFocus -> (newFocus ?: window.decorView.findFocus())?.let { mainUpView.setFocusView(newFocus, oldFocus, 1.0f) } }
彈框無法獲取焦點怎麼處理?
一般 dialog 等只要設置了 focus 屬性,然後在初始化調用 requestFocus,是沒問題的,但是不排除一些個別情況
比如 PopupWindow,因為 window 彈出後,activity 的 onKeyDown 會無法響應,所以需要單獨監聽,前提是獲取到焦點
如果碰到焦點不好處理,或者落焦後繪製邊框等不方便,這裡建議手動控制,因為落焦繪製是統一在全局監聽里處理的,window 上需要額外監聽,沒有對這塊邏輯封裝好,就表示需要把整套邏輯搬到 window 上處理,這顯然很冗餘
所以何不直接監聽 onKeyDown ,然後通過代碼手動繪製邊框焦點
contentView.apply { requestFocus() binding.flMove.setOnKeyListener { _, keyCode, _ -> Logger.d("setOnKeyListener keyCode $keyCode") when (keyCode) { KeyCode.KEYCODE_DPAD_LEFT -> { switchMenu(1) } KeyCode.KEYCODE_DPAD_RIGHT -> { switchMenu(0) } KeyCode.KEYCODE_BACK -> { dismiss() } } false } }
如果不太喜歡這個方案,還有另外一個方案
可以在xml中添加佈局,去仿造彈框,這樣焦點就可以統一處理,不過需要對這個佈局進行一些顯示隱藏的操作