android焦點控制
⑴ Android TV開發焦點移動源碼分析
點可以理解為選中態,在Android TV上起很重要的作用。一個視圖控制項只有在獲得焦點的狀態下,才能響應按鍵的Click事件。
相對於手機上用手指點擊屏幕產生的Click事件, 在TV中通過點擊遙控器的方向鍵來控制焦點的移動。當焦點移動到目標控制項上之後,按下遙控器的確定鍵,才會觸發一個Click事件,進而去做下一步的處理
在處理焦點的時候,有一些基礎的用法需要知道。
首先,一個控制項isFocusable()需要為true才有資格可以獲取到焦點。如果想要在觸摸模式下獲取焦點,需要通過setFocusableInTouchMode(boolean)來設置。也可以直接在xml布局文件中指定:
keyEvent 分發過程:
而當按下遙控器的按鍵時,會產生一個按鍵事件,就是KeyEvent,包含「上」,「下」,「左」,「右」,「返回」,「確定」等指令。焦點的處理就在KeyEvent的分發當中完成。
首先,KeyEvent會流轉到ViewRootImpl中開始進行處理,具體方法是內部類 ViewPostImeInputStage 中的 processKeyEvent :
接下來先看一下KeyEvent在view框架中的分發:
這里也是可以做焦點控制,最好是在 event.getAction() == KeyEvent.ACTION_DOWN 進行.
因為android 的 ViewRootlmpl 的 processKeyEvent 焦點搜索與請求的地方 進行了判斷if (event.getAction() == KeyEvent.ACTION_DOWN)
• 首先ViewGroup會一層一層往上執行父類的dispatchKeyEvent方法,如果返回true那麼父類的dispatchKeyEvent方法就會返回true,也就代表父類消費了該焦點事件,那麼焦點事件自然就不會往下進行分發。
• 然後ViewGroup會悔李鍵判斷mFocused這個view是否為空,如果為空就會return false,焦點繼續往下傳遞;如果不為空,那就會return mFocused的dispatchKeyEvent方法返回的結果。這個mFocused其實是擾殲ViewGroup中當前獲取焦點的子View
發現執行了onKeyListener中的onKey方法,如果onKey方法返回true,那麼dispatchKeyEvent方法也會返回true
如果想要修改ViewGroup焦點事件的分發
• 重寫view的dispatchKeyEvent方法
• 給某個子view設置onKeyListener監聽
下面再來看一下如果一個頁面第一次進入,系統是如何確定焦點是定位在哪個view上的
由於DecorView繼承自FrameLayout,這里調用的是ViewGroup的requestFocus
descendantFocusability:
• FOCUS_AFTER_DESCENDANTS:先分發給Child View進行處理,如果所有的Child View都沒有處理,則自己再處理
• FOCUS_BEFORE_DESCENDANTS:ViewGroup先對焦碧巧點進行處理,如果沒有處理則分發給child View進行處理
• FOCUS_BLOCK_DESCENDANTS:ViewGroup本身進行處理,不管是否處理成功,都不會分發給ChildView進行處理
因為 PhoneWindow 給 DecoreView 初始化時設置 了 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS),所以這里默認是FOCUS_AFTER_DESCENDANTS
到此第一次請求焦點的過程基本告一個段落
焦點移動的時候,默認的情況下,會按照一種演算法去找在指定移動方向上最近的鄰居。在一些情況下,焦點的移動可能跟開發者的意圖不符,這時開發者可以在布局文件中使用下面這些XML屬性來指定下一個焦點對象:
在KeyEvent分發中已經知道如果分發過程中event沒有被消耗,就會根據方向搜索以及請求焦點View
流程一:查找用戶指定的下一個焦點
流程二:獲取搜索方向上所有可以獲取焦點的view,使用演算法查找下一個view
addFocusables() 獲取搜索方向上可獲得焦點的view
descendantFocusability屬性決定了ViewGroup和其子view的聚焦優先順序
• FOCUS_BLOCK_DESCENDANTS:viewgroup會覆蓋子類控制項而直接獲得焦點
• FOCUS_BEFORE_DESCENDANTS:viewgroup會覆蓋子類控制項而直接獲得焦點
• FOCUS_AFTER_DESCENDANTS:viewgroup只有當其子類控制項不需要獲取焦點時才獲取焦點
addFocusables 的第一個參數views是由root決定的。在ViewGroup的focusSearch方法中傳進來的root是DecorView,也可以主動調用FocusFinder的findNextFocus方法,在指定的ViewGroup中查找焦點。
FocusFinder.findNextFocus 查找焦點
⑵ Android TV按鍵焦點原理淺談
原文鏈接: Android TV按鍵焦點原理淺談
本篇主要閱讀 Android 源碼講解 TV 的按鍵事件分發原理和焦點查找原理,源碼基於 Android9.0 ,首先思考幾個問題:
帶著這些問題,我們一起來擼 Android 源碼吧!了解了系統是如何處理的有便於我們解決 TV 上一些按鍵和焦點的問題。
首先我們看下按鍵事件的入口 ViewRootImpl 類中的 ViewPostImeInputStage 內部類:
可以看到注釋1,2,3,4分別判斷不同事件執行不同方法,本篇主要討論的TV焦點事件,主要看下 processKeyEvent 方法:
可以看到在該方法中執行了 mView.dispatchKeyEvent 方法,這里的 View 其實是 DecorView ,接著看下該方法:
上面首先判斷了如果是第一次按下則處理panel的快捷鍵,如果處理了則不往下走,否則繼續判斷當窗口未銷毀且回調非空則回調處理,如果處理了則不往下走,否則讓 PhoneWindow 對應的 onKeyDown , onKeyUp 方法來處理。
接下來我們按照這個派發順序依次來看看相關方法的實現,這里先看看 Activity 的 dispatchKeyEvent 實現:
我們看第1點 superDispatchKeyEvent 方法,可以看到該方法為一個抽象方法,而它的實現是實現它的子類 PhoneWindow :
該方法又回調用 DecorView 中的 superDispatchKeyEvent 方法:
此時,再來看下 ViewGroup 的 dispatchKeyEvent 方法:
接著看下 View 的 dispatchKeyEvent 方法:
該方法主要是判斷如果有給 View 設置 OnKeyListener 事件且 View 為可用狀態,則優先處理監聽事件,其次調用 KeyEvent 的 dispatch 方法,接下來我們看下該方法:
該方法主要處理了按下、彈起事件,其中按下如果 mRepeatCount 重復次數大於0判斷為長按,則執行長按事件。
我們繼續看下 View 的 onKeyDown 方法:
按下事件判斷了如果為確認相關的按鍵才到下一步處理,判斷點擊或長按條件滿足,執行按下 View 正中心坐標,然後執行 checkForLongClick 檢查長按方法,看下該方法如下:
我們經常會遇到電視按遙控器時長按會執行一次 onKeyDown 、 onKeyUp ,之後才是一直 onKeyDown ,松開後才執行 onKeyUp ,原因就在於這個檢查長按方法是延遲的。 delayOffset 傳進來的是0,所以延遲時間為 ViewConfiguration.getLongPressTimeout() ,即該類中定義的 DEFAULT_LONG_PRESS_TIMEOUT 常量。
同樣的如果是觸摸屏,可以看下 View 類中的 onTouchEvent 方法在按下操作的時候會開啟 CheckForTap 線程檢查是否是長按,該線程同樣是延遲的,時間為 ViewConfiguration.getTapTimeout() ,即該類中的 TAP_TIMEOUT 常量,知道了這個你就知道如果寫腳本或插件模擬長按應該間隔多長時間了,是不是一下你的模擬長按插件速度又可以更加准確快速的實現了。
不同版本系統定義的延遲時間有可能不一樣,比如Google API 28 的 DEFAULT_LONG_PRESS_TIMEOUT 是500, TAP_TIMEOUT 是100,而 API 30 的 DEFAULT_LONG_PRESS_TIMEOUT 是400, TAP_TIMEOUT 也是100。
接下來再看下 Activity 的 onKeyDown :
回到 Decorview 中的 dispatchKeyEvent 方法看看 PhoneWindow 的 onKeyDown 方法:
onKeyUp 方法也可以自己再看下,以上就是淺談按鍵事件的分發流程了。
總結:
上面講解了按鍵事件分發流程,當上面分發完所有都沒消費的時候,就會繼續走 ViewRootImpl 的焦點導航流程,接下來看下 performFocusNavigation 方法:
首先我們看 mView.findFocus() ,該方法實際是調用了 ViewGroup 的 findFocus 方法:
該方法很簡單,就是向下遞歸查找在當前頁面已經獲取焦點的 View ,繼續看 focused.focusSearch(direction) 調用了 View 的 focusSearch 方法:
該方法向上遞歸查找,調用 ViewGroup 的 focusSearch 方法:
如果是根命名空間,則調用 FocusFinder 的 findNextFocus 方法查找焦點,否則繼續往上查找。繼續看 FocusFinder 的 findNextFocus 方法:
可以看到該方法首先查找用戶指定的下一個獲取焦點的 view ,如果找到了直接返回該 view ,如果沒找到繼續下面先添加 effectiveRoot 下的所有 view 到 focusables 集合中去,然後調用 findNextFocus 方法查找系統可獲取下一個焦點的最近 view 。
我們先看下 findNextUserSpecifiedFocus 方法的實現:
通過用戶指定焦點方式不是本篇的重點,這里就不貼出內部細節源碼了。該方法實際就是調用 View 的 findUserSetNextFocus 方法來查找用戶設置的下一個可獲取焦點的 view ,然後在 while 循環中判斷如果找到的是可以獲取焦點並且可見的並且不是 InTouchNode 模式,則返回該焦點,否則繼續循環查找直到找了一個循環沒有找到可以獲取焦點的或者 userSetNextFocus 為 null 跳出循環返回 null 。
再來看下系統就近原則查找的 findNextFocus 方法:
該方法主要通過 在相對方向上找下一個焦點,該方法內部邏輯比較簡單,這里就不貼出來了,進去看下就知道其實就是先給 focusables 排序,然後從中找到 focused 在其中的後一個或前一個 view ,如果沒找到並且 focusables 不為空則返回 focusables 的第一個。
接下來我們重點看下 方法:
再看下 isBetterCandidate 方法,該方法很關鍵,內部包含一系列邏輯如何成為最佳候選者:
該方法英文注釋很直觀,就不中文翻譯了,首先看下成為候選人的 isCandidate 方法:
該方法判斷了目標Rect如果在源Rect的方向一側且不在內部的話,則為候選者,如第一個 destRect 左側應在 srcRect 左側左邊, destRect 右側應在 srcRect 右側左邊,其他方向同理。
接下來看下 beamBeats 方法:
⑶ 怎麼讓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移動應用中的焦點分析
簡單一點理解,在移動應用中,焦點就是當前正在處理事件的位置。在手機應用中,最有可能用到焦點的就是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方法。
⑸ Android音頻焦點處理方法
前兩宏陸天在項目上做了對音頻焦點使用方法的一個總結,蔽答頃記錄在下面。
在Android設備上,默認可以有多個應用同時播放音頻,但是,這種處理帶來的用戶體驗並不好,為了解決這個問題,Android引入了音頻焦點機制,一次只能有一個App持有音頻焦點。
一般情況下,當一個App失去音頻焦點時,為了有較好的用戶體驗,它應該主動暫停播放,從而使新獲得音頻焦點的App可以清晰的播放音頻,避免混音的情況。
以下是官方建議的處理音頻焦點應該遵循的一些規則:
處理音頻焦點都是通過AudioManager這個類,如下是獲得該類實例的方法:
AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
下面介紹音頻焦點處理相關的一些方法(不同的Android版本處理音頻焦點的方式略有差別,以下內容基於Android 4.4)。
下面進行詳細介紹。
2. abandonAudioFocus(OnAudioFocusChangeListener l)
參數同上。
返回值同上。
3. AudioManager.OnAudioFocusChangeListener
當音頻焦點發生變化時,可以在OnAudioFocusChangeListener的onAudioFocusChange(int focusChange)方法中監聽到,下面詳細說明該方法。
onAudioFocusChange(int focusChange)
參數:focusChange可以表明當前音頻焦點發生的是何種變化,需要根據該參數狀態做出正確的響應。
分為獲得和舉鎮丟失兩種情況:
下面是一個申請長音頻焦點,播放音樂的例子:
以上
⑹ android 中如何設置焦點的位置。
設置焦點需要以下幾步:
1,打開手機相機,進入設置,打開焦點功能。
2,進入拍照頁面,雙擊屏幕的一個地方,相機就會自動鎖定焦點。
3,焦點會帶有兩個鎖定框,一個鎖定,一個可以拖動。
4,鎖定在屏幕上的鎖定框就是焦點,可移動的是進行焦距調節。
多數相機的焦距處理並不明顯,直接移動焦點效果會好些。
⑺ 如何控制listview的焦點
1.將ListView的Item Layout的子控制項focusable屬性設置為false
2.對Item Layout的根控制項android:descendantFocusability="blocksDescendant"
例如:
<RelativeLayout
xmlns:android=""
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dip"
android:background="#ffffffff"
android:descendantFocusability="blocksDescendant >
<LinearLayout
<RatingBar
android:id="@+id/rb_bookRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:rating="2.0"
style="@style/RatingBar"
android:isIndicator="true"
/>
</LinearLayout>
<Button
android:id="@+id/btn_schele"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:focusable="false"
android:text="影訊"
style="@style/Button"
/>
</RelativeLayout>
⑻ Android 獲取焦點
你好,我舉例說明:比如有個輸入框,當你點擊輸入框時,這時它的游標在閃爍,可以輸入文字,那麼就可以說這個輸入框獲得焦點了。獲取焦點的控制項就是當前可以執行操作的控制項。
使用view.requestFocus()方法可以手動獲取焦點。
以上,希望對你有幫助。
⑼ android某一app設為焦點app
在做電視機頂盒應用的時候,每個控制項都是有焦點變化的顯示的冊啟,如果當前焦點被一個自己不知道的控制項獲取到,則其他控制項就不會獲取到焦點,我們必須要找到當前獲取焦點的控制項,將其 android:focusable="false" ,不讓它獲取到焦點。
通過findFocus()方法來獲取到當前的控制項。
在Activity的onCreate方法中,啟動線毀困程。
new Thread(run2).start();
run2的代碼如下所示:
Runnable run2 = new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
View rootview = DetailActivity.this.getWindow().getDecorView();
View aaa = rootview.findFocus();
Log.i("tag", aaa.toString());
}
}
};
這樣每隔5秒會獲取一纖姿念次當前有焦點的控制項,列印出log就可以了。
列印出的log如下所示:
最後就是控制項的id了,這樣你就找到了當前獲取焦點的控制項了。
結束。
鏈接:https://www.waimaiguai.com/technology/article/1327504
來源:外賣怪
⑽ Android TV 焦點原理源碼解析
相信很多剛接觸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焦點的原理實現就這些。總結一下:
為了方便同志們學習,我這做了張導圖,方便大家理解~