一點見解: 焦點那點事(二)

上一篇文章, 一點見解: 焦點那點事(一), 了解了焦點相關(guān)的一些基本知識, 提到焦點切換的關(guān)鍵方法ViewParent#focusSearch, 本文接著看, 焦點是從什么時候產(chǎn)生的, 又是如何在控件間切換的, 當控件被移除或者新增進布局時焦點又會發(fā)生什么變化.

焦點產(chǎn)生

頁面創(chuàng)建出來后, 什么時候開始分發(fā)焦點?
關(guān)于頁面創(chuàng)建流程的和繪制過程的文章有很多, 這里不再累述, 通過這些文章, 我們可以知道頁面控件的繪制入口是ViewRootImpl#performTraversals方法.

在這個方法中, 如果是第一次執(zhí)行這個方法, 同時, ViewRoot相關(guān)聯(lián)的DecorView沒有焦點控件, 那么就會調(diào)用DecorView#requestFocus, 實際上也就是調(diào)用了ViewGroup#requestFocus, 上一篇文章一點見解: 焦點那點事(一)介紹過, 在這個方法里, 會遍歷子控件, 執(zhí)行View#requestFocus直到某個控件持有焦點.

疑問: home鍵退出頁面, 然后返回時, 如果當前頁面沒有焦點, 還會走一次requestFocus, 這種情況是哪里觸發(fā)的?

焦點切換

雖然在觸摸模式也能產(chǎn)生焦點, 但是一般不會用到, 因此這里著重分析通過鍵盤操作來切換焦點的情況.

起點

既然是通過鍵盤切換焦點, 因此從鍵盤事件開始入手.
關(guān)于輸入事件的處理流程已經(jīng)有很多文章了, 這個也不是本文關(guān)注的重點, 因此不再累述, 可以參考原來Android觸控機制竟是這樣的粘秆?.

概括起來就是

  1. ViewRootImpl通過一個Receiver接收硬件發(fā)送過來的事件(包括觸摸事件和鍵盤事件)
  2. 然后ViewRootImpl會把這些事件放在隊列中
  3. 然后再按順序取出這些事件通過InputStage相關(guān)類分發(fā)出去, 最后會執(zhí)行InputStage#onProcess()方法
  4. 其中在ViewPostImeInputStage類中, 如果輸入的事件是鍵盤事件, 那么就會調(diào)用ViewPostImeInputStage#processKeyEvent()方法

processKeyEvent()

在這個方法里, 會先把事件傳遞給ViewGroup#dispatchKeyEvent()方法, 如果這個方法沒有消費掉這個事件, 并且這個事件是方向事件按下事件, 例如KeyEvent.KEYCODE_DPAD_LEFT等, 那么就會觸發(fā)焦點切換, 也就是focusSearch方法.

ViewGroup#dispatchKeyEvent()

首先看這個方法, 因為在ViewRootImpl中持有的是DecorView, 它本質(zhì)上是一個FrameLayout, 因此分發(fā)鍵盤事件時實際調(diào)用的會是ViewGroup#dispatchKeyEvent().

在這個方法里

  1. 如果這個ViewGroup持有焦點, 那么就會直接調(diào)用View#dispatchKeyEvent
  2. 如果是它的子控件持有焦點, 那么就會調(diào)用子控件View#dispatchKeyEvent

View#dispatchKeyEvent里面

  1. 詢問OnKeyListener是否消費這個事件
  2. 消費確認相關(guān)的按鍵事件, 例如KeyEvent.KEYCODE_DPAD_CENTER

由上可以知道, 一般情況下, ViewGroup#dispatchKeyEvent()只會消費確認事件, 方向事件是會繼續(xù)執(zhí)行下一步的.

觸發(fā)焦點切換

方向事件按下事件表明, 在按下的時候就會觸發(fā)焦點切換了, 這解釋了為什么長按方向鍵會一直切換焦點.

焦點切換時

  1. 如果當前已經(jīng)存在焦點, 那么就調(diào)用當前焦點控件的View#focusSearch(int), 這個方法又會馬上調(diào)用ViewParent#focusSearch(View, int)方法, 注意區(qū)分這兩個方法, 雖然同名, 但不是同一個方法.
  2. 如果不存在焦點, 那么就會調(diào)用ViewRootImpl#focusSearch, 這個方法直接調(diào)用了FocusFinder#findNextFocus來查找合適的控件
  3. 當找到具體的控件后, 就會調(diào)用該控件的requestFocus方法

這個過程說明

  1. 按下方向鍵時, 如果沒有控件持有焦點, 那么我們不能控制候選控件的選擇
  2. 按下方向鍵時, 如果有控件持有焦點, 那么可以通過重寫這個控件的父控件ViewParent#focusSearch來控制候選控件的選擇
  3. 無論是如何得到候選控件, 這個控件是通過requestFocus來獲取焦點的, 后續(xù)流程參考一點見解: 焦點那點事(一)

焦點控件失去焦點資格

上一篇文章提到控件要獲取焦點必須符合

  1. View#isFocusable返回true, 如果在觸摸模式, 則View#isFocusableInTouchMode也要返回true
  2. 控件必須可見
  3. 控件相關(guān)的父控件, 包括祖父控件等, ViewGroup#getDescendantFocusability()不能為ViewGroup#FOCUS_BLOCK_DESCENDANTS

unFocusable和unVisibility

改變控件的這兩個狀態(tài), 最終會調(diào)用View#setFlags方法, 在該方法中, 如果焦點控件是變?yōu)榱瞬豢梢娀蛘卟豢色@取焦點, 那么就會調(diào)用View#clearFocus來清除焦點, 跟手動清除焦點流程一樣.

FOCUS_BLOCK_DESCENDANTS

如果父控件突然變?yōu)榱?code>FOCUS_BLOCK_DESCENDANTS, 不會影響當前焦點控件的狀態(tài), 只會影響下一次焦點分發(fā)/查找的流程.

焦點控件被移除

控件被移除, 最終都會調(diào)用ViewGroup#removeViewInternal方法, 在這個方法中, 首先會調(diào)用View#unFocus來清除焦點, 具體參考上一篇文章的介紹, 因為View#unFocus方法不會調(diào)用ViewParent#clearChildFocus, 因此ViewGroup會主動調(diào)用自己的clearChildFocus方法, 緊接著會調(diào)用View#rootViewRequestFocus方法, 在這個方法中會調(diào)用getRootView()#requestFocus, 然后就會遍歷一次控件樹來重新分發(fā)焦點.

控件獲得焦點資格

和失去焦點資格類似, 最終會調(diào)用View#setFlags方法, 然后調(diào)用ViewParent#focusableViewAvailable方法, 默認實現(xiàn)中會一直向上級父控件傳遞, 最終就會調(diào)用ViewRootImpl#focusableViewAvailable方法, 在這個方法中, 兩種情況下這個新控件可以獲得焦點

  1. 如果當前沒有焦點控件, 那么就會調(diào)用這個新獲得焦點資格的控件的requestFocus方法
  2. 如果當前有焦點控件, 同時新的這個控件是當前焦點控件的子控件, 而這個焦點控件的焦點分發(fā)策略為FOCUS_AFTER_DESCENDANTS, 那么還是會調(diào)用requestFocus來把焦點給這個新的控件

新增控件(有焦點資格)

通過addView方式添加控件, 都會調(diào)用ViewGroup#addViewInner方法, 在這個方法中, 如果新增的控件的hasFocus方法為true, 那么就會調(diào)用父控件的ViewParent#requestChildFocus, 參考上一篇文章可以知道, 在這個方法里會把現(xiàn)有的焦點控件的焦點清除掉. 也就是說, 新增的控件如果持有焦點, 那么就會替換現(xiàn)有的控件成為焦點控件.

如果新增的控件沒有持有焦點, 即使它有焦點資格, 也不會有任何焦點相關(guān)的回調(diào)

注意: 新增(addView)控件時, 無論這個控件會不會獲得焦點, ViewParent#focusableViewAvailable都不會被調(diào)用.

總結(jié)

  1. 頁面第一次刷新布局時會通過根控件的requestFocus來尋找第一個焦點控件
  2. 當鍵盤輸入方向事件時, 頁面會通過ViewParent#focusSearch來尋找下一個焦點控件, 并調(diào)用它的requestFocus方法
  3. 焦點控件的可見性或者focusable屬性發(fā)生變化, 導致該控件不能繼續(xù)持有焦點, 那么就會清除焦點, 并重新通過根控件的requestFocus來分發(fā)焦點
  4. 當控件從不能持有焦點變?yōu)榭梢猿钟薪裹c, 會觸發(fā)ViewParent#focusableViewAvailable, 并在兩種情況下會替換舊焦點控件.
  5. 當焦點控件從布局中移除, 會重新通過根控件的requestFocus來分發(fā)焦點
  6. 當可以獲取焦點的控件新增進布局時, 不會調(diào)用ViewParent#focusableViewAvailable, 如果該控件被加入布局前已經(jīng)持有焦點, 那么就會替換舊焦點控件, 否則就不會觸發(fā)焦點相關(guān)方法.

RecyclerView是一個非常常用的控件, 其中列表中的子控件會復用/移除/新增等, 因此焦點的處理也比較特殊, 下一篇會詳細分析RecyclerView的焦點處理邏輯, 以此得到移除焦點控件后重新分發(fā)焦點的解決方案.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末如迟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子攻走,更是在濱河造成了極大的恐慌殷勘,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陋气,死亡現(xiàn)場離奇詭異,居然都是意外死亡引润,警方通過查閱死者的電腦和手機巩趁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人议慰,你說我怎么就攤上這事蠢古。” “怎么了别凹?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵草讶,是天一觀的道長。 經(jīng)常有香客問我炉菲,道長堕战,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任拍霜,我火速辦了婚禮嘱丢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祠饺。我一直安慰自己越驻,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布道偷。 她就那樣靜靜地躺著缀旁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勺鸦。 梳的紋絲不亂的頭發(fā)上并巍,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音祝旷,去河邊找鬼履澳。 笑死,一個胖子當著我的面吹牛怀跛,可吹牛的內(nèi)容都是我干的距贷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼吻谋,長吁一口氣:“原來是場噩夢啊……” “哼忠蝗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起漓拾,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤阁最,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后骇两,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體速种,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年低千,在試婚紗的時候發(fā)現(xiàn)自己被綠了配阵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖棋傍,靈堂內(nèi)的尸體忽然破棺而出救拉,到底是詐尸還是另有隱情,我是刑警寧澤瘫拣,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布亿絮,位于F島的核電站,受9級特大地震影響麸拄,放射性物質(zhì)發(fā)生泄漏派昧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一感帅、第九天 我趴在偏房一處隱蔽的房頂上張望斗锭。 院中可真熱鬧,春花似錦失球、人聲如沸岖是。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豺撑。三九已至,卻和暖如春黔牵,著一層夾襖步出監(jiān)牢的瞬間聪轿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工猾浦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留陆错,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓金赦,卻偏偏與公主長得像音瓷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子夹抗,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android開發(fā)使用的手機一般處于觸摸模式, 因此默認情況下并不會有焦點, 所以之前一直對焦點不是很熟悉. 但是...
    AssIstne閱讀 8,101評論 1 18
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,749評論 25 707
  • 前言 相信很多剛接觸AndroidTV開發(fā)的開發(fā)者绳慎,都會被各種焦點問題給折磨的不行。不管是學技術(shù)還是學習其他知識漠烧,...
    礪雪凝霜閱讀 5,566評論 15 25
  • 還行吧杏愤,畢竟沒怎么學好c。
    實在想不出昵稱丶閱讀 175評論 0 0
  • 三天就走過我的二十年 事出有因已脓,某月某一天在一起逛沃爾瑪珊楼,三人竟然深深被自助燒烤工具吸引久久不愿離去,更萌生了眾籌...
    無處留歡喜閱讀 285評論 0 1