Android View架構總結(二)View焦點

來源:互聯網
上載者:User

標籤:

請尊重分享成果,轉載請註明出處:
http://blog.csdn.net/hejjunlin/article/details/52263256

前言:View架構寫到第六篇,發現前面第二篇竟然沒有,然後事情是在公眾號發了,忘記在部落格上更新,所以關注公眾號的應該都看過了,趁今天有時間遂補上。(PS:本篇文章中源碼均是android 6.0,請知曉)

本來之前說view下篇是寫onMeasure,onLayou,onDraw相關的,筆者做盒子開發,遙控器按鍵,碰到的都是焦點控制相關。所以先把焦點放到了onMeasure,onLayou,onDraw之前。以前踩過不少坑,最近公司同事做分享,遂下決心總結下,Agenda如下:

  • ViewRoot
  • View的焦點
  • ViewGroup的焦點
  • 父容器焦點的處理
  • 失去焦點或清除焦點
  • 焦點移動
  • FocusFinder尋找焦點
  • 總結
Android View焦點

Android焦點相關邏輯大部分都在都在View, ViewGroup和FocusFinder三個類中.

ViewRoot

View對象都有一個mParent變數(添加到ViewGroup後), 代指其父容器. 絕大部分View的mParent都是ViewGroup類型, 除了根節點. 一個Window中View根節點DecorView的mParent稱為ViewRoot, 在安卓4.0後ViewRoot對應ViewRootImpl, 它不是View的子類, 而是個ViewParent. ViewRootImpl是串連Window和DecorView的紐帶, View的焦點, 按鍵, 布局, 渲染等流程都是從ViewRoot中開始的.

View的焦點

基本流程如下

View(包括ViewGroup)擷取焦點都通過如下三個方法:
View.java -> requestFocus()

從上面可以看到前兩個最終會執行到第三個方法.
最後的requestFocusNoSearch先判斷是否可以擷取焦點, 然後進入下面的流程:
View.java -> requestFocusNoSearch()

View.java -> handleFocusGainInternal()

上面的流程比較簡單: 如果當前沒有焦點, 先置焦點標誌, 再通知parent, 然後重新整理圖片.

主要的流程在mParent的requestChildFocus裡面, 後面會分析. 那裡會逐層向上修改焦點View並清除原來有焦點的View的焦點
onFocusChange會觸發invalidate重新整理, 然後調用onFocusChangeListener. 預設情況每個View只能設定一個onFocusChangeListener, 而開發中經常遇到需要設定多個Listener的情況, 我們就可以重寫onFocusChange方法, 實現回調多個onFocusChangeListener的需求.

ViewGroup的焦點

ViewGroup擷取焦點是在View擷取焦點流程中多了內部焦點處理
ViewGroup.java -> requestFocus()

上面代碼中descendantFocusability決定了是先按View焦點流程處理(自己處理焦點)還是先把給子View處理

  • FOCUS_BLOCK_DESCENDANTS 不允許子View擷取焦點,那麼按照View的流程進行
  • FOCUS_BEFORE_DESCENDANTS 先按照View的流程處理, 如果自己不能擷取焦點則給孩子處理
  • FOCUS_AFTER_DESCENDANTS 先嘗試給子view焦點, 如果沒有可擷取焦點再按照View流程自己擷取焦點
  • FOCUS_BEFORE_DESCENDANTS,預設值 我們可以通過setDescendantFocusability(int d)設定

onRequestFocusInDescendants方法是給子類重寫使用, 可以控制子View處理焦點. 預設按照子View順序處理, direction向下或向右則從第一個開始, 向上或向左則從最後一個開始, 直到某個子View擷取焦點

(注意此方法只在此ViewGroup及其上層View上調用requestFocus時會執行到)

父容器焦點的處理

在View擷取焦點流程中會調用mParent.requestChildFocus, 維護View樹上焦點唯一, 在各層ViewGroup中儲存有焦點的子View

ViewGroup.java -> requestChildFocus()

先清除自己的焦點, 如果原來內部有焦點, 先清除其焦點, 儲存擷取焦點的孩子, 然後調用上一層的requestChildFocus. 最後的調用可知, 這個方法會一直調用到View的樹的root節點.

在當前ViewGroup內部, 任何一個孩子取得焦點都會執行到這個方法, 因此此方法也是ViewGroup得知孩子焦點變化的方法之一.(可惜不能得知孩子失去焦點)

失去焦點或清除焦點

擷取焦點可以是主動的, 但失去焦點一般都是被動的(見上面的代碼), 因此邏輯相對簡單, 只要清除焦點狀態即可.

ViewGroup.java -> unFocus()

View.java -> unFocus() -> clearFocusInternal()

注意上面的方法是預設package存取層級的, 我們無法重寫也不能調用

也可以主動清除焦點, 與擷取焦點流程相似
View.java -> clearFocus()

ViewGroup.java -> clearFocus()

ViewGroup.java -> clearChildFocus()

以上是安卓View系統焦點處理的全部流程和涉及到的方法, ViewRootImpl的requestChildFocus和clearChildFocus實現我們不需要關注

另外還有以下一些輔助方法

  • boolean isFocusable() View是否可以擷取焦點
  • boolean isFocused() View是否擷取焦點
  • boolean hasFocus() View/ViewGroup內部是否有焦點
  • View findFocus() 取到View/ViewGroup內部的焦點View
  • View getFocusedChild() 取到ViewGroup內部有焦點的子View
  • View getRootView() 取到根節點View(一般是DecorView或頂層ViewGroup)
焦點移動

除了在代碼裡面控制焦點, 系統對沒有處理的方向鍵等一些按鍵自動按照焦點移動來處理, 見下面代碼

ViewRootImpl.java -> processKeyEvent()



代碼比較上, 但是主要做了三個步驟

  1. 如果View沒有處理按鍵, 把上下左右tab等按鍵轉換成對應方向
  2. 在當前焦點View上通過focusSearch方法尋找對應方向的下一個View
  3. 尋找到的View調用requestFocus,因此主要的流程在focusSearch中

View.java -> focusSearch()

普通View尋找什麼都沒做, 交給parent來完成.

ViewGroup.java -> focusSearch()

ViewRootImpl.java -> focusSearch()

我們可以重寫focusSearch控制焦點移動順序, 而預設的焦點移動順序由FocusFinder決定

FocusFinder尋找焦點

FocusFinder為public的工具類, 主要就兩個方法, 可以在給定的View內在指定方向尋找指定View或座標的下一個焦點
如下:
FocusFinder.java -> findNextFocus()

核心邏輯就兩步, 先尋找setNextFocusXXId設定的View, 如果沒有按照就近演算法尋找.
具體演算法不再分析, SDK裡面有源碼.

總結

綜合上面的流程分析, 我們在實現自訂View時, 對焦點的特殊需求有如下思路

  • requestFocus和clearFocus直接對View清除或轉移焦點
  • 除了onFocusChangeListener,還可以在onFocusChange方法中實現一些View失去/獲得焦點時通知
  • 對ViewGroup, 如果只需要在子View擷取焦點時得到通知, 有requestChildFocus方法.
  • 重寫onRequestFocusInDescendants方法可以控制某些情景下ViewGroup焦點
  • 控制焦點移動可以重寫focusSearch方法
  • 尋找焦點的過程,主要是從View的focusSearch(…)方法開始,從當前焦點開始逐層往外,最終在最外層布局執行FocusFinder中的核心方法來獲得下個焦點所在的視圖view.
  • 另外還有FocusFinder工具和上面的輔助方法.
  • 在view獲得焦點之前,必須先判斷該view是否具有獲得焦點的許可權,可通過isFocusable和isFocusableInTouchMode來判斷;//這個前文代碼中有
  • 同時可以通過setFocusable和setFocusableInTouchMode來設定指定view具有擷取焦點的許可權.
  • 在XML阻止子view獲得焦點的屬性是:android:descendantFocusability =”blocksDescendants”,但是如果當前activity經過了pause或者stop後再重新resume後該屬性會失效,這時可以在onresume裡面加上requestFocusFromTouch方法就能重新時屬性生效.

第一時間獲得部落格更新提醒,以及更多android乾貨,源碼分析,歡迎關注我的公眾號,掃一掃下方二維碼或者長按識別二維碼,即可關注。

如果你覺得好,隨手點贊,也是對筆者的肯定,也可以分享此公眾號給你更多的人,原創不易

Android View架構總結(二)View焦點

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.