Focus processing is one of the first steps you have to go through to move from Android phone development to TV development, a question xiao Wang, who recently switched from mobile development to TV development, came across.

After a month of hard work, Xiao Wang, XXTV version of the 1.0 version of the project was finally iteratively completed. I thought I could easily spend a weekend, but as soon as the version was sent out, the customer raised questions. Why can’t our list focus go up when pressing the left button on the left and down when pressing the right button on the right like iQiyi? Why doesn’t our navigation have focus memory?

In order to solve the customer’s problem, Xiao Wang spent a weekend to learn how Android handles KeyEvent events, and tested the learning effect by taking technical notes. Because output is the best way to learn.

KeyEvent Event Summary:

  1. The KeyEvent event is divided into two stages. The dividing point is that the current focus View receives dispatchKeyEvent. The first part is KeyEvent event processing, and the second part is focus search.
  2. By default, KeyEvent events are handed over to the View that currently has the focus. ViewGroup is just an event passing function.
  3. Only viewgroups that directly or indirectly contain the current focus View have a chance to call the dispatchKeyEvent and focusSearch methods
  4. The OnKeyListener set is only effective if the ViewGroup directly or indirectly contains the current focus View.
  5. We can override the ViewGroup dispatchKeyEvent method to implement our own KeyEvent interception handling.
  6. For cross-viewGroup focus lookups we can handle them in addFocusable, but for ViewGroup internal focus lookups, overriding focusSearch is preferred without breaking the original event handling criteria, followed by dispatchKeyEvent.

What is focus memory? What is focus internal search?

Let’s say that the current focus is on C, press right, D gets focus, and for ViewGroup N this is an internal search for focus

Let’s say that the current focus is on C, the button is up, A gets the focus, this is A focus search across containers, if the focus of M is away from A, and as long as there’s A ViewGroup M that has A child View that gets the focus, it must be on A so let’s say that ViewGroup M has focus memory.

So how do you solve the customer’s problem? Wang faces two main problems

  1. How do I record the last focus element?
  2. When do you intervene in the focus search process and handle it yourself?

What happens when the View gets focus

To understand the process in detail, Wang started with the requestFocus method of the View, and went to requestChildFocus to discover the important information about how the View gets focus. The focus ViewGroup is recorded in mFocused from the current focus View up to the root ViewGroup. The chain holds the focus information.

ViewGroup:

@Override
public void requestChildFocus(View child, View focused) {
    if (DBG) {
        System.out.println(this + " requestChildFocus()");
    }
    if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
        return;
    }

    // Unfocus us, if necessary
    super.unFocus(focused);

    // We had a previous notion of who had focus. Clear it.
    if(mFocused ! = child) {if(mFocused ! =null) {
            mFocused.unFocus(focused);
        }

        mFocused = child;
    }
    if(mParent ! =null) {
        mParent.requestChildFocus(this, focused); }}Copy the code

Android KeyEvent event distribution mechanism

With three years of work experience, Xiao Wang has long been familiar with the distribution process of traditional mobile touch events. Its classic distribution process is as follows:

ViewRootImpl -> DecorView -> Activity -> Window -> DecorView

The classic U-shaped chart:

After reviewing the distribution process of touch events on mobile phones, Xiao Wang guessed that the KeyEvent event on remote control should be similar to the flow of touch events on mobile phones. The main logic for mobile touch events is dispatchTouchEvent, so the main logic for KeyEvent should be dispatchKeyEvent.

ViewGroup:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if(mInputEventConsistencyVerifier ! =null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 1);
    }

    if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
            == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
        if (super.dispatchKeyEvent(event)) {
            return true; }}else if(mFocused ! =null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
            == PFLAG_HAS_BOUNDS) {
        if (mFocused.dispatchKeyEvent(event)) {
            return true; }}if(mInputEventConsistencyVerifier ! =null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
    }
    return false;
}
Copy the code

View:

public boolean dispatchKeyEvent(KeyEvent event) {
    if(mInputEventConsistencyVerifier ! =null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 0);
    }

    // Give any attached key listener a first crack at the event.
    //noinspection SimplifiableIfStatement
    ListenerInfo li = mListenerInfo;
    if(li ! =null&& li.mOnKeyListener ! =null && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
        return true;
    }

    if (event.dispatch(this, mAttachInfo ! =null
            ? mAttachInfo.mKeyDispatchState : null.this)) {
        return true;
    }

    if(mInputEventConsistencyVerifier ! =null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return false;
}
Copy the code

By reading the source code, Xiao Wang found:

  1. The KeyEvent event is passed in the ViewGroup and handled in the View.
  2. By default, KeyEvent events are handed over to the View that currently has the focus. ViewGroup is just an event passing function.
  3. We can override the ViewGroup to implement our own KeyEvent interception handling.
  4. The OnKeyListener setting is only effective if the current View has focus.
  5. The dispatchKeyEvent method only deals with keys and does not involve a focus search.

Focus on search

Xiao Wang through the above system source reading, only the event transfer processing, but there is no focus on the processing, Xiao Wang will target ViewRootImpl. The entire KeyEvent distribution process is implemented in the processKeyEvent method of the ViewPostImeInputStage inner class of ViewRootImpl.

The simple pseudo-code is as follows

// Event distribution
if(mView.dispatchKeyEvent(event)){
	return FINISH_HANDLED;
}
// Focus search
View focused = mView.findFocus()
focused.focusSearch(direct)
Copy the code

That is, when all views’ dispatchKeyEvent returns false, ViewRootImpl retrives the current focus View from the DecorView and calls its focusSearch method.

View:

public View focusSearch(@FocusRealDirection int direction) {
    if(mParent ! =null) {
        return mParent.focusSearch(this, direction);
    } else {
        return null; }}Copy the code

ViewGroup:

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;
}
Copy the code

The whole focus search process is through the current focus View continuously up the focus search.

In order to clearly illustrate the whole process of the incident, Xiao Wang drew the following picture. Help yourself understand the memory.

  1. The KeyEvent event is divided into two stages. The dividing point is that the current focus View receives dispatchKeyEvent. The first part is KeyEvent event processing, and the second part is focus search.
  2. The KeyEvent event is passed in the ViewGroup and handled in the View.
  3. Only viewgroups that directly or indirectly contain the current focus View have a chance to call the dispatchKeyEvent and focusSearch methods
  4. By default, KeyEvent events are handed over to the View that currently has the focus. ViewGroup is just an event passing function.
  5. We can override the ViewGroup to implement our own KeyEvent interception handling.
  6. The OnKeyListener set is only effective if the ViewGroup directly or indirectly contains the current focus View.
  7. The DispatchKeyEvent method only deals with keys and does not involve a focus search.

Focus on the lookup process in FocusFinder

After understanding the KeyEvent event distribution process, Wang still had no idea how to solve the customer’s problem, so he had to read the source code to see if he could find some inspiration.

FocusFinder:

private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
    View next = null;
    ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
    if(focused ! =null) {
        // Find the View specified by nextFocusRight nextFocusLeft nextFocusUp nextFocusDown
        next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
    }
    if(next ! =null) {
        return next;
    }
    ArrayList<View> focusables = mTempList;
    try {
        focusables.clear();
        // Add a View in the container that can get the focus
        // This method controls which subviews of the current ViewGroup can get focus
        effectiveRoot.addFocusables(focusables, direction);
        if(! focusables.isEmpty()) {// Use the nearest algorithm to find the most suitable View to get the focus nextnext = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables); }}finally {
        focusables.clear();
    }
    return next;
}
Copy the code

FocusFinder’s focus search process:

  1. Finds the specifically specified focus View
  2. Get the nearest View that can get focus

ViewGroup focus memory function implementation

With this in mind, Wang decided to record the current focus element in requestChildFocus.

Timing to intervene in focus processing:

  • In the dispatchKeyEvent() method, the event is passed centered on the current focus. Focus memory needs to be implemented across viewgroups. Does not meet the
  • FocusSearch and dispatchKeyEvent do not meet the requirements
  • AddFocusables adds a View that can get focus. How to add only the View recorded last time to the container? Then the container can only get focus from the View recorded last time, which can achieve focus memory function. meet

Reference: WandroidTv version of DynamicLinearLayout class about focus memory implementation

Github.com/xiaolutang/…

Left and right button test focus up and down

The problem is a focus on internal search,

We can do this ourselves by handling dispatchKeyEvent or focusSearch methods. Xiao Wang thought that if the dispatchKeyEvent was processed, it would break the original KeyEvent flow. Therefore, Wang uses focusSearch to deal with it.

Refer to WanAndroidTv edition DynamicFlexboxLayout and LibTvRecyclerView

Github.com/xiaolutang/…

Summary:

  1. Cross-container focus lookup is handled by overriding the addFocusables method.
  2. The focusSearch inside the container is realized by focusSearch first, and the dispatchKeyEvent method is considered second.

Why is ViewPage’s focus handling unusual

After wang implemented the left-right button focus lookup problem, there was no problem in the separate test. But it doesn’t work when you put in a ViewPager. Xiao Wang felt very strange. Did he have a problem understanding the KeyEvent process? With doubt wang opened the ViewPager source code.

ViewPager:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    // Let the focused view and/or our descendants get the key first
    return super.dispatchKeyEvent(event) || executeKeyEvent(event);
}
Copy the code

If the keyEvent is not handled by a View and the event is left or right, it will be consumed by the ViewPager. Do your own focus search. But this interrupts the KeyEvent and focus search process.

The RecyclerView KeyEvent event process has been changed by the RecyclerView

public class NavViewPager extends ViewPager {
    public NavViewPager(@NonNull Context context) {
        super(context);
    }

    public NavViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public View focusSearch(View focused, int direction) {
        if(arrowScroll(direction)){
            return null;
        }
        return super.focusSearch(focused, direction);
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if(hasFocus()){
            return super.dispatchKeyEvent(event);
        }

        View focused = findFocus();
        if(focused ! =null) {return focused.dispatchKeyEvent(event);
        }
        return false; }}Copy the code

This is the end of the Android KeyEvent process. Xiao Wang no longer has to worry about the focus of the project.

Finally, the conclusion at the beginning of the article is supplemented:

  1. The KeyEvent event is divided into two stages. The dividing point is that the current focus View receives dispatchKeyEvent. The first part is KeyEvent event processing, and the second part is focus search.
  2. By default, KeyEvent events are handed over to the View that currently has the focus. ViewGroup is just an event passing function.
  3. Only viewgroups that directly or indirectly contain the current focus View have a chance to call the dispatchKeyEvent and focusSearch methods
  4. The OnKeyListener set is only effective if the ViewGroup directly or indirectly contains the current focus View.
  5. We can override the ViewGroup dispatchKeyEvent method to implement our own KeyEvent interception handling.
  6. For cross-viewGroup focus lookups we can handle them in addFocusable, but for ViewGroup internal focus lookups, overriding focusSearch is preferred without breaking the original event handling criteria, followed by dispatchKeyEvent.

Output is the best way to check learning outcomes.

This is the first time I’ve written a technical article in this way. Welcome to forward, welcome to follow