Android TV焦点总结

时间:2022-07-23
本文章向大家介绍Android TV焦点总结,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
【1】 ViewRootImpl.ViewPostImeInputStage.onProcess()

当你拿着遥控器瞎按的时候,按键处理的起点是这儿

public final class ViewRootImpl implements ViewParent,
       View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
   
   final class ViewPostImeInputStage extends InputStage {
       @Override
       protected int onProcess(QueuedInputEvent q) {
           if (q.mEvent instanceof KeyEvent) {
               //按键处理的起点
               return processKeyEvent(q);
          } else {
               //...
          }
      }        
  }
   
       private int processKeyEvent(QueuedInputEvent q) {
           final KeyEvent event = (KeyEvent)q.mEvent;


           //【1.1】
           //这里的 mView 当然是我们的 DecorView,
           if (mView.dispatchKeyEvent(event)) {
               return FINISH_HANDLED;
          }
           //...
           // Handle automatic focus changes.
           if (event.getAction() == KeyEvent.ACTION_DOWN) {
               if (groupNavigationDirection != 0) {
                   //...
              } else {
                   //关键代码
                   // 自动寻焦的逻辑【2】
                   if (performFocusNavigation(event)) {
                       return FINISH_HANDLED;
                  }
              }
          }
           return FORWARD;
      }  
}
【1.1】DecorView.dispatchKeyEvent()

【/frameworks/base/core/java/android/internal/policy/DecorView.java】

从此开始,DecorView开始进行KeyEvent的分发

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
   
   
   //1. 重写了父类FrameLayout的dispatchKeyEvent()方法,但不会执行 super.dispatchKeyEvent()了
   //2.
   @Override
   public boolean dispatchKeyEvent(KeyEvent event) {
       final int keyCode = event.getKeyCode();
       final int action = event.getAction();
       final boolean isDown = action == KeyEvent.ACTION_DOWN;

       //...

       if (!mWindow.isDestroyed()) {
           // 获取 Callback对象 就是activity
           //见【1.2】
           final Window.Callback cb = mWindow.getCallback();
           //执行activity 的dispatchKeyEvent(),super.dispatchKeyEvent(event)不会执行滴
           final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                  : super.dispatchKeyEvent(event);
           if (handled) {
               return true;
          }
      }

       return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
              : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
  }
   
}
【1.2】Activity.dispatchKeyEvent()

按键分发来到Activity中

public class Activity extends ContextThemeWrapper
       implements LayoutInflater.Factory2,
       Window.Callback{
   
   //参数省略了..
   final void attach() {

       //这儿将 activity 传入
       mWindow = new PhoneWindow(this, window, activityConfigCallback);
       mWindow.setWindowControllerCallback(this);
       //setCallback
       mWindow.setCallback(this);
       //...
  }
 
   // 注意 Activity实现了Window.Callback接口
   //dispatchKeyEvent()方法 就是来自Window.Callback
   public boolean dispatchKeyEvent(KeyEvent event) {
       onUserInteraction();

       //...
       //win 是PhoneWindow 对象
       Window win = getWindow();
       //关键代码【1.3】
       if (win.superDispatchKeyEvent(event)) {
           return true;
      }
       View decor = mDecor;
       if (decor == null) decor = win.getDecorView();
       return event.dispatch(this, decor != null
               ? decor.getKeyDispatcherState() : null, this);
  }    
   
}
【1.3】PhoneWindow

按键分发来到PhoneWindow中

兜兜转换 执行DecorView的

mDecor.superDispatchKeyEvent(event)方法,

又回到DecorView中

public class PhoneWindow extends Window implements MenuBuilder.Callback {

   //
   @Override
   public boolean superDispatchKeyEvent(KeyEvent event) {
       //mDecor就是DecorView
       //
       return mDecor.superDispatchKeyEvent(event);
  }    
}
【1.4】

按键分发来到DecorView中

经过PhoneWindow的按键分发,又来到DecorView的superDispatchKeyEvent()方法

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
   
   public boolean superDispatchKeyEvent(KeyEvent event) {
       //...
       //执行 父类的dispatchKeyEvent()方法
       //父类是FrameLayout,发现FrameLayout没有dispatchKeyEvent()
       //进入ViewGroup 【1.5】
       if (super.dispatchKeyEvent(event)) {
           return true;
      }

       return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
  }    
}
【1.5】

按键分发来到ViewGroup中

至此我们按键的分发:

DecorView

【---->】

Activity

【---->】

DecorView

【---->】

ViewGroup

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
   
   //
   @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)) {
           //如果 view 处理返回 true,这里也返回 true
           //【1.6】 View
           if (super.dispatchKeyEvent(event)) {
               return true;
          }
      } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
               == PFLAG_HAS_BOUNDS) {
           //ViewGroup自己把KeyEvent交给mFocused处理  
           //ViewGroup处理 也是回调view 的 dispatchKeyEvent()
           if (mFocused.dispatchKeyEvent(event)) {
               return true;
          }
      }
       //...
       return false;
  }    
}

通过flag的判断,有两个处理路径,也可以看到在处理keyEvent时,ViewGroup扮演两个角色:

1.View的角色,也就是此时keyEvent需要在自己与其他View之间流转

2.ViewGroup的角色,此时keyEvent需要在自己的子View之间流转

【1.6】 View
public class View implements Drawable.Callback, KeyEvent.Callback,
       AccessibilityEventSource {
   
   public boolean dispatchKeyEvent(KeyEvent event) {

        //如果设置了mOnKeyListener,则优先走onKey方法
       ListenerInfo li = mListenerInfo;
       if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
               && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
           return true;
      }

       //把View自己当作参数传入,调用KeyEvent的dispatch方法 在里面回调 onKeyDown/onKeyUp
       if (event.dispatch(this, mAttachInfo != null
               ? mAttachInfo.mKeyDispatchState : null, this)) {
           return true;
      }
       //...
       return false;
  }    
}

View这里,会优先处理OnKeyListener的onKey回调。

然后才可能会走KeyEvent的dispatch,最终走到View的OnKeyDown或者OnKeyUp

ok,我要开始写绕口令了...

上面的执行流程总结就是:

DecorView.dispatchKeyEvent() 如果下面向上返回 false  交给 window 处理

【---->】

 Activity.dispatchKeyEvent()  如果下面向上返回 false 先交给自己的onKey         Down/onKeyUp处理 ,还是返回 false,往上抛出交给 DecorView 处理

【---->】

PhoneWindow.superDispatchKeyEvent()  如果下面向上返回 false ,往上抛出交给 Activity 处理

【---->】

DecorView.superDispatchKeyEvent() 如果下面向上返回 false ,往上抛出交给 PhoneWindow 处理

【---->】

DecorView.this.super.dispatchKeyEvent(event) 如果下面向上返回 false ,往上抛出

【---->】

ViewGroup.dispatchKeyEvent() 如果下面向上返回 false ,看看自己或者子 view 有没有焦点,如果有,自身处理 先执行onKey(),

没有处理,再执行onKeyDown/onKeyUp ,还是返回 false,往上抛出

【---->】

View.dispatchKeyEvent 看看有没有焦点,如果有,自身处理 先执行onKey(),没有处理,再执行onKeyDown/onKeyUp ,还是返回 false,往上抛出

如果以上没有处理

执行自动寻焦的逻辑

【2】 ViewRootImpl.ViewPostImeInputStage.processKeyEvent()

按键事件自动寻焦

代码回到ViewRootImpl【1.1】返回 false ,代码继续往下执行

public final class ViewRootImpl implements ViewParent,
       View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
   
   final class ViewPostImeInputStage extends InputStage {
       @Override
       protected int onProcess(QueuedInputEvent q) {
           if (q.mEvent instanceof KeyEvent) {
               //按键处理的起点
               return processKeyEvent(q);
          } else {
               //...
          }
      }        
  }  
       private int processKeyEvent(QueuedInputEvent q) {
           final KeyEvent event = (KeyEvent)q.mEvent;


           //【1.1】
           //这里的 mView 当然是我们的 DecorView,
           if (mView.dispatchKeyEvent(event)) {
               return FINISH_HANDLED;
          }
           //...

           // Handle automatic focus changes.
           if (event.getAction() == KeyEvent.ACTION_DOWN) {
               if (groupNavigationDirection != 0) {
                   //...
              } else {
                   //关键代码
                   // 自动寻焦的逻辑【2.2】
                   if (performFocusNavigation(event)) {
                       return FINISH_HANDLED;
                  }
              }
          }
           return FORWARD;
      }  
       
   
}
【2.2】 performFocusNavigation()
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
        
        private boolean performFocusNavigation(KeyEvent event) {
             //...
            if (direction != 0) {
                View focused = mView.findFocus();
                if (focused != null) {
                    //关键代码
                    //【2.3】
                    View v = focused.focusSearch(direction);
                    if (v != null && v != focused) {
                        //...
                    }

                    // Give the focused view a last chance to handle the dpad key.
                    if (mView.dispatchUnhandledMove(focused, direction)) {
                        return true;
                    }
                } else {
                    if (mView.restoreDefaultFocus()) {
                        return true;
                    }
                }
            }
            return false;
        }
}
【2.3】View.focusSearch()
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
   
    public View focusSearch(@FocusRealDirection int direction) {
        if (mParent != null) {
            //关键代码
            return mParent.focusSearch(this, direction);
        } else {
            return null; }}}
            
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    //
    public View focusSearch(View focused, int direction) {
        //最终进入 if 分支
        if (isRootNamespace()) {
            //关键代码 【2.4】
            return FocusFinder.getInstance().findNextFocus(this, focused, direction);
        } else if (mParent != null) {
            //不是 root 布局 继续递归,最终进入 if 分支
            return mParent.focusSearch(focused, direction);
        }
        return null; }
        }

View并不会直接去找,而是交给它的parent去找,

而 VieGroup 呢?来一个递归的判断直到是顶层布局,执行FocusFinder.getInstance().findNextFocus(this, focused, direction)

【2.4】FocusFinder
public class FocusFinder {
    
   private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        ViewGroup effectiveRoot = getEffectiveRoot(root, focused);
        if (focused != null) {
            //优先从xml或者代码中指定focusid的View中找
            next = findNextUserSpecifiedFocus(effectiveRoot, focused, direction);
        }
        if (next != null) {
            return next;
        }
        ArrayList<View> focusables = mTempList;
        try {
            focusables.clear();
            effectiveRoot.addFocusables(focusables, direction);
            if (!focusables.isEmpty()) {
                //其次,根据算法去找,原理就是找在方向上最近的View
                next = findNextFocus(effectiveRoot, focused, focusedRect, direction, focusables);
            }
        } finally {
            focusables.clear();
        }
        return next;
    }
    
   private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
            int direction, ArrayList<View> focusables) {
        if (focused != null) {
            //...
        } else {
            //...
        }
    }
}
  • 优先找开发者指定的下一个focus的视图 ,就是在xml或者代码中指定NextFocusDirection Id的视图
  • 其次,根据算法去找,原理就是找在方向上最近的视图

更多内容 欢迎关注公众号