前面三篇都是從源碼的角度分析按鍵事件雀久、焦點變換的原理隐圾,作為應用層的開發(fā)者必搞,
分析源碼都是帶著實際的開發(fā)困惑的偎快,要不然誰沒事做會read the fucking source code,
今天就分享一個Android TV開發(fā)中的解決的焦點移動問題入录。
起因
使用過奇異果TV應用的同學會發(fā)現(xiàn)它有焦點換行的功能蛤奥,就是當一直按遙控器右鍵時焦點View會換行到下一行的第一個View,如下
使用體驗很好僚稿,重要的是產(chǎn)品同學也覺得很好凡桥。
看到這個效果,我的第一直覺是給每個View設置Id蚀同,Id從左到右缅刽、從上到下增加,或者給每個View設置自增的Tag,每次都去尋找Id + 1或者Tag +1的View蠢络,然后讓它獲得焦點衰猛,這不就OK了。但這還是沒有優(yōu)雅的解決問題刹孔,只是把問題拋給了后臺啡省、運營,讓他們一個一個的設置運營位的編號。
那有沒有成本很小的解決方法呢卦睹?
getFocusedRect方法
上一篇的分析中我們知道當按鍵事件要轉(zhuǎn)換成焦點移動時畦戒,需要在眾多的View中尋找一個位置
最合適的View,然后讓它獲得焦點。而這個位置的依據(jù)是什么结序?其實就是每個View的getFocusedRect返回的Rect兢交,
對于當前有焦點的View,用這個返回的Rect計算它搜尋的起始位置笼痹;對于沒有焦點的View配喳,用
這個Rect來表示自己當前的位置。View的這個方法的代碼如下:
/**
* When a view has focus and the user navigates away from it, the next view is searched for
* starting from the rectangle filled in by this method.
*
* By default, the rectangle is the {@link #getDrawingRect(android.graphics.Rect)})
* of the view. However, if your view maintains some idea of internal selection,
* such as a cursor, or a selected row or column, you should override this method and
* fill in a more specific rectangle.
*
* @param r The rectangle to fill in, in this view's coordinates.
*/
public void getFocusedRect(Rect r) {
getDrawingRect(r);
}
實際上默認返回的就是View的繪制區(qū)域凳干,但注釋也說了可以根據(jù)實際的需要自己修改晴裹,那我就有一些想法了:可以根據(jù)View在不同的位置返回不同的Rect。
原理與實現(xiàn)
1.原理
- 對于有焦點的View救赐,如果它沒靠近屏幕的邊緣涧团,這個方法返回實際的繪制區(qū)域
- 對于有焦點的View,如果它靠近屏幕的右邊緣经磅,將這個Rect移動到下一行的左側(cè)屏幕的左外側(cè)泌绣,左邊緣的View同理
- 對于沒有焦點的View,這個方法返回實際的繪制區(qū)域
這樣當在右邊緣的View上按右鍵時预厌,實際上它的搜尋的起始位置在下一行的左外側(cè)阿迈,位置最合適的View就是下一行第一個View,這樣就可以花很小的代價解決這焦點換行的問題轧叽。
原理圖如下:
當View在屏幕右邊緣時苗沧,將Focus Rect的top設置為自己的botom的位置、Rect的right設置為0炭晒,實際的搜索起始Rect是黑色虛線的Rect(原理可看上篇的分析)待逞,這樣最合適的獲得焦點的View就是第二行的第一個View,達到了換行的目的
2.實現(xiàn)
重寫View的getFocusedRect方法如下:
@Override
public final void getFocusedRect(Rect r) {
//如果有焦點
if (isFocused()) {
//獲得根View的繪制區(qū)域
rootView.getDrawingRect(mRootViewRect);
//獲得自己的繪制區(qū)域
getDrawingRect(mTempDrawingRect);
//轉(zhuǎn)換自己的繪制區(qū)域到根View的坐標系中去网严,方便計算FocusedRect的位置
rootView.offsetDescendantRectToMyCoords(this, mTempDrawingRect);
//判斷自己是否滿足換行的條件识樱,1)按右鍵 2)自己在屏幕的右邊緣
if ((mTempDrawingRect.right + 20) > mRootViewRect.right && currentKeyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
//重新獲取自己的位置
getDrawingRect(mTempDrawingRect);
//轉(zhuǎn)換根View的坐標系中去
rootView.offsetDescendantRectToMyCoords(this, mTempDrawingRect);
//將這個Rect移動到自己的下方、左側(cè)屏幕的左外側(cè)
mTempDrawingRect.left = 0 - getMeasuredWidth();
mTempDrawingRect.right = 0;
mTempDrawingRect.bottom = mTempDrawingRect.bottom + getMeasuredHeight();
mTempDrawingRect.top = mTempDrawingRect.top + getMeasuredHeight();
//將focus rect的坐標系還原到自己的坐標系
rootView.offsetRectIntoDescendantCoords(this, mTempDrawingRect);
r.set(mTempDrawingRect);
} else if (mTempDrawingRect.left - 20 < mRootViewRect.left && currentKeyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
//左鍵同理
......
} else {//沒有在左右邊緣或者沒有按對應的左右鍵
super.getFocusedRect(r);
}
} else {//沒有焦點
super.getFocusedRect(r);
}
}
大概的原理都寫在了代碼注釋中了震束,完整的代碼實現(xiàn)見FocusLineFeedFrameLayout.java