一點(diǎn)見解: 焦點(diǎn)那點(diǎn)事(一)

Android開發(fā)使用的手機(jī)一般處于觸摸模式, 因此默認(rèn)情況下并不會有焦點(diǎn), 所以之前一直對焦點(diǎn)不是很熟悉. 但是在電視端開發(fā)上, 焦點(diǎn)的處理可以說直接影響了用戶體驗(yàn), 因此借此熟悉下焦點(diǎn)處理的流程.

本文著重介紹焦點(diǎn)相關(guān)的一些關(guān)鍵方法, 先從局部了解下焦點(diǎn)的一些基礎(chǔ)規(guī)則和行為特點(diǎn).

獲取焦點(diǎn)的前提

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

View

獲取焦點(diǎn)

調(diào)用View#requestFocus系列方法

進(jìn)入View#requestFocusNoSearch

在該方法中會對控件的當(dāng)前狀態(tài)進(jìn)行判斷, 如果不符合獲取焦點(diǎn)的前提則直接返回false告知調(diào)用方, 控件不會獲取焦點(diǎn)

只要符合前提就會繼續(xù)執(zhí)行, 最終必定返回true, 不論當(dāng)前控件的焦點(diǎn)狀態(tài)是否有改變

符合前提則進(jìn)入 View#handleFocusGainInternal

如果控件已經(jīng)持有焦點(diǎn), 則不會做任何事情, 直接結(jié)束流程

如果沒有焦點(diǎn),

  1. 改變焦點(diǎn)標(biāo)志位, 此時(shí)View#isFocused就會返回true
  2. 通過ViewParent#requestChildFocus通知父控件即將獲取焦點(diǎn)
  3. 通知其他部件焦點(diǎn)狀態(tài)發(fā)生變化(略, 本文不關(guān)心)
  4. 觸發(fā)OnGlobalFocusChangeListener的回調(diào)
  5. 觸發(fā)OnFocusChangeListener回調(diào)
  6. 重繪, 結(jié)束流程

清除焦點(diǎn)

調(diào)用View#clearFocus主動放棄焦點(diǎn)

如果控件本身沒有焦點(diǎn), 則什么都不會發(fā)生

如果控件持有焦點(diǎn)

  1. 改變焦點(diǎn)標(biāo)志位
  2. 通過ViewParent#clearChildFocus通知父控件, 當(dāng)前控件放棄焦點(diǎn)
  3. 觸發(fā)OnFocusChangeListener回調(diào)
  4. 調(diào)用當(dāng)前控件的根控件(rootView)的requestFocus方法
  5. 如果步驟4中沒有找到新的焦點(diǎn)控件, 則觸發(fā)OnGlobalFocusChangeListener的回調(diào), 注: 如果找到新的焦點(diǎn)控件, 那么新的控件獲取焦點(diǎn)的過程中就會回調(diào)OnGlobalFocusChangeListener, 所以這里只有沒找到才進(jìn)行步驟5

注: 由上流程可以知道, 如果根控件查找控件的時(shí)候找到的控件還是這個(gè)控件, 那么OnFocusChangeListener就會被調(diào)用兩次, 先失去焦點(diǎn), 然后又獲取到焦點(diǎn)

ViewGroup

焦點(diǎn)分發(fā)策略DescendantFocusability

  1. FOCUS_BLOCK_DESCENDANTS: 攔截焦點(diǎn), 直接自己嘗試獲取焦點(diǎn)
  2. FOCUS_BEFORE_DESCENDANTS: 首先自己嘗試獲取焦點(diǎn), 如果自己不能獲取焦點(diǎn), 則嘗試讓子控件獲取焦點(diǎn)
  3. FOCUS_AFTER_DESCENDANTS: 首先嘗試把焦點(diǎn)給子控件, 如果所有子控件都不要, 則自己嘗試獲取焦點(diǎn)

獲取焦點(diǎn)

根據(jù)焦點(diǎn)分發(fā)策略決定下面兩個(gè)方法的調(diào)用順序

通過View#requestFocus自己獲取焦點(diǎn)

ViewGroup看作View, 直接走View獲取焦點(diǎn)的流程來獲取焦點(diǎn)

進(jìn)入onRequestFocusInDescendants

可以傳入方向來改變遍歷的順序, 默認(rèn)是從0遞增

遍歷子控件, 調(diào)用子控件的View#requestFocus來嘗試把焦點(diǎn)給可見的子控件, 某個(gè)子控件成功獲取到焦點(diǎn)后, 停止遍歷

注: 重寫該方法可以改變ViewGroup分發(fā)焦點(diǎn)給子控件的行為, 例如遍歷順序

清除焦點(diǎn)

如果焦點(diǎn)控件不是它的子控件, 那么直接把當(dāng)前的ViewGroup看作ViewView#clearFocus流程, 反之則調(diào)用焦點(diǎn)控件的View#clearFocus.

注: 區(qū)別在于重新分發(fā)焦點(diǎn)時(shí)的選擇范圍.

ViewParent

ViewParent是一個(gè)接口, 表示了一個(gè)父控件應(yīng)該具備的功能, ViewGroup實(shí)現(xiàn)了該接口.

與焦點(diǎn)相關(guān)的接口有4個(gè)

clearChildFocus

當(dāng)子控件主動放棄焦點(diǎn)的時(shí)候會通過這個(gè)方法通知父控件.

ViewGroup的默認(rèn)實(shí)現(xiàn)中, 會置空當(dāng)前焦點(diǎn)控件, 表示該父控件下沒有子控件獲取焦點(diǎn), 接著把這個(gè)事件通知給上級父控件.

注1: 這個(gè)方法名有點(diǎn)讓人誤解, 應(yīng)該把這個(gè)方法看作一個(gè)回調(diào), 表明了一個(gè)狀態(tài), 在這個(gè)方法中并沒有做清除焦點(diǎn)的操作, 實(shí)際的清除動作是在View#clearFocus中完成的, 這個(gè)方法也是在這個(gè)流程中被調(diào)用的. 而且是在子控件已經(jīng)放棄焦點(diǎn)后調(diào)用.
注2: 區(qū)分主動放棄和因?yàn)槠渌丶@取了焦點(diǎn)而被動丟失焦點(diǎn)的情況

requestChildFocus

當(dāng)子控件獲取了焦點(diǎn)后, 通過這個(gè)方法通知父控件. 同clearChildFocus類似, 應(yīng)該把這個(gè)方法看作是一個(gè)回調(diào).

ViewGroup的默認(rèn)實(shí)現(xiàn)中, 因?yàn)橥瑫r(shí)只會有一個(gè)焦點(diǎn), 因此在這里應(yīng)該把舊焦點(diǎn)清除掉, 大致流程如下

  1. 如果焦點(diǎn)分發(fā)策略為FOCUS_BLOCK_DESCENDANTS則什么也不干
  2. 如果父控件自身有焦點(diǎn), 通過View#unFocus清除焦點(diǎn)
  3. 如果父控件當(dāng)前已經(jīng)有焦點(diǎn)控件, 并且和新的控件不一致, 那么通過View#unFocus清除舊焦點(diǎn)控件的焦點(diǎn)
  4. 向上傳遞這個(gè)事件

內(nèi)部清除焦點(diǎn)View#unFocus

這個(gè)方法和View#clearFocus相同點(diǎn)在于都會執(zhí)行View#clearFocusInternal方法, 區(qū)別在于unFocus只會執(zhí)行clearFocus中, 上文清除焦點(diǎn)中提到的1, 3步驟, 因此不會通知父控件, 不會觸犯requestChildFocus回調(diào), 因?yàn)檫@個(gè)方法是在子控件被動失去焦點(diǎn)時(shí)調(diào)用的, 所以也不會觸發(fā)焦點(diǎn)分發(fā).

因此新舊焦點(diǎn)切換的大致流程是

  1. 新焦點(diǎn)控件獲取焦點(diǎn)
  2. 新焦點(diǎn)控件通知父控件
  3. 父控件清除舊焦點(diǎn)控件的焦點(diǎn)
  4. 舊焦點(diǎn)控件回調(diào)OnFocusChangeListener
  5. 觸發(fā)OnGlobalFocusChangeListener的回調(diào)
  6. 新焦點(diǎn)控件回調(diào)OnFocusChangeListener

focusableViewAvailable

通知父控件, 子控件的狀態(tài)發(fā)生改變, 從不能獲取焦點(diǎn), 變成可能可以獲取焦點(diǎn).

有兩種情況會被調(diào)用

  1. 子控件從unFocusable變?yōu)閒ocusable
  2. 子控件從不可見變?yōu)榭梢? 即使它不是focusable也會調(diào)用, 因此它的子控件可能可以獲取焦點(diǎn).

ViewGroup中的默認(rèn)實(shí)現(xiàn)只是在符合條件的情況下把這個(gè)事件向上傳遞給自己的父控件.

focusSearch(View, int)

查找指定方向中最近的, 想要獲取焦點(diǎn)的控件.

這個(gè)方法直接決定了焦點(diǎn)的移動規(guī)則, 非常重要.

ViewGroup的默認(rèn)實(shí)現(xiàn)中, 會一直向上傳遞, 直到根控件, 接著調(diào)用FocusFinder#findNextFocus方法查找合適的控件. 稍后再分析這個(gè)方法.

View中有一個(gè)同名的方法focusSearch(int), 該方法直接調(diào)用了父控件的focusSearch(View, int)來查找下一個(gè)焦點(diǎn)控件

findNextFocus

查找步驟大致如下

手動指定

如果有通過android:nextFocusDown等手動指定控件, 則返回對應(yīng)方向的控件

動態(tài)計(jì)算
  1. 獲取所有可以獲取焦點(diǎn)的控件的集合
  2. 計(jì)算相對當(dāng)前焦點(diǎn)控件的坐標(biāo)
  3. 根據(jù)方向選擇合適的控件

總結(jié)

  1. 分析的過程要注意區(qū)分ViewViewGroup的差異和新焦點(diǎn)和舊焦點(diǎn)控件的方法調(diào)用.
  2. ViewParent是一個(gè)接口, 其中一些方法應(yīng)該看作是回調(diào), 子控件通過這些回調(diào)通知父控件焦點(diǎn)狀態(tài)發(fā)生了變化, 提醒父控件進(jìn)行相關(guān)處理, 確保只有一個(gè)焦點(diǎn)存在
  3. 某個(gè)控件獲取焦點(diǎn)的同時(shí), 舊焦點(diǎn)控件也會失去焦點(diǎn), 這個(gè)動作是在requestChildFocus中發(fā)生的.
  4. 焦點(diǎn)移動的關(guān)鍵方法是focusSearch(View, int), 下一篇文章一點(diǎn)見解: 焦點(diǎn)那點(diǎn)事(二)接著分析焦點(diǎn)移動的發(fā)起點(diǎn)和過程.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子惜纸,更是在濱河造成了極大的恐慌猪勇,老刑警劉巖钥屈,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咽袜,死亡現(xiàn)場離奇詭異妨马,居然都是意外死亡咐熙,警方通過查閱死者的電腦和手機(jī)弱恒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棋恼,“玉大人返弹,你說我怎么就攤上這事锈玉。” “怎么了义起?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵拉背,是天一觀的道長。 經(jīng)常有香客問我默终,道長椅棺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任齐蔽,我火速辦了婚禮两疚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘含滴。我一直安慰自己诱渤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布谈况。 她就那樣靜靜地躺著勺美,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸦做。 梳的紋絲不亂的頭發(fā)上励烦,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天谓着,我揣著相機(jī)與錄音泼诱,去河邊找鬼。 笑死赊锚,一個(gè)胖子當(dāng)著我的面吹牛治筒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舷蒲,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼耸袜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了牲平?” 一聲冷哼從身側(cè)響起堤框,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纵柿,沒想到半個(gè)月后蜈抓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昂儒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年沟使,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渊跋。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腊嗡,死狀恐怖着倾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情燕少,我是刑警寧澤卡者,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站客们,受9級特大地震影響虎眨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜镶摘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一嗽桩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凄敢,春花似錦碌冶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拒逮,卻和暖如春罐氨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滩援。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工栅隐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人玩徊。 一個(gè)月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓租悄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恩袱。 傳聞我的和親對象是個(gè)殘疾皇子泣棋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

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

  • 上一篇文章, 一點(diǎn)見解: 焦點(diǎn)那點(diǎn)事(一), 了解了焦點(diǎn)相關(guān)的一些基本知識, 提到焦點(diǎn)切換的關(guān)鍵方法ViewPar...
    AssIstne閱讀 2,801評論 7 6
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,304評論 25 707
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,444評論 0 17
  • 相信每個(gè)人都有時(shí)間過的好快的感覺畔塔。平時(shí)事務(wù)繁忙的人這種感覺會更快一些潭辈,每個(gè)時(shí)間點(diǎn)都安排著事情,他們的感覺是時(shí)間太快...
    不萊梅閱讀 252評論 0 2
  • >Linker中主要的兩個(gè)源點(diǎn)是dlopen和dlsym澈吨。 * dlopen傳入兩個(gè)參數(shù)把敢,返回一個(gè)文件句柄。傳入的...
    sakuradream閱讀 765評論 0 0