android獲取焦點
A. Android TV 焦點原理源碼解析
相信很多剛接觸AndroidTV開發的開發者,都會被各種焦點問題給折磨的不行。不管是學技術還是學習其他知識,都要學習和理解其中原理,碰到問題我們才能得心應手。下面就來探一探Android的焦點分發的過程。
Android焦點事件的分發是從ViewRootImpl的processKeyEvent開始的,源碼如下:
源碼比較長,下面我就慢慢來講解一下具體的每一個細節。
dispatchKeyEvent方法返回true代表焦點事件被消費了。
ViewGroup的dispatchKeyEvent()方法的源碼如下:
(2)ViewGroup的dispatchKeyEvent執行流程
(3)下面再來瞧瞧view的dispatchKeyEvent方法的具體的執行過程
驚奇的發現執行了onKeyListener中的onKey方法,如果onKey方法返回true,那麼dispatchKeyEvent方法也會返回true
可以得出結論:如果想要修改ViewGroup焦點事件的分發,可以這么干:
注意:實際開發中,理論上所有焦點問題都可以通過給dispatchKeyEvent方法增加監聽來來攔截來控制。
(1)dispatchKeyEvent方法返回false後,先得到按鍵的方向direction值,這個值是一個int類型參數。這個direction值是後面來進行焦點查找的。
(2)接著會調用DecorView的findFocus()方法一層一層往下查找已經獲取焦點的子View。
ViewGroup的findFocus方法如下:
View的findFocus方法
說明:判斷view是否獲取焦點的isFocused()方法, (mPrivateFlags & PFLAG_FOCUSED) != 0 和view 的isFocused()方法是一致的。
其中isFocused()方法的作用是判斷view是否已經獲取焦點,如果viewGroup已經獲取到了焦點,那麼返回本身即可,否則通過mFocused的findFocus()方法來找焦點。mFocused其實就是ViewGroup中獲取焦點的子view,如果mView不是ViewGourp的話,findFocus其實就是判斷本身是否已經獲取焦點,如果已經獲取焦點了,返回本身。
(3)回到processKeyEvent方法中,如果findFocus方法返回的mFocused不為空,說明找到了當前獲取焦點的view(mFocused),接著focusSearch會把direction(遙控器按鍵按下的方向)作為參數,找到特定方向下一個將要獲取焦點的view,最後如果該view不為空,那麼就讓該view獲取焦點。
(4)focusSearch方法的具體實現。
focusSearch方法的源碼如下:
可以看出focusSearch其實是一層一層地網上調用父View的focusSearch方法,直到當前view是根布局(isRootNamespace()方法),通過注釋可以知道focusSearch最終會調用DecorView的focusSearch方法。而DecorView的focusSearch方法找到的焦點view是通過FocusFinder來找到的。
(5)FocusFinder是什麼?
它其實是一個實現 根據給定的按鍵方向,通過當前的獲取焦點的View,查找下一個獲取焦點的view這樣演算法的類。焦點沒有被攔截的情況下,Android框架焦點的查找最終都是通過FocusFinder類來實現的。
(6)FocusFinder是如何通過findNextFocus方法尋找焦點的。
下面就來看看FocusFinder類是如何通過findNextFocus來找焦點的。一層一層往下看,後面會執行findNextUserSpecifiedFocus()方法,這個方法會執行focused(即當前獲取焦點的View)的findUserSetNextFocus方法,如果該方法返回的View不為空,且isFocusable = true && isInTouchMode() = true的話,FocusFinder找到的焦點就是findNextUserSpecifiedFocus()返回的View。
(7)findNextFocus會優先根據XML里設置的下一個將獲取焦點的View ID值來尋找將要獲取焦點的View。
看看View的findUserSetNextFocus方法內部都幹了些什麼,OMG不就是通過我們xml布局裡設置的nextFocusLeft,nextFocusRight的viewId來找焦點嗎,如果按下Left鍵,那麼便會通過nextFocusLeft值里的View Id值去找下一個獲取焦點的View。
可以得出以下結論:
1. 如果一個View在XML布局中設置了focusable = true && isInTouchMode = true,那麼這個View會優先獲取焦點。
2. 通過設置nextFocusLeft,nextFocusRight,nextFocusUp,nextFocusDown值可以控制View的下一個焦點。
Android焦點的原理實現就這些。總結一下:
為了方便同志們學習,我這做了張導圖,方便大家理解~
B. Android 輸入法窗口焦點獲取流程(2) ,輸入法窗口和應用窗口綁定
基於Android9.x
Window和Session創建成功後,窗口的下一步流程為獲取焦點
我們看下焦點獲取過程,跟輸入法相關的流程
兩個Activity切換時,對應的狀態變化過程為:
以下是Activity窗口初次獲取焦點的流程
當兩個activity 切換時,失去焦點的窗口調用過程如下:
對應的,獲取焦點的額窗口的調用過程如下:
當B窗口的狀態切換到RESUMED時,當窗口的focus可能變化時,會調用updateFocusedWindowLocked
在該方法中,判斷,如果還沒有執行startInputInner方法,則執行startInputInner方法,否則,直接執行startInputOrWindowGainedFocus方法
主要流程:
1:設置controlFlags的flag為CONTROL_WINDOW_FIRST
2:檢查是否已經執行過startInputInner,沒有的話執行startInputInner-->startInputOrWindowGainedFocus;否則,直接執行startInputOrWindowGainedFocus
兩條路徑,攜帶的startInputReason參數不一樣
主要流程:
1:檢查要啟動和退出的ServedView是否為同一個,如果為同一個,則表示已經執行過startInputInner,則返回false,表示不再執行startInputInner
2:如果獲取焦點的是EditorText,會創建跟IMS通信的mServedInputConnectionWrapper對象
主要流程:
1:創建EditorInfo對象tba,這個參數對TextView布局才有意義,它的初始化是在mServedView的onCreateInputConnection完成實例化的
2:根據EditorInfo創建一個InputConnection對象,輸入法應用通過該對象,完成輸入內容到輸入框的傳遞;ACTIVITY獲取焦點場景,該對象
為null,因為沒有要輸入的對象
startInputOrWindowGainedFocus攜帶的參數
startInputReason = 1
表示,該流程是窗口獲取焦點過程
mClient
應用層創建的IInputMethodClient對象,為服務層提供應用層的各個回調方法
該方法跟應用進程首次創建時Session時,傳遞到IMMS的對象是同一個對象
windowGainingFocus:
應用層的ViewRootImpl$W對象
controlFlags |= CONTROL_START_INITIAL;
表示window窗口剛開始獲取焦點
softInputMode = SOFT_INPUT_ADJUST_RESIZE , 允許調整輸入法窗口,避免被其他窗口遮擋
tba , EditorInfo對象
servedContext
null
missingMethodFlags
ic等於null的情況下,為0
當應用層傳遞的W對象windowToken不為null的時候,則創建windowGainedFocus對象,返回給app
結果返回後,會對IMM的對象進行賦值
如此,進入一個窗口,獲取窗口焦點過程,窗口與輸入法相關的流程,就結束了。
下一篇:輸入法在輸入框彈出流程
Android輸入法(3),彈出流程
C. android tv常見問題(一)焦點查找規律
https://github.com/Geekholt/TvFocus
Recyclerview聚焦到最後一個Item,繼續按下鍵,焦點保持不變。
Recyclerview聚焦到最後一個Item,繼續按下鍵,焦點會跳出RecyclerView,跳到附近的View上。
那麼當Recyclerview滑動到最底部時,按下鍵,Android系統是如何找到下一個需要被聚焦的view的呢?我們把斷點打在ViewGroup的focusSearch方法上,可以看到從ViewRootImp的performFocusNavigation方法開始,依次調用了如下方法。
View並不會直接去找焦點,而是交給它的parent去找。
焦點會逐級的交給父ViewGroup的focusSearch方法去處理,直到最外層的布局,最後實際上是調用了FocusFinder的findNextFocus方法去尋找新的焦點。
但是這里要注意的是,RecyclerView和其他的ViewGroup不一樣,它自己重寫了focusSearch方法。所以在焦點查找委託到達到DecorView之前,會先執行RecyclerView的focusSearch方法。
那麼,RecyclerView和其他ViewGroup在尋找焦點方面有什麼不一樣呢? 為什麼RecyclerView要重寫ViewGroup的焦點查找機制呢 ?想知道這些問題的答案,那我們首先要知道ViewGroup的焦點查找機制。
ViewGroup的焦點查找機制的核心其實就是FocusFinder的findNextFocus方法。
主要步驟:
主要注意三點:
在addFocusables之後,找到指定方向上與當前focused距離最近的view。在進行查找之前,會統一坐標系。
總的來說就是根據當前focused的位置以及按鍵的方向,循環比較focusable集合中哪一個最適合,然後返回最合適的view,焦點查找就算完成了。
用於比較的方法。分別是將 當前聚焦的view , 當前遍歷到的focusable 和 目前為止最合適的focusable (i = 0時是優先順序最低的rect)進行比較。
判斷是否可以做為候選。可以看作是一個初步篩選的方法,但是到底哪個更好還需要看beamBeat方法,這個方法會將通過篩選的focusable和當前最合適的focusable進行比較,選出更合適的一個。
到這里為止ViewGroup的focusSearch方法基本上就講完了。那麼下面來看一下RecyclerView的focusSearch方法是如何實現焦點查找的。
前面講到了,該方法主要是為了解決 RecyclerView聚焦在按鍵方向上、當前屏幕區域內可見的最後一個item時,當前不可見的下一個item將無法獲得焦點。
這個方法是由LayoutManager來實現的,這就是RecyclerView的針對上面提到的情況的焦點查找方法。這里主要分析LinearLayoutManager中實現的該方法,如果在使用其他的LayoutManager時出現RecyclelerView焦點不符合預期的話,可以查看對於LayoutManager下的onFocusSearchFailed方法。
主要關注方法,通過這個方法的命名我們大致就可以看出來這個方法的作用了。這個方法主要會 根據當前RecyclerVIew的正逆序以及按鍵方向,找出最近一個部分或完全不可見的View 。
這個方法是RecyclerView內部的方法,和FocusFinder中的isCandidate方法的邏輯可以說幾乎是一摸一樣的。
到此為止ViewGroup的focusSearch和RecyclerVIew的focusSearch都分析完了。我們已經知道RecyclerView滑動到最底部的時候,發生了哪些焦點行為,那麼解決起來就比較簡單了。
結合KeyEvent事件的流轉,處理焦點的時機,按照優先順序(順序)依次是:
以上任一處都可以指定焦點,一旦消費了就不再往下走。
比如前面說到了RecyclerView就是通過重寫focusSearch方法對邊界上部分可見或不可見的view的焦點查找進行了特殊處理。
重寫RecyclerView的focusSearch方法
D. Android 獲取焦點
你好,我舉例說明:比如有個輸入框,當你點擊輸入框時,這時它的游標在閃爍,可以輸入文字,那麼就可以說這個輸入框獲得焦點了。獲取焦點的控制項就是當前可以執行操作的控制項。
使用view.requestFocus()方法可以手動獲取焦點。
以上,希望對你有幫助。
E. Android EditText獲取焦點並彈出軟鍵盤
1、首先,在xml文件中通過讓edittext獲取焦點
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="17dp"
android:textColor="#333333"
android:textColorHint="#999999"
android:hint="請輸入課件描述..."
android:gravity="top|left"
android:background="@null"
>
<requestFocus/>
</EditText>
2、在清單文件中給activity添加android:windowSoftInputMode=」stateVisible」屬性,這樣一進入這個頁面的時候游標就自動顯示,軟鍵盤也顯示出來
<activity
android:name=".wonderfulmoment."
android:windowSoftInputMode="stateVisible"></activity>
F. Android移動應用中的焦點分析
簡單一點理解,在移動應用中,焦點就是當前正在處理事件的位置。在手機應用中,最有可能用到焦點的就是EditText,如果同一個界面中有多個EditText,通常情況下同一時間只有一個能夠輸入內容,此時,這個EditText就獲取了焦點。
在Android中,對焦點的設置分為兩種情況,TouchMode和非TouchMode。現在的手機基本都是觸摸屏,我們用手指觸摸屏幕來操作Android應用時,處於TouchMode。除了TouchMode之外,還有非TouchMode,利用外接設備來操作應用。比如鍵盤。使用Genymotion模擬器的時候,一個界面上有多個控制項時,可以用電腦tab鍵來進行移動,被選中的控制項會高亮顯示,這時候就是非TouchMode,被選中的控制項獲得了焦點。
在手機應用中,用到焦點的時候並不多,但是TV應用中,需要用遙控器來操作選中控制項,這時候就需要對焦點進行處理了。關於焦點,常用方法如下:
在View類中, isFocusable() 和 isFocusableInTouchMode() 獲取到的結果都是false,也就是說,直接繼承自View的控制項是不能獲取焦點的。我們常用控制項中對這兩個方法進行了改寫,比如EditText,這兩個方法都是true,而Button則只有 isFocusable() 返回true。這也就是為什麼我們用tab鍵選取Button的時候能夠高亮顯示,而滑鼠點擊(模擬觸控)的時候不能高亮顯示的原因了。如果想在點擊的時候也能高亮顯示Button,需要手動設置 setFocusableInTouchMode(true) ,就可以了。
如果想對控制項的焦點狀態進行監聽,需要設置 setOnFocusChangeListener() ,只要控制項的焦點狀態發生變化(獲得或者失去焦點),都會調用 onFocusChange 方法
關於焦點的移動,默認的演算法會尋找指定方向上最近的可以獲取焦點的元素(非TouchMode)。另外在創建控制項的時候,也可以指定尋找焦點的方向,設置nextFocusDown、nextFocusLeft、nextFocusRight 和 nextFocusUp的值為指定元素就可以了。看以下例子:
這里指定了上面的button向上尋找焦點時,下一個元素是id為bottom的元素,也就是說,上面的Button在獲取了焦點之後,繼續按向上鍵,系統會將焦點移動到id為bottom的元素上,而不是繼續向上。
在開發手機應用的過程中,對焦點的處理並不多,它與事件是兩個不同的體系,通常情況下焦點和事件是相互獨立並不沖突。但是在Button的點擊事件中會有一點問題。如果我們隊一個button設置了 setFocusableInTouchMode(true) ,使他可以獲取焦點,那麼我們點擊這個button的時候,第一次點擊並不會執行 onClick() 方法,而是執行 onFocusChange() 。第二次點擊的時候才會執行 onClick() 方法。看起來好像 onFocusChange() 消耗了點擊事件,實際上並不是的。
這個問題我們看一下源碼就清楚了:
onClick() 方法是在onTouchEvent的ACTION_UP里調用的,看一下View的onTouchEvent方法:
可以看到,只有當focusTaken為false的時候才會執行onClick,focusTaken的值默認是false的,但是在 isFocusable() && isFocusableInTouchMode() && !isFocused() 為true的時候,會去 requestFocus 獲取焦點,並將值賦給focusTaken。
關鍵在於 isFocused() ,如果當前Button沒有獲取焦點, isFocused() 返回false, !isFocused() 值為ture,Button就會去獲取焦點,從而導致 focusTaken 為true, onClick 方法就不會執行了,只有Button已經獲取了焦點的時候才會執行onClick方法。
G. Android TV按鍵焦點原理淺談
原文鏈接: Android TV按鍵焦點原理淺談
本篇主要閱讀 Android 源碼講解 TV 的按鍵事件分發原理和焦點查找原理,源碼基於 Android9.0 ,首先思考幾個問題:
帶著這些問題,我們一起來擼 Android 源碼吧!了解了系統是如何處理的有便於我們解決 TV 上一些按鍵和焦點的問題。
首先我們看下按鍵事件的入口 ViewRootImpl 類中的 ViewPostImeInputStage 內部類:
可以看到注釋1,2,3,4分別判斷不同事件執行不同方法,本篇主要討論的TV焦點事件,主要看下 processKeyEvent 方法:
可以看到在該方法中執行了 mView.dispatchKeyEvent 方法,這里的 View 其實是 DecorView ,接著看下該方法:
上面首先判斷了如果是第一次按下則處理panel的快捷鍵,如果處理了則不往下走,否則繼續判斷當窗口未銷毀且回調非空則回調處理,如果處理了則不往下走,否則讓 PhoneWindow 對應的 onKeyDown , onKeyUp 方法來處理。
接下來我們按照這個派發順序依次來看看相關方法的實現,這里先看看 Activity 的 dispatchKeyEvent 實現:
我們看第1點 superDispatchKeyEvent 方法,可以看到該方法為一個抽象方法,而它的實現是實現它的子類 PhoneWindow :
該方法又回調用 DecorView 中的 superDispatchKeyEvent 方法:
此時,再來看下 ViewGroup 的 dispatchKeyEvent 方法:
接著看下 View 的 dispatchKeyEvent 方法:
該方法主要是判斷如果有給 View 設置 OnKeyListener 事件且 View 為可用狀態,則優先處理監聽事件,其次調用 KeyEvent 的 dispatch 方法,接下來我們看下該方法:
該方法主要處理了按下、彈起事件,其中按下如果 mRepeatCount 重復次數大於0判斷為長按,則執行長按事件。
我們繼續看下 View 的 onKeyDown 方法:
按下事件判斷了如果為確認相關的按鍵才到下一步處理,判斷點擊或長按條件滿足,執行按下 View 正中心坐標,然後執行 checkForLongClick 檢查長按方法,看下該方法如下:
我們經常會遇到電視按遙控器時長按會執行一次 onKeyDown 、 onKeyUp ,之後才是一直 onKeyDown ,松開後才執行 onKeyUp ,原因就在於這個檢查長按方法是延遲的。 delayOffset 傳進來的是0,所以延遲時間為 ViewConfiguration.getLongPressTimeout() ,即該類中定義的 DEFAULT_LONG_PRESS_TIMEOUT 常量。
同樣的如果是觸摸屏,可以看下 View 類中的 onTouchEvent 方法在按下操作的時候會開啟 CheckForTap 線程檢查是否是長按,該線程同樣是延遲的,時間為 ViewConfiguration.getTapTimeout() ,即該類中的 TAP_TIMEOUT 常量,知道了這個你就知道如果寫腳本或插件模擬長按應該間隔多長時間了,是不是一下你的模擬長按插件速度又可以更加准確快速的實現了。
不同版本系統定義的延遲時間有可能不一樣,比如Google API 28 的 DEFAULT_LONG_PRESS_TIMEOUT 是500, TAP_TIMEOUT 是100,而 API 30 的 DEFAULT_LONG_PRESS_TIMEOUT 是400, TAP_TIMEOUT 也是100。
接下來再看下 Activity 的 onKeyDown :
回到 Decorview 中的 dispatchKeyEvent 方法看看 PhoneWindow 的 onKeyDown 方法:
onKeyUp 方法也可以自己再看下,以上就是淺談按鍵事件的分發流程了。
總結:
上面講解了按鍵事件分發流程,當上面分發完所有都沒消費的時候,就會繼續走 ViewRootImpl 的焦點導航流程,接下來看下 performFocusNavigation 方法:
首先我們看 mView.findFocus() ,該方法實際是調用了 ViewGroup 的 findFocus 方法:
該方法很簡單,就是向下遞歸查找在當前頁面已經獲取焦點的 View ,繼續看 focused.focusSearch(direction) 調用了 View 的 focusSearch 方法:
該方法向上遞歸查找,調用 ViewGroup 的 focusSearch 方法:
如果是根命名空間,則調用 FocusFinder 的 findNextFocus 方法查找焦點,否則繼續往上查找。繼續看 FocusFinder 的 findNextFocus 方法:
可以看到該方法首先查找用戶指定的下一個獲取焦點的 view ,如果找到了直接返回該 view ,如果沒找到繼續下面先添加 effectiveRoot 下的所有 view 到 focusables 集合中去,然後調用 findNextFocus 方法查找系統可獲取下一個焦點的最近 view 。
我們先看下 findNextUserSpecifiedFocus 方法的實現:
通過用戶指定焦點方式不是本篇的重點,這里就不貼出內部細節源碼了。該方法實際就是調用 View 的 findUserSetNextFocus 方法來查找用戶設置的下一個可獲取焦點的 view ,然後在 while 循環中判斷如果找到的是可以獲取焦點並且可見的並且不是 InTouchNode 模式,則返回該焦點,否則繼續循環查找直到找了一個循環沒有找到可以獲取焦點的或者 userSetNextFocus 為 null 跳出循環返回 null 。
再來看下系統就近原則查找的 findNextFocus 方法:
該方法主要通過 在相對方向上找下一個焦點,該方法內部邏輯比較簡單,這里就不貼出來了,進去看下就知道其實就是先給 focusables 排序,然後從中找到 focused 在其中的後一個或前一個 view ,如果沒找到並且 focusables 不為空則返回 focusables 的第一個。
接下來我們重點看下 方法:
再看下 isBetterCandidate 方法,該方法很關鍵,內部包含一系列邏輯如何成為最佳候選者:
該方法英文注釋很直觀,就不中文翻譯了,首先看下成為候選人的 isCandidate 方法:
該方法判斷了目標Rect如果在源Rect的方向一側且不在內部的話,則為候選者,如第一個 destRect 左側應在 srcRect 左側左邊, destRect 右側應在 srcRect 右側左邊,其他方向同理。
接下來看下 beamBeats 方法:
H. Android高德地圖獲取不到輸入框焦點
1、創建工程,並在工程中選擇AddExternelJARs,選定MapApi.jar,點擊OK,這樣就可以將高德地圖AndroidAPI庫文件引入。
2、在工程中將引入的庫文件MapApi.jar選中,點擊OK,這樣就可以在程序中使用Android高德地圖獲取輸入框焦點。