問題表現(xiàn)
在開發(fā)中遇到一個偶現(xiàn)的崩潰,日志堆棧如下:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.edittextdemo, PID: 9528
java.lang.IllegalStateException: focus search returned a view that wasn't able to take focus!
at android.widget.TextView.onKeyUp(TextView.java:7591)
at android.view.KeyEvent.dispatch(KeyEvent.java:2788)
at android.view.View.dispatchKeyEvent(View.java:11780)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1845)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1845)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1845)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1845)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1845)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1845)
at com.android.internal.policy.DecorView.superDispatchKeyEvent(DecorView.java:547)
at com.android.internal.policy.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1884)
at android.app.Activity.dispatchKeyEvent(Activity.java:3430)
at android.support.v7.app.AppCompatActivity.dispatchKeyEvent(AppCompatActivity.java:534)
at android.support.v7.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:58)
at android.support.v7.app.AppCompatDelegateImplBase$AppCompatWindowCallbackBase.dispatchKeyEvent(AppCompatDelegateImplBase.java:316)
at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:421)
at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5371)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5243)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4737)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4790)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4756)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4887)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4764)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4944)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4737)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4790)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4756)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4764)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4737)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7363)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7337)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7295)
at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:4315)
at android.os.Handler.dispatchMessage(Handler.java:109)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7555)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:469)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963)
復(fù)現(xiàn)路徑
解決任何缺陷,不僅僅是崩潰問題芋类,解決問題的第一步都是找到復(fù)現(xiàn)路徑。如果找到了穩(wěn)定復(fù)現(xiàn)的路徑界阁,那么恭喜你:已經(jīng)成功了一半了侯繁!
對于本文中的崩潰問題,經(jīng)過一層一層的抽絲剝繭泡躯,逐步剔除無關(guān)代碼贮竟,終于在 Demo 工程中找到了穩(wěn)定復(fù)現(xiàn)的路徑丽焊。
Demo 工程是個列表頁,用 RecyclerView 實現(xiàn)坝锰,列表中有 3 種類型的 Item:TextView粹懒、僅支持?jǐn)?shù)字類型輸入的 EditText 和 CheckBox。
當(dāng)點擊輸入框后直接點擊軟鍵盤的回車按鈕顷级,必崩凫乖。完整代碼和效果錄屏分別如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RecyclerView rv = new RecyclerView(this);
LinearLayoutManager lm = new LinearLayoutManager(this);
rv.setLayoutManager(lm);
rv.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
DividerItemDecoration did = new DividerItemDecoration(this, lm.getOrientation());
rv.addItemDecoration(did);
ArrayList<Integer> data = new ArrayList<Integer>(10);
for (int i = 0; i < 13; i++) {
data.add(i);
}
rv.setAdapter(new MyAdapter(data));
setContentView(rv);
}
class MyAdapter extends RecyclerView.Adapter {
ArrayList<Integer> data;
int viewTypeTextView, viewTypeEditText, viewTypeCheckbox;
public MyAdapter(ArrayList<Integer> data) {
this.data = data;
viewTypeEditText = this.data.size() - 2;
viewTypeCheckbox = this.data.size() - 1;
}
@Override
public int getItemViewType(int position) {
if (position == data.size() - 2) {
return viewTypeEditText;
} else if (position == data.size() - 1) {
return viewTypeCheckbox;
} else {
return viewTypeTextView;
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
View view;
if (viewType == viewTypeEditText) {
view = buildEditText(viewGroup.getContext());
} else if (viewTypeCheckbox == viewType) {
view = buildCheckbox(viewGroup.getContext());
} else {
view = buildTextView(viewGroup.getContext());
}
return new MyHolder(view);
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, int position) {
}
@Override
public int getItemCount() {
return data.size();
}
private View buildTextView(Context context) {
TextView textView = new TextView(context);
textView.setText("文本項");
textView.setTextSize(20);
textView.setGravity(Gravity.CENTER);
textView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
textView.setPadding(0, 50, 0, 50);
return textView;
}
private View buildEditText(Context context) {
EditText editText = new EditText(context);
editText.setHint("輸入框");
editText.setInputType(EditorInfo.TYPE_CLASS_NUMBER);
editText.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return editText;
}
private View buildCheckbox(Context context) {
CheckBox checkBox = new CheckBox(context);
checkBox.setText("單選框");
return checkBox;
}
class MyHolder extends RecyclerView.ViewHolder {
public MyHolder(@NonNull View itemView) {
super(itemView);
}
}
}
}
)
奇怪的是,同樣的代碼弓颈,如果把文本控件的個數(shù)減少到 1 個帽芽,則不再出現(xiàn)崩潰,即將上面的代碼僅僅做如下修改:
ArrayList<Integer> data = new ArrayList<Integer>(10);
for (int i = 0; i < 1; i++) {
data.add(i);
}
對比之后翔冀,不免讓人猜測:難道是否崩潰取決于單選框是否顯示在屏幕中导街?即,當(dāng)單選框出現(xiàn)在屏幕中時纤子,不會崩潰搬瑰?;否則會崩潰控硼?我們來驗證一下:在文本控件個數(shù)為13的場景下泽论,滑動頁面,將單選框展示在屏幕中卡乾,果然不會崩潰:
這是一個神奇的問題翼悴,激起了我征服她的欲望。
問題原因
我們先看下這個異常是從什么地方拋出來的幔妨。直接在源碼中全局搜索異常關(guān)鍵字 focus search returned
鹦赎,發(fā)現(xiàn)總共有3處,全部集中在 TextView.java 中误堡。
前兩處在 onEditorAction()
中:
public void onEditorAction(int actionCode) {
// omitted
if (actionCode == EditorInfo.IME_ACTION_NEXT) {
View v = focusSearch(FOCUS_FORWARD);
if (v != null) {
if (!v.requestFocus(FOCUS_FORWARD)) {
throw new IllegalStateException("focus search returned a view "
+ "that wasn't able to take focus!");
}
}
return;
} else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
View v = focusSearch(FOCUS_BACKWARD);
if (v != null) {
if (!v.requestFocus(FOCUS_BACKWARD)) {
throw new IllegalStateException("focus search returned a view "
+ "that wasn't able to take focus!");
}
}
return;
}
// omitted
}
最后一處在 onKeyUp()
中:
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// omitted
if (!hasOnClickListeners()) {
View v = focusSearch(FOCUS_DOWN);
if (v != null) {
if (!v.requestFocus(FOCUS_DOWN)) {
throw new IllegalStateException("focus search returned a view "
+ "that wasn't able to take focus!");
}
/*
* Return true because we handled the key; super
* will return false because there was no click
* listener.
*/
super.onKeyUp(keyCode, event);
return true;
}
}
// omitted
}
根據(jù)堆棧日志古话,我們可以看出實際拋出異常的地方是 onKeyUp()
這個方法。
僅僅從代碼中我們可以看到锁施,拋出該異常的前提是 !hasOnClickListeners() = true
煞额,最簡單粗暴的解決該崩潰的方法就是給 EditText 設(shè)置一個 OnClickListener,讓代碼運行不到拋出異常的地方沾谜,也就不會崩潰了,雖然不優(yōu)雅胀莹,但是有效基跑,可以應(yīng)急。
我們都是有追求的程序員描焰,不會這么淺嘗輒止媳否、得過且過栅螟,接著往下看。關(guān)鍵代碼在于如下幾行:
View v = focusSearch(FOCUS_DOWN);
if (v != null) {
if (!v.requestFocus(FOCUS_DOWN)) {
throw new IllegalStateException("focus search returned a view "
+ "that wasn't able to take focus!");
}
}
看過這幾行代碼篱竭,我們會心生疑問:
-
v
是否為空力图?如果不為空,是哪個控件掺逼? - 如果
v
不為空吃媒,v.requestFocus(FOCUS_DOWN)
為什么失敗吕喘?
帶著這2個問題赘那,我們看下2個方法對應(yīng)的代碼。僅僅是看系統(tǒng)源代碼氯质,效率有點低募舟。我們可以結(jié)合穩(wěn)定復(fù)現(xiàn)的路徑,進行單步調(diào)試闻察,這樣效率更高些拱礁。根據(jù)斷點調(diào)試(關(guān)于如何對系統(tǒng)源代碼進行斷點調(diào)試,參見筆者的另一篇博客《淺嘗安卓事件分發(fā)機制》)辕漂,在不發(fā)生崩潰的路徑上呢灶,v = null
;在發(fā)生崩潰的路徑上钮热,v
是輸入框下面的單選框填抬,此時 v.requestFocus(FOCUS_DOWN)=false
,正是造成崩潰的原因隧期。
我們先不看 focusSearch()
飒责,這牽涉到焦點搜索策略,內(nèi)容較多仆潮。我們先看 View#requestFocus()
宏蛉。從 《TouchMode 101》 我們知道,除了 EditText性置,其他控件拾并,包括單選框控件,其 isFocusableInTouchMode()
都返回 false鹏浅。View#requestFocus()
內(nèi)部實際調(diào)用的方法是:
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
if ((mViewFlags & FOCUSABLE) != FOCUSABLE
|| (mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
// need to be focusable in touch mode if in touch mode
if (isInTouchMode() &&
(FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) {
return false;
}
// need to not have any parents blocking us
if (hasAncestorThatBlocksDescendantFocus()) {
return false;
}
handleFocusGainInternal(direction, previouslyFocusedRect);
return true;
}
注意其中的第2個 if 語句嗅义,由于單選框在 TouchMode 下 focusable = false,于是代碼運行到這個 if 語句就直接返回 false 了隐砸,導(dǎo)致在 TextView#onKeyUp()
方法中拋出異常之碗。既然如此,我們?nèi)绻麖娦邪褑芜x框的在 TouchMode 模式下設(shè)置為 focusable 的季希,是不是就解決該問題了褪那?我們來驗證下幽纷。在 Demo 工程中添加1行代碼,做如下修改:
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
View view;
if (viewType == viewTypeEditText) {
view = buildEditText(viewGroup.getContext());
} else if (viewTypeCheckbox == viewType) {
view = buildCheckbox(viewGroup.getContext());
// 設(shè)置單選框在 TouchMode 模式下是 focusable 的
view.setFocusableInTouchMode(true);
} else {
view = buildTextView(viewGroup.getContext());
}
return new MyHolder(view);
}
在13個文本項且不滑動單選框到屏幕中的場景下博敬,測試效果:
而且看起來并沒有啥副作用友浸。
回過頭來看 View#focusSearch()
:
View v = focusSearch(FOCUS_DOWN);
if (v != null) {
if (!v.requestFocus(FOCUS_DOWN)) {
throw new IllegalStateException("focus search returned a view "
+ "that wasn't able to take focus!");
}
}
為什么單選框顯示在屏幕上時 v = null
而其不顯示在屏幕上時則指向單選框?focusSearch()
在 View偏窝、ViewGroup收恢、RecyclerView 中分別有各自的實現(xiàn)方式。
在 View#focusSearch()
中囚枪,其實是調(diào)用的 ViewGroup 或者 RecyclerView 的 focusSearch()
:
public View focusSearch(@FocusRealDirection int direction) {
if (mParent != null) {
return mParent.focusSearch(this, direction);
} else {
return null;
}
}
而 ViewGroup#focusSearch()
看起來也比較簡單派诬,功能是一路類遞歸調(diào)用知道尋找到頂層的根控件,將根控件作為參數(shù)链沼,調(diào)用 FocusFinder.getInstance().findNextFocus(this, focused, direction)
:
/**
* Find the nearest view in the specified direction that wants to take
* focus.
*
* @param focused The view that currently has focus
* @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
* FOCUS_RIGHT, or 0 for not applicable.
*/
@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;
}
FocusFinder.getInstance().findNextFocus(this, focused, direction)
是 ViewGroup 中焦點搜索策略的核心代碼默赂,內(nèi)容較多,暫且不展開講述括勺。
RecyclerView#focusSearch()
更為復(fù)雜:
/**
* Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
* in the Adapter but not visible in the UI), it employs a more involved focus search strategy
* that differs from other ViewGroups.
* <p>
* It first does a focus search within the RecyclerView. If this search finds a View that is in
* the focus direction with respect to the currently focused View, RecyclerView returns that
* child as the next focus target. When it cannot find such child, it calls
* {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views
* in the focus search direction. If LayoutManager adds a View that matches the
* focus search criteria, it will be returned as the focus search result. Otherwise,
* RecyclerView will call parent to handle the focus search like a regular ViewGroup.
* <p>
* When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that
* is not in the focus direction is still valid focus target which may not be the desired
* behavior if the Adapter has more children in the focus direction. To handle this case,
* RecyclerView converts the focus direction to an absolute direction and makes a preliminary
* focus search in that direction. If there are no Views to gain focus, it will call
* {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a
* focus search with the original (relative) direction. This allows RecyclerView to provide
* better candidates to the focus search while still allowing the view system to take focus from
* the RecyclerView and give it to a more suitable child if such child exists.
*
* @param focused The view that currently has focus
* @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
* {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD},
* {@link View#FOCUS_BACKWARD} or 0 for not applicable.
*
* @return A new View that can be the next focus after the focused View
*/
@Override
public View focusSearch(View focused, int direction) {
View result = mLayout.onInterceptFocusSearch(focused, direction);
if (result != null) {
return result;
}
final boolean canRunFocusFailure = mAdapter != null && mLayout != null
&& !isComputingLayout() && !mLayoutFrozen;
final FocusFinder ff = FocusFinder.getInstance();
if (canRunFocusFailure
&& (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) {
// convert direction to absolute direction and see if we have a view there and if not
// tell LayoutManager to add if it can.
boolean needsFocusFailureLayout = false;
if (mLayout.canScrollVertically()) {
final int absDir =
direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
final View found = ff.findNextFocus(this, focused, absDir);
needsFocusFailureLayout = found == null;
if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
// Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
direction = absDir;
}
}
if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) {
boolean rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
? View.FOCUS_RIGHT : View.FOCUS_LEFT;
final View found = ff.findNextFocus(this, focused, absDir);
needsFocusFailureLayout = found == null;
if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) {
// Workaround for broken FOCUS_BACKWARD in API 15 and older devices.
direction = absDir;
}
}
if (needsFocusFailureLayout) {
consumePendingUpdateOperations();
final View focusedItemView = findContainingItemView(focused);
if (focusedItemView == null) {
// panic, focused view is not a child anymore, cannot call super.
return null;
}
eatRequestLayout();
mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
resumeRequestLayout(false);
}
result = ff.findNextFocus(this, focused, direction);
} else {
result = ff.findNextFocus(this, focused, direction);
if (result == null && canRunFocusFailure) {
consumePendingUpdateOperations();
final View focusedItemView = findContainingItemView(focused);
if (focusedItemView == null) {
// panic, focused view is not a child anymore, cannot call super.
return null;
}
eatRequestLayout();
result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
resumeRequestLayout(false);
}
}
if (result != null && !result.hasFocusable()) {
if (getFocusedChild() == null) {
// Scrolling to this unfocusable view is not meaningful since there is no currently
// focused view which RV needs to keep visible.
return super.focusSearch(focused, direction);
}
// If the next view returned by onFocusSearchFailed in layout manager has no focusable
// views, we still scroll to that view in order to make it visible on the screen.
// If it's focusable, framework already calls RV's requestChildFocus which handles
// bringing this newly focused item onto the screen.
requestChildOnScreen(result, null);
return focused;
}
return isPreferredNextFocus(focused, result, direction)
? result : super.focusSearch(focused, direction);
}
在崩潰的路徑中缆八,相關(guān)代碼為:
/**
* Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
* in the Adapter but not visible in the UI), it employs a more involved focus search strategy
* that differs from other ViewGroups.
* <p>
* It first does a focus search within the RecyclerView. If this search finds a View that is in
* the focus direction with respect to the currently focused View, RecyclerView returns that
* child as the next focus target. When it cannot find such child, it calls
* {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views
* in the focus search direction. If LayoutManager adds a View that matches the
* focus search criteria, it will be returned as the focus search result. Otherwise,
* RecyclerView will call parent to handle the focus search like a regular ViewGroup.
* <p>
* When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that
* is not in the focus direction is still valid focus target which may not be the desired
* behavior if the Adapter has more children in the focus direction. To handle this case,
* RecyclerView converts the focus direction to an absolute direction and makes a preliminary
* focus search in that direction. If there are no Views to gain focus, it will call
* {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a
* focus search with the original (relative) direction. This allows RecyclerView to provide
* better candidates to the focus search while still allowing the view system to take focus from
* the RecyclerView and give it to a more suitable child if such child exists.
*
* @param focused The view that currently has focus
* @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
* {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD},
* {@link View#FOCUS_BACKWARD} or 0 for not applicable.
*
* @return A new View that can be the next focus after the focused View
*/
@Override
public View focusSearch(View focused, int direction) {
// omitted
if (canRunFocusFailure
&& (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) {
// omitted
} else {
// omitted
// 關(guān)鍵代碼
result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
// omitted
}
// omitted
}
關(guān)鍵在于 result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
一行,這里的 mLayout 是 LinearLayoutManager疾捍,我們看 LinearLayoutManager#onFocusSearchFailed()
的代碼實現(xiàn):
@Override
public View onFocusSearchFailed(View focused, int focusDirection,
RecyclerView.Recycler recycler, RecyclerView.State state) {
resolveShouldLayoutReverse();
if (getChildCount() == 0) {
return null;
}
final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
if (layoutDir == LayoutState.INVALID_LAYOUT) {
return null;
}
ensureLayoutState();
ensureLayoutState();
final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace());
updateLayoutState(layoutDir, maxScroll, false, state);
mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
mLayoutState.mRecycle = false;
fill(recycler, mLayoutState, state, true);
// nextCandidate is the first child view in the layout direction that's partially
// within RV's bounds, i.e. part of it is visible or it's completely invisible but still
// touching RV's bounds. This will be the unfocusable candidate view to become visible onto
// the screen if no focusable views are found in the given layout direction.
final View nextCandidate;
if (layoutDir == LayoutState.LAYOUT_START) {
nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToStart(recycler, state);
} else {
nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToEnd(recycler, state);
}
// nextFocus is meaningful only if it refers to a focusable child, in which case it
// indicates the next view to gain focus.
final View nextFocus;
if (layoutDir == LayoutState.LAYOUT_START) {
nextFocus = getChildClosestToStart();
} else {
nextFocus = getChildClosestToEnd();
}
if (nextFocus.hasFocusable()) {
if (nextCandidate == null) {
return null;
}
return nextFocus;
}
return nextCandidate;
}
在崩潰的路徑上奈辰,我們單步調(diào)試會發(fā)現(xiàn),nextCandidate 是輸入框控件乱豆,而 nextFocus 是單選框控件奖恰,于是該方法就返回了單選框控件。
解決方案
- 為輸入框控件設(shè)置空的 OnClickListener宛裕;
- 將單選框控件設(shè)置為在 TouchMode 模式下 focusable 為 true瑟啃;
- 將輸入框控件的 imeOptions 設(shè)置為 actionDone/actionNext/actionUnspecified 的其中之一;
- 給輸入框控件設(shè)置一個空的 OnEditorActionListener揩尸,其中方法直接返回 true 即可蛹屿;