您的位置:首页 > 移动开发 > Android开发

由FocusChange引发的问题手札

2016-11-20 14:57 106 查看
这次碰见了Fragment.replace后发生的focus改变问题。

这个问题是用户用蓝牙键盘的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,避开这个获取焦点环节。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息