該篇就看看按鍵焦點(diǎn)導(dǎo)航惹盼,從輸入事件流入到ViewRootImpl說(shuō)起
按鍵事件流入
按鍵觸摸事件都會(huì)封裝為InputEvent饮戳,然后會(huì)流轉(zhuǎn)到ViewRootImpl中ViewPostImeInputStage內(nèi)部類的onProcess方法進(jìn)行處理,具體的按鍵事件流程可以參考該篇Android8.0 按鍵事件處理流程
frameworks/base/core/java/android/view/ViewRootImpl.java
final class ViewPostImeInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
// 1. 該分支為處理按鍵事件
return processKeyEvent(q);
} else {
// else中處理觸摸事件
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
}
按鍵焦點(diǎn)導(dǎo)航當(dāng)然屬于按鍵事件(KeyEvent)灼伤,那KeyEvent的處理當(dāng)然是看processKeyEvent(q)方法了
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// Deliver the key to the view hierarchy.
// 注釋1
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
...
// 只按Tab鍵則焦點(diǎn)導(dǎo)航方向?yàn)橄蚯安绻蠯eyEvent.META_SHIFT_ON標(biāo)識(shí)符仰猖,則表示按下了shift鍵芬探,則導(dǎo)航方向?yàn)橄蚝? int groupNavigationDirection = 0;
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
groupNavigationDirection = View.FOCUS_FORWARD;
} else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
groupNavigationDirection = View.FOCUS_BACKWARD;
}
}
// 判斷是否作為快捷鍵
// If a modifier is held, try to interpret the key as a shortcut.
if (event.getAction() == KeyEvent.ACTION_DOWN
&& !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())
&& groupNavigationDirection == 0) {
if (mView.dispatchKeyShortcutEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
}
...
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (groupNavigationDirection != 0) {
if (performKeyboardGroupNavigation(groupNavigationDirection)) {
return FINISH_HANDLED;
}
} else {
// 注釋2
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
}
}
return FORWARD;
}
注釋1處神得,由View框架(Activity,ViewGroup&View)進(jìn)行按鍵事件處理,關(guān)于按鍵事件派發(fā)可參考Android8.0 按鍵事件處理流程。mView.dispatchKeyEvent偷仿, mView具體指的是哩簿? 如果當(dāng)前是Activity和Dialog,mView就是DecorView酝静,是View樹(shù)的根;如果是Toast,mView是id為com.android.internal.R.id.message节榜,下文只分析Activity。形入,
注釋2處全跨,如果View框架沒(méi)有處理按鍵事件,則繼續(xù)處理方向鍵焦點(diǎn)導(dǎo)航處理
這里我們就分析按方向鍵進(jìn)行焦點(diǎn)導(dǎo)航
方向鍵焦點(diǎn)導(dǎo)航流程
private boolean performFocusNavigation(KeyEvent event) {
int direction = 0;
// 指定焦點(diǎn)方向亿遂,根據(jù)按下的是上下左右還是Tab導(dǎo)航鍵決定方向
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (event.hasNoModifiers()) {
direction = View.FOCUS_LEFT;
}
break;
...
}
if (direction != 0) {
// 注釋1
View focused = mView.findFocus();
if (focused != null) {
// 注釋2
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
// do the math the get the interesting rect
// of previous focused into the coord system of
// newly focused view
focused.getFocusedRect(mTempRect);
if (mView instanceof ViewGroup) {
((ViewGroup) mView).offsetDescendantRectToMyCoords(
focused, mTempRect);
((ViewGroup) mView).offsetRectIntoDescendantCoords(
v, mTempRect);
}
// 注釋3
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
return true;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
return true;
}
} else {
if (mView.restoreDefaultFocus()) {
return true;
}
}
}
return false;
}
焦點(diǎn)導(dǎo)航整體流程就是注釋1,2,3
- 找到當(dāng)前擁有焦點(diǎn)的View
- 查找下個(gè)應(yīng)擁有焦點(diǎn)的View
- 目標(biāo)View請(qǐng)求焦點(diǎn)
下面對(duì)1浓若,2展開(kāi)分析
1. 找到當(dāng)前擁有焦點(diǎn)的View-->mView.findFocus()
mView即DecorView繼承自FrameLayout,沒(méi)復(fù)寫(xiě)findFocus方法蛇数,所以找到ViewGroup中的findFocus方法挪钓。
ViewGroup
@Override
public View findFocus() {
if (isFocused()) {
return this;
}
if (mFocused != null) {
return mFocused.findFocus();
}
return null;
}
邏輯很簡(jiǎn)單,如果當(dāng)前View擁有焦點(diǎn)直接返回自己,否則調(diào)用內(nèi)部間接持有focus的子View即mFocused耳舅,遍歷查找自己管轄范圍內(nèi)View知道找到擁有焦點(diǎn)的那個(gè)View碌上。
// The view contained within this ViewGroup that has or contains focus.
private View mFocused;
mFocused指向了擁有或者包含焦點(diǎn)的直接子View。
View的findFocus()方法
/**
* Find the view in the hierarchy rooted at this view that currently has
* focus.
*
* @return The view that currently has focus, or null if no focused view can
* be found.
*/
public View findFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
}
/**
* Returns true if this view has focus itself, or is the ancestor of the
* view that has focus.
*
* @return True if this view has or contains focus, false otherwise.
*/
@ViewDebug.ExportedProperty(category = "focus")
public boolean hasFocus() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0;
}
/**
* Returns true if this view has focus
*
* @return True if this view has focus, false otherwise.
*/
@ViewDebug.ExportedProperty(category = "focus")
public boolean isFocused() {
return (mPrivateFlags & PFLAG_FOCUSED) != 0;
}
View的hasFocus()方法和isFocused()方法對(duì)比
Stackoverflow解釋來(lái)了:
hasFocus() is different from isFocused(). hasFocus() == true means that the View or one of its descendants is focused. If you look closely, there's a chain of hasFocused Views till you reach the View that isFocused.
意思就是isFocused返回true表示該View當(dāng)前focused狀態(tài)為true,而hasFocus返回true表示的是該View或者層次結(jié)構(gòu)中子view的focused狀態(tài)為true浦徊。focused狀態(tài)可通過(guò)DDMS截取屏幕來(lái)查看屬性值馏予。
知道了意思也就知道了findFocus()方法返回的是一個(gè)具體的focused狀態(tài)為true的View
2. 查找下個(gè)要獲取焦點(diǎn)的View -->focused.focusSearch(direction)
focused可能是ViewGroup也可能是View
ViewGroup.java
@Override
public View focusSearch(View focused, int direction) {
if (isRootNamespace()) {
// root namespace means we should consider ourselves the top of the
// tree for focus searching; otherwise we could be focus searching
// into other tabs. see LocalActivityManager and TabHost for more info.
return FocusFinder.getInstance().findNextFocus(this, focused, direction);
} else if (mParent != null) {
return mParent.focusSearch(focused, direction);
}
return null;
}
View.java
public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
mParent是什么?mParent指的是View樹(shù)中父View盔性,對(duì)于普通View霞丧,mParent就是ViewGroup,這樣一級(jí)一級(jí)網(wǎng)上,最頂級(jí)就是DecorView冕香,而DecorView的mParent是ViewRootImpl蛹尝,具體流程不展開(kāi)分析后豫,可以自行搜索查看源碼。
因此focusSearch方法最終會(huì)調(diào)用到ViewGroup中的focusSearch方法中突那,直至isRootNamespace返回true,也就是當(dāng)前view為DecorView時(shí),會(huì)走FocusFinder.getInstance().findNextFocus(this, focused, direction)
挫酿。FocusFinder用于從當(dāng)前擁有焦點(diǎn)的View中找到給定方向上的下一個(gè)可獲取焦點(diǎn)的View。想想要在所有View樹(shù)中找到目標(biāo)View愕难,那肯定是從View樹(shù)的頂層View去入手早龟,是吧!
FocusFinder是查找焦點(diǎn)View的核心算法類
frameworks/base/core/java/android/view/FocusFinder.java
public final View findNextFocus(ViewGroup root, View focused, int direction) {
return findNextFocus(root, focused, null, direction);
}
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
if (focused != null) {
// 注釋1
next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
}
if (next != null) {
return next;
}
ArrayList<View> focusables = mTempList;
try {
focusables.clear();
// 注釋2
effectiveRoot.addFocusables(focusables, direction);
if (!focusables.isEmpty()) {
// 注釋3
next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
}
} finally {
focusables.clear();
}
return next;
}
注釋1是獲取開(kāi)發(fā)者指定的下一個(gè)獲取焦點(diǎn)的View务漩,xml中指定方式例如nextFocusDown="@id/next_id"
,java代碼指定view1.setNextFocusDownId(R.id.next_id);
,指定了按下鍵下個(gè)擁有焦點(diǎn)為id為next_id的view1拄衰,此時(shí)按下"下導(dǎo)航鍵"view1獲取焦點(diǎn)它褪。
注釋2會(huì)得到所有可獲取焦點(diǎn)View的集合饵骨。
注釋3會(huì)在可獲取焦點(diǎn)View集合中查找下一個(gè)該獲取焦點(diǎn)的View。
下面對(duì)這三個(gè)過(guò)程展開(kāi)分析
1. 查找開(kāi)發(fā)人員指定的下個(gè)獲取焦點(diǎn)的View
private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
// check for user specified next focus
// 注釋1
View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
View cycleCheck = userSetNextFocus;
boolean cycleStep = true; // we want the first toggle to yield false
// 注釋2
while (userSetNextFocus != null) {
if (userSetNextFocus.isFocusable()
&& userSetNextFocus.getVisibility() == View.VISIBLE
&& (!userSetNextFocus.isInTouchMode()
|| userSetNextFocus.isFocusableInTouchMode())) {
return userSetNextFocus;
}
userSetNextFocus = userSetNextFocus.findUserSetNextFocus(root, direction);
if (cycleStep = !cycleStep) {
cycleCheck = cycleCheck.findUserSetNextFocus(root, direction);
if (cycleCheck == userSetNextFocus) {
// found a cycle, user-specified focus forms a loop and none of the views
// are currently focusable.
break;
}
}
}
return null;
}
注釋1處findUserSetNextFocus方法會(huì)得到指定的下一個(gè)可獲取焦點(diǎn)的view
View findUserSetNextFocus(View root, @FocusDirection int direction) {
switch (direction) {
case FOCUS_LEFT:
// mNextFocusLeftId就是xml中nextFocusDown屬性指定的或者通過(guò)java代碼setNextFocusLeftId()設(shè)置的id
if (mNextFocusLeftId == View.NO_ID) return null;
return findViewInsideOutShouldExist(root, mNextFocusLeftId);
...
case FOCUS_FORWARD:
if (mNextFocusForwardId == View.NO_ID) return null;
return findViewInsideOutShouldExist(root, mNextFocusForwardId);
case FOCUS_BACKWARD: {
if (mID == View.NO_ID) return null;
final int id = mID;
return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
@Override
public boolean test(View t) {
return t.mNextFocusForwardId == id;
}
});
}
}
return null;
}
注釋2處循環(huán)去查找到一個(gè)真正下個(gè)可以獲取焦點(diǎn)的view茫打。因?yàn)橛脩粼O(shè)置的下個(gè)獲取焦點(diǎn)的View有可能處于不可見(jiàn)或者focusable屬性為false居触,此時(shí)其無(wú)法獲取焦點(diǎn),故會(huì)繼續(xù)查找下個(gè)指定的可獲取焦點(diǎn)的view老赤。
2. 得到所有可獲取焦點(diǎn)的View集合
effectiveRoot.addFocusables(focusables, direction);
先會(huì)調(diào)用到View中兩個(gè)參數(shù)方法轮洋,然后再根據(jù)多態(tài)來(lái)調(diào)用View或者ViewGroup中的三個(gè)參數(shù)的addFocusables()方法
View.java
public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
// 注釋1
addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
}
public void addFocusables(ArrayList<View> views, @FocusDirection int direction,
@FocusableMode int focusableMode) {
if (views == null) {
return;
}
if (!isFocusable()) {
return;
}
if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
&& !isFocusableInTouchMode()) {
return;
}
// 添加自己到可獲取焦點(diǎn)的集合中
views.add(this);
}
ViewGroup.java
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
final boolean blockFocusForTouchscreen = shouldBlockFocusForTouchscreen();
final boolean focusSelf = (isFocusableInTouchMode() || !blockFocusForTouchscreen);
// 如果是FOCUS_BLOCK_DESCENDANTS,調(diào)用View中addFocusables方法將自己添加到可獲取焦點(diǎn)的views集合中抬旺,然后返回弊予,不再添加view樹(shù)中該view的子view
if (descendantFocusability == FOCUS_BLOCK_DESCENDANTS) {
if (focusSelf) {
super.addFocusables(views, direction, focusableMode);
}
return;
}
if (blockFocusForTouchscreen) {
focusableMode |= FOCUSABLES_TOUCH_MODE;
}
// 如果是FOCUS_BEFORE_DESCENDANTS,此時(shí)先將自己加入views集合中开财,然后再將view樹(shù)中子view添加到views中汉柒。
if ((descendantFocusability == FOCUS_BEFORE_DESCENDANTS) && focusSelf) {
super.addFocusables(views, direction, focusableMode);
}
int count = 0;
final View[] children = new View[mChildrenCount];
for (int i = 0; i < mChildrenCount; ++i) {
View child = mChildren[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
children[count++] = child;
}
}
// view樹(shù)子view進(jìn)行一個(gè)排序
FocusFinder.sort(children, 0, count, this, isLayoutRtl());
for (int i = 0; i < count; ++i) {
children[i].addFocusables(views, direction, focusableMode);
}
// When set to FOCUS_AFTER_DESCENDANTS, we only add ourselves if
// there aren't any focusable descendants. this is
// to avoid the focus search finding layouts when a more precise search
// among the focusable children would be more interesting.
// 如果是FOCUS_AFTER_DESCENDANTS,則最后將自己加入到views中
if ((descendantFocusability == FOCUS_AFTER_DESCENDANTS) && focusSelf
&& focusableCount == views.size()) {
super.addFocusables(views, direction, focusableMode);
}
}
-
注釋1會(huì)判斷當(dāng)前狀態(tài)是否是觸摸模式责鳍,如果是TouchMode 則焦點(diǎn)模式為FOCUSABLES_TOUCH_MODE碾褂,否則 FOCUSABLES_ALL。這塊有必要說(shuō)明下TouchMode和這兩個(gè)屬性的意思历葛。
Touch Mode是在手指觸摸屏幕后而進(jìn)入的狀態(tài)正塌,一旦通過(guò)D-pad或者觸摸球進(jìn)行操作后就會(huì)退出Touch Mode
FOCUSABLES_ALL表示的所有可獲取焦點(diǎn)的View,F(xiàn)OCUSABLES_TOUCH_MODE表示的是在TouchMode下可獲取焦點(diǎn)的View,例如EditText或者某個(gè)控件指定了/** * View flag indicating whether {@link #addFocusables(ArrayList, int, int)} * should add all focusable Views regardless if they are focusable in touch mode. */ public static final int FOCUSABLES_ALL = 0x00000000; /** * View flag indicating whether {@link #addFocusables(ArrayList, int, int)} * should add only Views focusable in touch mode. */ public static final int FOCUSABLES_TOUCH_MODE = 0x00000001;
android:focusableInTouchMode="true"
addFocusables()三個(gè)參數(shù)的方法會(huì)根據(jù)多態(tài)調(diào)用
ViewGroup會(huì)根據(jù)descendantFocusability屬性來(lái)決定將自己先加入或者后加入或者不加入到可獲取焦點(diǎn)的集合中恤溶,因?yàn)镕ocusFinder總是去查找集合中首先可以獲取焦點(diǎn)的View乓诽。
3. 在可獲取焦點(diǎn)的集合中找到目標(biāo)View
next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
FocusFinder
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
int direction, ArrayList<View> focusables) {
if (focused != null) {
if (focusedRect == null) {
focusedRect = mFocusedRect;
}
// fill in interesting rect from focused
// 取得考慮scroll之后的焦點(diǎn)Rect,該Rect是相對(duì)focused視圖本身的
focused.getFocusedRect(focusedRect);
// 將當(dāng)前focused視圖的坐標(biāo)系咒程,轉(zhuǎn)換到root的坐標(biāo)系中鸠天,統(tǒng)一坐標(biāo),以便進(jìn)行下一步的計(jì)算
root.offsetDescendantRectToMyCoords(focused, focusedRect);
} else {
...
}
switch (direction) {
case View.FOCUS_FORWARD:
case View.FOCUS_BACKWARD:
return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
direction);
case View.FOCUS_UP:
case View.FOCUS_DOWN:
case View.FOCUS_LEFT:
case View.FOCUS_RIGHT:
// 注釋1
return findNextFocusInAbsoluteDirection(focusables, root, focused,
focusedRect, direction);
default:
throw new IllegalArgumentException("Unknown direction: " + direction);
}
}
注釋1根據(jù)導(dǎo)航方向孵坚,查找離自己最近的可獲取焦點(diǎn)View
View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
Rect focusedRect, int direction) {
// initialize the best candidate to something impossible
// (so the first plausible view will become the best choice)
mBestCandidateRect.set(focusedRect);
switch(direction) {
case View.FOCUS_LEFT:
mBestCandidateRect.offset(focusedRect.width() + 1, 0);
break;
case View.FOCUS_RIGHT:
mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
break;
case View.FOCUS_UP:
mBestCandidateRect.offset(0, focusedRect.height() + 1);
break;
case View.FOCUS_DOWN:
mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
}
View closest = null;
int numFocusables = focusables.size();
// 遍歷所有focusable的視圖
for (int i = 0; i < numFocusables; i++) {
View focusable = focusables.get(i);
// only interested in other non-root views
if (focusable == focused || focusable == root) continue;
// get focus bounds of other view in same coordinate system
// 取得focusable的rect
focusable.getFocusedRect(mOtherRect);
// 將該rect轉(zhuǎn)化到root的坐標(biāo)系中
root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
// 進(jìn)行比較粮宛,選出離當(dāng)前view最近的可獲取焦點(diǎn)的view
if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
mBestCandidateRect.set(mOtherRect);
closest = focusable;
}
}
return closest;
}
具體判斷是否符和要求是iCandidate()方法
boolean isCandidate(Rect srcRect, Rect destRect, int direction) {
switch (direction) {
case View.FOCUS_LEFT:
return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
&& srcRect.left > destRect.left;
case View.FOCUS_RIGHT:
return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
&& srcRect.right < destRect.right;
case View.FOCUS_UP:
return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
&& srcRect.top > destRect.top;
case View.FOCUS_DOWN:
return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
&& srcRect.bottom < destRect.bottom;
}
}
當(dāng)然這樣的焦點(diǎn)查找方式屬于普遍的查找理論窥淆,但是我們能否指定查找規(guī)則或者做一些客制化呢?答案是肯定的巍杈,見(jiàn)下文忧饭。
3. 目標(biāo)View請(qǐng)求焦點(diǎn)-->v.requestFocus()
v.requestFocus(),調(diào)用下個(gè)應(yīng)獲取焦點(diǎn)的View的requestFocus()是其獲取焦點(diǎn)并讓上個(gè)獲取焦點(diǎn)的View清除焦點(diǎn),最后回調(diào)onFocusChange()方法筷畦。
客制化焦點(diǎn)查找規(guī)則
- 通過(guò)nextFocusDown等屬性指定下個(gè)獲取焦點(diǎn)的View
- 重寫(xiě)focusSearch()方法词裤,RecyclerView就重寫(xiě)了該方法
- View中處理按鍵事件,則按鍵事件不會(huì)走到處理焦點(diǎn)流程中鳖宾。例如onKeyDown()方法中監(jiān)聽(tīng)導(dǎo)航鍵來(lái)指定焦點(diǎn)查找規(guī)則吼砂,例如ListView重寫(xiě)了onKeyDown(),并做了焦點(diǎn)查找的客制化鼎文。
總結(jié)
View框架不處理導(dǎo)航鍵的焦點(diǎn)查找trace
01-04 00:38:34.282 2077 2077 W System.err: java.lang.Exception: Stack trace
01-04 00:38:34.283 2077 2077 W System.err: at java.lang.Thread.dumpStack(Thread.java:1348)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.FocusFinder.findNextFocusInAbsoluteDirection(FocusFinder.java:340)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.FocusFinder.findNextFocus(FocusFinder.java:268)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.FocusFinder.findNextFocus(FocusFinder.java:110)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.FocusFinder.findNextFocus(FocusFinder.java:80)
01-04 00:38:34.283 2077 2077 W System.err: at android.support.v7.widget.RecyclerView.focusSearch(RecyclerView.java:2441)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.View.focusSearch(View.java:10194)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.performFocusNavigation(ViewRootImpl.java:4841)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:4966)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4782)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4318)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4371)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4337)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4464)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4345)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4521)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4318)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4371)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4337)
01-04 00:38:34.283 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4345)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4318)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4371)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4337)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4497)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:4664)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2435)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:1998)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:1989)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2412)
01-04 00:38:34.284 2077 2077 W System.err: at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141)
01-04 00:38:34.284 2077 2077 W System.err: at android.os.MessageQueue.nativePollOnce(Native Method)
01-04 00:38:34.286 2077 2077 W System.err: at android.os.MessageQueue.next(MessageQueue.java:325)
01-04 00:38:34.286 2077 2077 W System.err: at android.os.Looper.loop(Looper.java:142)
01-04 00:38:34.286 2077 2077 W System.err: at android.app.ActivityThread.main(ActivityThread.java:6523)
01-04 00:38:34.286 2077 2077 W System.err: at java.lang.reflect.Method.invoke(Native Method)
01-04 00:38:34.286 2077 2077 W System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
01-04 00:38:34.286 2077 2077 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:857)