相信很多剛接觸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 頁面失去焦點
在網上找了好久,有點監聽軟鍵盤事件,有點調用 clearFouse()方法,但是測試了都沒有!xml中也找不到相應的屬性可以關閉這個默認行為
1 解決之道:在EditText的父級控制項中找一個,設置成
Android:focusable="true"
android:focusableInTouchMode="true"
這樣,就把EditText默認的行為截斷了!
<LinearLayout
style="@style/FillWrapWidgetStyle"
android:orientation="vertical"
android:background="@color/black"
android:gravity="center_horizontal"
android:focusable="true"
android:focusableInTouchMode="true"
>
<ImageView
android:id="@+id/logo"
style="@style/WrapContentWidgetStyle"
android:background="@drawable/dream_dictionary_logo"
/>
<RelativeLayout
style="@style/FillWrapWidgetStyle"
android:background="@drawable/searchbar_bg"
android:gravity="center_vertical"
>
<EditText
android:id="@+id/searchEditText"
style="@style/WrapContentWidgetStyle"
android:background="@null"
android:hint="Search"
android:layout_marginLeft="40dp"
android:singleLine="true"
/>
</RelativeLayout>
</LinearLayout>
2 還有一個方法也可以非常簡單的實現這個功能:
EditText對象的clearFocus();
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editMsgView.getWindowToken(), 0);(關閉軟鍵盤。。。)
3更多問題解決辦法請參考android學習手冊,例子、源碼、文檔全部搞定,採用androidstudo的目錄結構,360手機助手中下載。下面是截圖。
③ android 中如何設置焦點的位置。
默認從上倒下 從左到右第一個可以輸入的控制項作為焦點,如果不想默認可以指定某個view或得焦點
④ 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方法。