Android焦点分发策略

[TOC]

焦点分发基础知识

获取焦点的前提

  • View#isFocusable返回true, 如果在触摸模式, 则View#isFocusableInTouchMode也要返回true
  • 控件必须可见
  • 控件相关的父控件, 包括祖父控件等, ViewGroup#getDescendantFocusability()不能为ViewGroup#FOCUS_BLOCK_DESCENDANTS

焦点相关api

  • requestFocus

主动请求焦点

  • clearFocus

主动清除焦点

  • focusSearch

递归搜索需要焦点的View

  • ViewGroup.setDescendantFocusability

三个参数含义:
FOCUS_BEFORE_DESCENDANTS
ViewGroup本身相对焦点进行处理,如果沒有处理则分发给child View进行处理

FOCUS_AFTER_DESCENDANTS
先分发给Child View进行处理,如果所有的Child View都沒有处理,則自己再处理

FOCUS_BLOCK_DESCENDANTS
ViewGroup本身进行处理,不管是否处理成功,都不会分发给ChildView进行处理

  • onRequestFocusInDescendants

可以传入方向来改变遍历的顺序, 默认是从0递增,遍历子控件,调用子控件的View#requestFocus来尝试把焦点给可见的子控件, 某个子控件成功获取到焦点后, 停止遍历

注: 重写该方法可以改变ViewGroup分发焦点给子控件的行为, 例如遍历顺

  • requestChildFocus

当子控件主动放弃焦点的时候,回调这个方法通知父控件.

  • clearChildFocus

当子控件获取了焦点后, 回调这个方法通知父控件

  • focusableViewAvailable

通知父控件, 子控件的状态发生改变, 从不能获取焦点, 变成可能可以获取焦点.

  • hasFocus

当前视图是否是焦点视图或子视图里面有焦点视图

  • isFocused()

当前视图是否是焦点视图

  • findFocus

查找焦点控件

  • getFocusedChild

获取子控件的焦点view

参考博客

焦点分发源码分析

在Android中任何事件都会经由ViewRootImpl$ViewPostImeInputStage的onProcess处理.在onProcess方法中会判断不同事件类型做不同的处理.这里我们只分析触发焦点转移的KeyEvent.事件分发机制参考

当我们触发按键,最终会由底层把事件传入onProcess,在onProcess中如果是KeyEvent类型就进入processKeyEvent方法中进行处理.

processKeyEvent方法源码如下:

private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            if (event.getAction() != KeyEvent.ACTION_UP) {
                // If delivering a new key event, make sure the window is
                // now allowed to start updating.
                handleDispatchDoneAnimating();
            }
            //1.向View树分发KeyEvent事件
            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }

            //........................此处省略部分代码
            
            //2.处理焦点的转移
            // Handle automatic focus changes.
           //........................此处省略部分代码
                if (direction != 0) {
                //step1 找到当前View树中的焦点View
                    View focused = mView.findFocus();
                    if (focused != null) {
                    //step2 当前焦点View根据方向搜索下一个需要焦点的View并主动请求焦点
                        View v = focused.focusSearch(direction);
                        if (v != null && v != focused) {
                            // do the math the get the interesting rect
                            // of previous focused into the coord system of
                            // newly focused view
                            focused.getFocusedRect(mTempRect);
                            if (mView instanceof ViewGroup) {
                                ((ViewGroup) mView).offsetDescendantRectToMyCoords(
                                        focused, mTempRect);
                                ((ViewGroup) mView).offsetRectIntoDescendantCoords(
                                        v, mTempRect);
                            }
                            if (v.requestFocus(direction, mTempRect)) {
                                playSoundEffect(SoundEffectConstants
                                        .getContantForFocusDirection(direction));
                                return FINISH_HANDLED;
                            }
                        }

                        // Give the focused view a last chance to handle the dpad key.
                        if (mView.dispatchUnhandledMove(focused, direction)) {
                            return FINISH_HANDLED;
                        }
                    } else {
                        // find the best view to give focus to in this non-touch-mode with no-focus
                        //step3 ViewRootImpl根据方向搜索下一个需要焦点的View并主动请求焦点
                        View v = focusSearch(null, direction);
                        if (v != null && v.requestFocus(direction)) {
                            return FINISH_HANDLED;
                        }
                    }
                }
            }

这个方法中主要做了两件事,1.向View树分发KeyEvent事件 2.处理焦点的转移,下面主要分析处理焦点的转移这条逻辑.

1.向View树分发KeyEvent事件

// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
    return FINISH_HANDLED;
}

这就是我们熟悉View层的事件分发,同理还有

  • dispatchHoverEvent

  • dispatchTouchEvent

2.处理焦点的转移

step1 : 当我们的按键有一个确定的方向,首先找到当前View树中的焦点view.

 if (direction != 0) {
    View focused = mView.findFocus();
}

step2 : 如果找到了当前焦点View,则调用当前焦点View的焦点搜索方法,并返回下一个需要焦点的View,最后需要焦点的View主动请求焦点

  View v = focused.focusSearch(direction);
  
  if (v.requestFocus(direction, mTempRect)) {
    playSoundEffect(SoundEffectConstants
            .getContantForFocusDirection(direction));
    return FINISH_HANDLED;
    }

最终View#focusSearch直接调用的是ViewPaent#focusSearch
ViewParent是一个接口,最后在ViewGroup中实现,后文分析ViewGroup#focusSearch,View的focusSearch方法如下:

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

step3 : 如果未找到当前焦点View,则调用ViewRootImpl的焦点搜索方法,并返回下一个需要焦点的View,最后需要焦点的View主动请求焦点

View v = focusSearch(null, direction);
if (v != null && v.requestFocus(direction)) {
    return FINISH_HANDLED;
}

本质上ViewRootImpl#focusSearch方法重写的是ViewPaent#focusSearch,此方法内部直接调用焦点帮助类,返回一个需要焦点的View.方法如下:

    @Override
    public View focusSearch(View focused, int direction) {
        checkThread();
        if (!(mView instanceof ViewGroup)) {
            return null;
        }
        return FocusFinder.getInstance().findNextFocus((ViewGroup) mView, focused, direction);
    }

至此,焦点分发的逻辑就进入了View层,下面分析View层的逻辑.

ViewGroup#focusSearch

因为View的focusSearch直接调用的是ViewParent的focusSearch,所以我们只分析ViewGroup的focusSearch

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;
    }

ViewGroup的focusSearch方法非常简单,如果是root View就调用焦点帮助类返回一个需要焦点的View,反之递归调用ViewGroup的focusSearch,直到找到一个需要焦点的View.

requestFocus

了解完focusSearch我们继续分析requestFocus,requestFocus在View和ViewGrope有各自不同的实现.

ViewGroup#requestFocus

@Override
    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        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 : onRequestFocusInDescendants(direction, previouslyFocusedRect);
            }
            case FOCUS_AFTER_DESCENDANTS: {
                final boolean took = onRequestFocusInDescendants(direction, previouslyFocusedRect);
                return took ? took : super.requestFocus(direction, previouslyFocusedRect);
            }
        }
    }

ViewGroup的requestFocus会根据三种分发策略决定是自己先请求还是child先请求,最终都会调用View的requestFocus

View#requestFocus

View的requestFocus会调用到requestFocusNoSearch
在requestFocusNoSearch中做一些位运算后最终调用handleFocusGainInternal

void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {

        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);
            refreshDrawableState();
        }
    }

handleFocusGainInternal中通知父容器自己请求了焦点,回调onFocusChanged,并刷新View

你可能感兴趣的:(Android焦点分发策略)