由FocusChange引发的问题手札
2016-11-20 14:57
106 查看
这次碰见了Fragment.replace后发生的focus改变问题。
这个问题是用户用蓝牙键盘的Enter键点击了某个tabContent的内容,该内容由ListFragment来展现,切换的方式是ft.replace。所以发生了显示异常问题(显示的不是所点item对应的内容,而是tab0的内容)。
FragmentTransaction的replace最终会引起 removeFragment,而removeFragment就会进而removeFocus。
下面我们针对Stack来细看代码流程:
从Fragment的removeFragment开始看吧。
一直走到 moveToState中的 Fragment.ACTIVITY_CREATED,这里的switch case全然不用break和return,很奇怪
进入ViewGroup removeView
ViewGroup继承自View,实现了ViewParent, ViewManager接口,这里是进入View的rootViewRequestFocus
requestFocus的注释还是很值得看的
我一直以为马上调用的还是View的requestFocus(int direction, Rect previouslyFocusedRect),然而它调用的却是ViewGroup的。这是因为Java是一门面向对象语言,当子类对象调用的方法在子类中被复写时,那么应该调用的是子类的对象,也就是说,马上调用的是
at android.view.ViewGroup.requestFocus(ViewGroup.java:3057)
private static final int FLAG_MASK_FOCUSABILITY = 0x60000;
ViewDebug的注解是用在显示于Hierarchy View中的,咱们继续看,为什么这个里面走的是FOCUS_AFTER_DESCENDANTS,呃,mGroupFlags我们还是暂时保留吧,不过从initViewGroup()中来看setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS)默认是FOCUS_BEFORE_DESCENDANTS。
后面是一个递归找焦点的过程。
public static final int FOCUS_FORWARD = 0x00000002;
根据direction来决定index从0还是从count-1开始。
咱们看log的时候requestFocus有时候是3054行,有时候是3057行,其差别在于FOCUS_AFTER_DESCENDANTS和FOCUS_BEFORE_DESCENDANTS。就这么递归直到HorizontalScrollView.java
这里走回View(nextFocus)的requestFocus,也就是requestFocusNoSearch。
先是TextView的onFocusChange
调了
bfc6
父类View的onFocusChanged
我们看到log里会打出focusIn 和focusOut的信息,其实就是这里调用的IMM的方法。然后是通知onFocusChangeListener的监听者。而TabWidget就是其中一个。
TabWidget在onFocusChange里面干了比较重要的事setCurrentTab,也就是这个问题发生的主要原因。在挨个询问焦点的过程中setCurrentTab为0了,所以回调onTabSelectionChanged。
TabHost onTabSelectionChanged
setCurrentTab中会调用invokeOnTabChangeListener,最终反馈到Activity
最后的解决方案是在ListFragment中加了callback,当tab列表中的一项被点击的时候(onListItemClick)去回调其onFocusChange通知监听者(TabActivity)这个时候短暂地将tab设为focusable false,避开这个获取焦点环节。
这个问题是用户用蓝牙键盘的Enter键点击了某个tabContent的内容,该内容由ListFragment来展现,切换的方式是ft.replace。所以发生了显示异常问题(显示的不是所点item对应的内容,而是tab0的内容)。
Fragment fg = AListFragment.getNewInstance(subList, mListMode, mHeaderMode, key, mPlaylistId); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(android.R.id.tabcontent, fg, Integer.toString(subList)); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); ft.addToBackStack(Integer.toString(subList)); ft.commit();
FragmentTransaction的replace最终会引起 removeFragment,而removeFragment就会进而removeFocus。
onTabChanged: tabId= 131073 java.lang.RuntimeException at com.samsung.musicplus.contents.extra.MusicSelectListTabActivity.onTabChanged(MusicSelectListTabActivity.java:236) at android.widget.TabHost.invokeOnTabChangeListener(TabHost.java:462) at android.widget.TabHost.setCurrentTab(TabHost.java:442) at android.widget.TabHost$2.onTabSelectionChanged(TabHost.java:194) at android.widget.TabWidget.onFocusChange(TabWidget.java:683) at android.view.View.onFocusChanged(View.java:6278) at android.widget.TextView.onFocusChanged(TextView.java:9559) at android.view.View.handleFocusGainInternal(View.java:6004) at android.view.View.requestFocusNoSearch(View.java:9077) at android.view.View.requestFocus(View.java:9056) at android.widget.HorizontalScrollView.onRequestFocusInDescendants(HorizontalScrollView.java:2056) at android.view.ViewGroup.requestFocus(ViewGroup.java:3057) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3054) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3057) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3054) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3054) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3054) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3057) at android.view.View.requestFocus(View.java:9023) at android.view.View.requestFocus(View.java:9002) at android.view.View.rootViewRequestFocus(View.java:6177) at android.view.ViewGroup.removeViewInternal(ViewGroup.java:4968) at android.view.ViewGroup.removeViewInternal(ViewGroup.java:4928) at android.view.ViewGroup.removeView(ViewGroup.java:4859) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1084) at android.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1275) at android.app.BackStackRecord.run(BackStackRecord.java:744) at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1557) at android.app.FragmentManagerImpl$1.run(FragmentManager.java:488) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:7331) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
下面我们针对Stack来细看代码流程:
从Fragment的removeFragment开始看吧。
public void removeFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); final boolean inactive = !fragment.isInBackStack(); if (!fragment.mDetached || inactive) { if (false) { // Would be nice to catch a bad remove here, but we need // time to test this to make sure we aren't crashes cases // where it is not a problem. if (!mAdded.contains(fragment)) { throw new IllegalStateException("Fragment not added: " + fragment); } } if (mAdded != null) { mAdded.remove(fragment); } if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.mAdded = false; fragment.mRemoving = true; moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED, transition, transitionStyle, false); } }
一直走到 moveToState中的 Fragment.ACTIVITY_CREATED,这里的switch case全然不用break和return,很奇怪
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { if (f.mState < newState) { switch (f.mState) { case Fragment.INITIALIZING: case Fragment.CREATED: case Fragment.ACTIVITY_CREATED: case Fragment.STOPPED: case Fragment.STARTED: } } else if (f.mState > newState) { switch (f.mState) { case Fragment.RESUMED: case Fragment.STARTED: case Fragment.STOPPED: case Fragment.ACTIVITY_CREATED: if (newState < Fragment.ACTIVITY_CREATED) { if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f); if (f.mView != null) { // Need to save the current view state if not // done already. if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) { saveFragmentViewState(f); } } f.performDestroyView(); if (f.mView != null && f.mContainer != null) { Animator anim = null; if (mCurState > Fragment.INITIALIZING && !mDestroyed) { anim = loadAnimator(f, transit, false, transitionStyle); } if (anim != null) { final ViewGroup container = f.mContainer; final View view = f.mView; final Fragment fragment = f; container.startViewTransition(view); f.mAnimatingAway = anim; f.mStateAfterAnimating = newState; anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator anim) { container.endViewTransition(view); if (fragment.mAnimatingAway != null) { fragment.mAnimatingAway = null; moveToState(fragment, fragment.mStateAfterAnimating, 0, 0, false); } } }); 4000 anim.setTarget(f.mView); setHWLayerAnimListenerIfAlpha(f.mView, anim); anim.start(); } f.mContainer.removeView(f.mView); //1084 } f.mContainer = null; f.mView = null; } case Fragment.CREATED: } } f.mState = newState; }
进入ViewGroup removeView
public void removeView(View view) { if (removeViewInternal(view)) { requestLayout(); invalidate(true); } } private boolean removeViewInternal(View view) { final int index = indexOfChild(view); if (index >= 0) { removeViewInternal(index, view); //4928 return true; } return false; } private void removeViewInternal(int index, View view) { if (mTransition != null) { mTransition.removeChild(this, view); } boolean clearChildFocus = false; if (view == mFocused) { view.unFocus(null); clearChildFocus = true; } view.clearAccessibilityFocus(); cancelTouchTarget(view); cancelHoverTarget(view); if (view.getAnimation() != null || (mTransitioningViews != null && mTransitioningViews.contains(view))) { addDisappearingView(view); } else if (view.mAttachInfo != null) { view.dispatchDetachedFromWindow(); } if (view.hasTransientState()) { childHasTransientStateChanged(view, false); } needGlobalAttributesUpdate(false); removeFromArray(index); if (clearChildFocus) { clearChildFocus(view); if (!rootViewRequestFocus()) { //4968 notifyGlobalFocusCleared(this); } } dispatchViewRemoved(view); if (view.getVisibility() != View.GONE) { notifySubtreeAccessibilityStateChangedIfNeeded(); } int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); for (int i = 0; i < transientCount; ++i) { final int oldIndex = mTransientIndices.get(i); if (index < oldIndex) { mTransientIndices.set(i, oldIndex - 1); } } }
ViewGroup继承自View,实现了ViewParent, ViewManager接口,这里是进入View的rootViewRequestFocus
boolean rootViewRequestFocus() { final View root = getRootView(); return root != null && root.requestFocus(); }
requestFocus的注释还是很值得看的
/** * Call this to try to give focus to a specific view or to one of its * descendants. * * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns * false), or if it is focusable and it is not focusable in touch mode * ({@link #isFocusableInTouchMode}) while the device is in touch mode. * * See also {@link #focusSearch(int)}, which is what you call to say that you * have focus, and you want your parent to look for the next one. * * This is equivalent to calling {@link #requestFocus(int, Rect)} with arguments * {@link #FOCUS_DOWN} and <code>null</code>. * * @return Whether this view or one of its descendants actually took focus. */ public final boolean requestFocus() { return requestFocus(View.FOCUS_DOWN); } public final boolean requestFocus(int direction) { return requestFocus(direction, null); } /** * Call this to try to give focus to a specific view or to one of its descendants * and give it hints about the direction and a specific rectangle that the focus * is coming from. The rectangle can help give larger views a finer grained hint * about where focus is coming from, and therefore, where to show selection, or * forward focus change internally. * * A view will not actually take focus if it is not focusable ({@link #isFocusable} returns * false), or if it is focusable and it is not focusable in touch mode * ({@link #isFocusableInTouchMode}) while the device is in touch mode. * * A View will not take focus if it is not visible. * * A View will not take focus if one of its parents has * {@link android.view.ViewGroup#getDescendantFocusability()} equal to * {@link ViewGroup#FOCUS_BLOCK_DESCENDANTS}. * * See also {@link #focusSearch(int)}, which is what you call to say that you * have focus, and you want your parent to look for the next one. * * You may wish to override this method if your custom {@link View} has an internal * {@link View} that it wishes to forward the request to. * * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT * @param previouslyFocusedRect The rectangle (in this View's coordinate system) * to give a finer grained hint about where focus is coming from. May be null * if there is no hint. * @return Whether this view or one of its descendants actually took focus. */ public boolean requestFocus(int direction, Rect previouslyFocusedRect) { return requestFocusNoSearch(direction, previouslyFocusedRect); }
我一直以为马上调用的还是View的requestFocus(int direction, Rect previouslyFocusedRect),然而它调用的却是ViewGroup的。这是因为Java是一门面向对象语言,当子类对象调用的方法在子类中被复写时,那么应该调用的是子类的对象,也就是说,马上调用的是
at android.view.ViewGroup.requestFocus(ViewGroup.java:3057)
/** * {@inheritDoc} * * Looks for a view to give focus to respecting the setting specified by * {@link #getDescendantFocusability()}. * * Uses {@link #onRequestFocusInDescendants(int, android.graphics.Rect)} to * find focus within the children of this group when appropriate. * * @see #FOCUS_BEFORE_DESCENDANTS * @see #FOCUS_AFTER_DESCENDANTS * @see #FOCUS_BLOCK_DESCENDANTS * @see #onRequestFocusInDescendants(int, android.graphics.Rect) */ @Override public boolean requestFocus(int direction, Rect previouslyFocusedRect) { if (DBG) { System.out.println(this + " ViewGroup.requestFocus direction=" + direction); } int descendantFocusability = getDescendantFocusability(); switch (descendantFocusability) { case FOCUS_BLOCK_DESCENDANTS: return super.requestFocus(direction, previouslyFocusedRect); case FOCUS_BEFORE_DESCENDANTS: { final boolean took = super.requestFocus(direction, previouslyFocusedRect); return took ? took : //3054 onRequestFocusInDescendants(direction, previouslyFocusedRect); } case FOCUS_AFTER_DESCENDANTS: { final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect); // 3057 return took ? took : super.requestFocus(direction, previouslyFocusedRect); } default: throw new IllegalStateException("descendant focusability must be " + "one of FOCUS_BEFORE_DESCENDANTS, FOCUS_AFTER_DESCENDANTS, FOCUS_BLOCK_DESCENDANTS " + "but is " + descendantFocusability); } } /** * Gets the descendant focusability of this view group. The descendant * focusability defines the relationship between this view group and its * descendants when looking for a view to take focus in * {@link #requestFocus(int, android.graphics.Rect)}. * * @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS}, * {@link #FOCUS_BLOCK_DESCENDANTS}. */ @ViewDebug.ExportedProperty(category = "focus", mapping = { @ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"), @ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"), @ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS") }) public int getDescendantFocusability() { return mGroupFlags & FLAG_MASK_FOCUSABILITY; }
private static final int FLAG_MASK_FOCUSABILITY = 0x60000;
/** * This view will get focus before any of its descendants. */ public static final int FOCUS_BEFORE_DESCENDANTS = 0x20000; /** * This view will get focus only if none of its descendants want it. */ public static final int FOCUS_AFTER_DESCENDANTS = 0x40000; /** * This view will block any of its descendants from getting focus, even * if they are focusable. */ public static final int FOCUS_BLOCK_DESCENDANTS = 0x60000;
ViewDebug的注解是用在显示于Hierarchy View中的,咱们继续看,为什么这个里面走的是FOCUS_AFTER_DESCENDANTS,呃,mGroupFlags我们还是暂时保留吧,不过从initViewGroup()中来看setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS)默认是FOCUS_BEFORE_DESCENDANTS。
at android.view.ViewGroup.requestFocus(ViewGroup.java:3057) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3054) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3057) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3054) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3054) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098) at android.view.ViewGroup.requestFocus(ViewGroup.java:3054) at android.view.ViewGroup.onRequestFocusInDescendants(ViewGroup.java:3098)
后面是一个递归找焦点的过程。
/** * Look for a descendant to call {@link View#requestFocus} on. * Called by {@link ViewGroup#requestFocus(int, android.graphics.Rect)} * when it wants to request focus within its children. Override this to * customize how your {@link ViewGroup} requests focus within its children. * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and FOCUS_RIGHT * @param previouslyFocusedRect The rectangle (in this View's coordinate system) * to give a finer grained hint about where focus is coming from. May be null * if there is no hint. * @return Whether focus was taken. */ @SuppressWarnings({"ConstantConditions"}) protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { int index; int increment; int end; int count = mChildrenCount; if ((direction & FOCUS_FORWARD) != 0) { index = 0; increment = 1; end = count; } else { index = count - 1; increment = -1; end = -1; } final View[] children = mChildren; for (int i = index; i != end; i += increment) { View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { if (child.requestFocus(direction, previouslyFocusedRect)) { return true; } } } return false; }
public static final int FOCUS_FORWARD = 0x00000002;
根据direction来决定index从0还是从count-1开始。
咱们看log的时候requestFocus有时候是3054行,有时候是3057行,其差别在于FOCUS_AFTER_DESCENDANTS和FOCUS_BEFORE_DESCENDANTS。就这么递归直到HorizontalScrollView.java
at android.view.View.onFocusChanged(View.java:6278) at android.widget.TextView.onFocusChanged(TextView.java:9559) at android.view.View.handleFocusGainInternal(View.java:6004) at android.view.View.requestFocusNoSearch(View.java:9077) at android.view.View.requestFocus(View.java:9056) at android.widget.HorizontalScrollView.onRequestFocusInDescendants(HorizontalScrollView.java:2056)
/** * When looking for focus in children of a scroll view, need to be a little * more careful not to give focus to something that is scrolled off screen. * * This is more expensive than the default {@link android.view.ViewGroup} * implementation, otherwise this behavior might have been made the default. */ @Override protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { // convert from forward / backward notation to up / down / left / right // (ugh). if (direction == View.FOCUS_FORWARD) { direction = View.FOCUS_RIGHT; } else if (direction == View.FOCUS_BACKWARD) { direction = View.FOCUS_LEFT; } final View nextFocus = previouslyFocusedRect == null ? FocusFinder.getInstance().findNextFocus(this, null, direction) : FocusFinder.getInstance().findNextFocusFromRect(this, previouslyFocusedRect, direction); if (nextFocus == null) { return false; } if (isOffScreen(nextFocus)) { return false; } return nextFocus.requestFocus(direction, previouslyFocusedRect); // 2056 }
这里走回View(nextFocus)的requestFocus,也就是requestFocusNoSearch。
private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) { // need to be focusable if ((mViewFlags & FOCUSABLE_MASK) != 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); //9077 return true; } /** * Give this view focus. This will cause * {@link #onFocusChanged(boolean, int, android.graphics.Rect)} to be called. * * Note: this does not check whether this {@link View} should get focus, it just * gives it focus no matter what. It should only be called internally by framework * code that knows what it is doing, namely {@link #requestFocus(int, Rect)}. * * @param direction values are {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, * {@link View#FOCUS_LEFT} or {@link View#FOCUS_RIGHT}. This is the direction which * focus moved when requestFocus() is called. It may not always * apply, in which case use the default View.FOCUS_DOWN. * @param previouslyFocusedRect The rectangle of the view that had focus * prior in this View's coordinate system. */ void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) { if (DBG) { System.out.println(this + " requestFocus()"); } if ((mPrivateFlags & PFLAG_FOCUSED) == 0) { mPrivateFlags |= PFLAG_FOCUSED; View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null; if (mParent != null) { mParent.requestChildFocus(this, this); } if (mAttachInfo != null) { mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this); } onFocusChanged(true, direction, previouslyFocusedRect); // 6004 refreshDrawableState(); } }
先是TextView的onFocusChange
@Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (mTemporaryDetach) { // If we are temporarily in the detach state, then do nothing. super.onFocusChanged(focused, direction, previouslyFocusedRect); return; } if (mEditor != null) mEditor.onFocusChanged(focused, direction); if (focused) { if (mText instanceof Spannable) { Spannable sp = (Spannable) mText; MetaKeyKeyListener.resetMetaState(sp); } } startStopMarquee(focused); if (mTransformation != null) { mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); } super.onFocusChanged(focused, direction, previouslyFocusedRect); //9559 }
调了
bfc6
父类View的onFocusChanged
/** * Called by the view system when the focus state of this view changes. * When the focus change event is caused by directional navigation, direction * and previouslyFocusedRect provide insight into where the focus is coming from. * When overriding, be sure to call up through to the super class so that * the standard focus handling will occur. * * @param gainFocus True if the View has focus; false otherwise. * @param direction The direction focus has moved when requestFocus() * is called to give this view focus. Values are * {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT}, * {@link #FOCUS_RIGHT}, {@link #FOCUS_FORWARD}, or {@link #FOCUS_BACKWARD}. * It may not always apply, in which case use the default. * @param previouslyFocusedRect The rectangle, in this view's coordinate * system, of the previously focused view. If applicable, this will be * passed in as finer grained information about where the focus is coming * from (in addition to direction). Will be <code>null</code> otherwise. */ @CallSuper protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction, @Nullable Rect previouslyFocusedRect) { if (gainFocus) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } else { notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } InputMethodManager imm = InputMethodManager.peekInstance(); if (!gainFocus) { if (isPressed()) { setPressed(false); } if (imm != null && mAttachInfo != null && mAttachInfo.mHasWindowFocus) { imm.focusOut(this); } onFocusLost(); } else if (imm != null && mAttachInfo != null && mAttachInfo.mHasWindowFocus) { imm.focusIn(this); } invalidate(true); ListenerInfo li = mListenerInfo; if (li != null && li.mOnFocusChangeListener != null) { li.mOnFocusChangeListener.onFocusChange(this, gainFocus); // 6278 } if (mAttachInfo != null) { mAttachInfo.mKeyDispatchState.reset(this); } }
我们看到log里会打出focusIn 和focusOut的信息,其实就是这里调用的IMM的方法。然后是通知onFocusChangeListener的监听者。而TabWidget就是其中一个。
at com.samsung.musicplus.contents.extra.MusicSelectListTabActivity.onTabChanged(MusicSelectListTabActivity.java:236) at android.widget.TabHost.invokeOnTabChangeListener(TabHost.java:462) at android.widget.TabHost.setCurrentTab(TabHost.java:442) at android.widget.TabHost$2.onTabSelectionChanged(TabHost.java:194) at android.widget.TabWidget.onFocusChange(TabWidget.java:683)
TabWidget在onFocusChange里面干了比较重要的事setCurrentTab,也就是这个问题发生的主要原因。在挨个询问焦点的过程中setCurrentTab为0了,所以回调onTabSelectionChanged。
/** {@inheritDoc} */ public void onFocusChange(View v, boolean hasFocus) { if (v == this && hasFocus && getTabCount() > 0 && mSelectedTab != -1) { getChildTabViewAt(mSelectedTab).requestFocus(); return; } if (hasFocus) { int i = 0; int numTabs = getTabCount(); while (i < numTabs) { if (getChildTabViewAt(i) == v) { setCurrentTab(i); mSelectionChangedListener.onTabSelectionChanged(i, false); //683 if (isShown()) { // a tab is focused so send an event to announce the tab widget state sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } break; } i++; } } }
TabHost onTabSelectionChanged
mTabWidget.setTabSelectionListener(new TabWidget.OnTabSelectionChanged() { public void onTabSelectionChanged(int tabIndex, boolean clicked) { setCurrentTab(tabIndex); //442 if (clicked) { mTabContent.requestFocus(View.FOCUS_FORWARD); } } });
setCurrentTab中会调用invokeOnTabChangeListener,最终反馈到Activity
public void setCurrentTab(int index) { if (index < 0 || index >= mTabSpecs.size()) { return; } if (index == mCurrentTab) { return; } // notify old tab content if (mCurrentTab != -1) { mTabSpecs.get(mCurrentTab).mContentStrategy.tabClosed(); } mCurrentTab = index; final TabHost.TabSpec spec = mTabSpecs.get(index); // Call the tab widget's focusCurrentTab(), instead of just // selecting the tab. mTabWidget.focusCurrentTab(mCurrentTab); // tab content mCurrentView = spec.mContentStrategy.getContentView(); if (mCurrentView.getParent() == null) { mTabContent .addView( mCurrentView, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } if (!mTabWidget.hasFocus()) { // if the tab widget didn't take focus (likely because we're in touch mode) // give the current tab content view a shot mCurrentView.requestFocus(); } //mTabContent.requestFocus(View.FOCUS_FORWARD); invokeOnTabChangeListener(); } private void invokeOnTabChangeListener() { if (mOnTabChangeListener != null) { /* { EAF */ if (IS_ELASTIC_ENABLED && InjectionManager.getInstance() != null) { InjectionManager.getInstance().dispatchTabHostEvent(getCurrentView().getContext(),getCurrentTabTag()); } /* EAF } */ mOnTabChangeListener.onTabChanged(getCurrentTabTag()); } }
最后的解决方案是在ListFragment中加了callback,当tab列表中的一项被点击的时候(onListItemClick)去回调其onFocusChange通知监听者(TabActivity)这个时候短暂地将tab设为focusable false,避开这个获取焦点环节。
相关文章推荐
- C++中动态内存分配引发问题的解决方案
- 语言问题的引发
- SAPOsCol引发的问题
- 读本期《程序员》(8月刊)而引发的对计算机相关专业毕业生就业问题的一些思考
- [导入]Visual Studio 2005 Web Deployment Projects版本不同引发的问题
- 强制结束令牌(token)引发的问题
- Oss2007中单个Item授权可能会引发另一头疼的问题
- Java对象赋值引发的问题
- 一个游戏引发的思考(概率问题)
- 由于交流中的问题引发的开发问题
- Linux下Oracle字符集问题引发的汉字插入失败解决方案
- C++中动态内存分配引发问题的解决方案
- 由索引引发的奇怪的无效条件问题
- 值类型装箱引发的效率问题(vb.net)
- Cached JSP引发的问题与思考
- 一个VS.net自动生成代码引发的问题
- 模块隔离时引发的Hibernate问题
- Oss2007中单个Item授权可能会引发另一头疼的问题
- [导入]Visual Studio 2005 Web Deployment Projects版本不同引发的问题
- Visual Studio 2005 Web Deployment Projects版本不同引发的问题