DecorView →PhoneWindow →Activity→ViewGroup→view
專注于為中小企業提供成都做網站、成都網站設計服務,電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業淳安免費做網站提供優質的服務。我們立足成都,凝聚了一批互聯網行業人才,有力地推動了近千家企業的穩健成長,幫助中小企業通過網站建設實現規模擴充和轉變。
下面我們根據按鍵事件的分發流程,抽絲剝繭,逐一分析。
private int processKeyEvent(QueuedInputEvent q)
1、DecorView.java
2、Activity.java
3、ViewGroup.java
4、View.java
通過該方法,接收器receiver的onKeyDown、onKeyUp、onKeyLongPress、onKeyMultiple等方法將被回調。
在上述按鍵事件的入口中提到的ViewRootImpl中
如果mView.dispatchKeyEvent(event)返回true,則結束事件分發;
如果返回false,則調用如下方法
繼續執行后續的焦點導航流程。
焦點導航的總體流程就是:
1、View focused = mView.findFocus();//從視圖樹的頂層,即DecorView一層一層的遞歸查找當前獲得焦點的view
2、View v = focused.focusSearch(direction);根據導航的方向查找下一個可獲取焦點的view
3、v.requestFocus(direction, mTempRect)請求獲取焦點
4、v.requestFocus(direction,mTempRect)內部,調用mParent.requestChildFocus(this, focused)逐層遞歸向上級通知
ViewRootImpl.java
mView即DecorView,從DecorView開始,一層一層的向下遞歸查找當前獲得焦點的view
找到了當前獲得焦點的focused,調用該焦點view的focusSearch(direction)方法查找direction方向上下一個將要獲取焦點的view。
focused.focusSearch(direction)實際上會調用mParent.focusSearch(this, direction)方法,層層遞歸,直到調用到DecorView的focusSearch(this, direction)方法。
而DecorView繼承ViewGroup,實際上最終會調用到FocusFinder.getInstance().findNextFocus(this, focused, direction),this 就是DecorView對象。
最終會調用到DecorView父類ViewGroup中的FocusFinder.getInstance().findNextFocus(this, focused, direction);
ViewGroup.java
FocusFinder.java
搜索到下一個獲取焦點的view后,調用該view.requestFocus(direction, mTempRect)方法
注意:調用requestFocus(direction, mTempRect)需要區分調用者。
如果是ViewGroup,則會更加焦點獲取策略,實現父View和子View之間獲取焦點的優先級。
如下是ViewGroup.java 和View.java 中requestFocus方法是實現:
ViewGroup.java
View.java
View獲取到焦點后,會調用mParent.requestChildFocus(this, focused)逐層遞歸向上級通知
ViewGroup.java
相信很多剛接觸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焦點的原理實現就這些。總結一下:
為了方便同志們學習,我這做了張導圖,方便大家理解~
Android焦點分發主要涉及的方法就是
findFocus:View都有,發現焦點
requestFocus:View都有,請求獲取焦點
requestChildFocus:只有ViewGroup有
focusSearch:View都有,焦點查詢
這幾個方法下面將分別介紹一下這幾個方法。
1、焦點的獲取,默認進入頁面
系統自動請求焦點也是從最頂層的容器控件開始向內請求的,我們進入一個界面系統會自動請求焦點,尋找焦點,最后使用一個控件獲得焦點。
2、焦點的切換
切換焦點的時候,也是要從最頂層的父容器尋找到焦點(findFocus),然后從獲得到焦點的控件開始從內向外調用focusSearch尋找下一個焦點控件。
總結:Android的焦點分發跟事件分發類似,有一個從內向外,從外向內的過程,焦點分發中,尋找當前的焦點控件(findFocus)和焦點的請求(requestChildFocus)都是從外向內的,就是從頂層的父容向內層的子容器尋找和請求,但是搜尋下一個焦點(focusSearch)是從當前焦點控件開始的,就是從內向外尋找,到這里我們知道了Android焦點分發的一個基本流程。
參考 Android TV 按鍵焦點事件分發流程詳解
基于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),彈出流程
焦點大概就是你當前要操作的地方.
例如,你有三行輸入框,
焦點在第一行輸入框時,你打字就會輸入進第1個輸入框,
焦點在哪個上,字就會輸入到哪里.
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方法。
主要關注findPartiallyOrCompletelyInvisibleChildClosestToEnd方法,通過這個方法的命名我們大致就可以看出來這個方法的作用了。這個方法主要會 根據當前RecyclerVIew的正逆序以及按鍵方向,找出最近一個部分或完全不可見的View 。
這個方法是RecyclerView內部的方法,和FocusFinder中的isCandidate方法的邏輯可以說幾乎是一摸一樣的。
到此為止ViewGroup的focusSearch和RecyclerVIew的focusSearch都分析完了。我們已經知道RecyclerView滑動到最底部的時候,發生了哪些焦點行為,那么解決起來就比較簡單了。
結合KeyEvent事件的流轉,處理焦點的時機,按照優先級(順序)依次是:
以上任一處都可以指定焦點,一旦消費了就不再往下走。
比如前面說到了RecyclerView就是通過重寫focusSearch方法對邊界上部分可見或不可見的view的焦點查找進行了特殊處理。
重寫RecyclerView的focusSearch方法