由FocusChange引发的问题手札

这次碰见了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);
                                     }
                                 }
                             });
                             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
}

调了父类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,避开这个获取焦点环节。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值