android focus查找 方向键如何查找focus

本文详细阐述了在Android界面中,按下方向键时焦点查找的机制。从ViewRoot的事件派发开始,通过遍历View和ViewGroup,结合focusFinder的工作流程,包括收集可聚焦视图、转换坐标系以及定位下一个焦点视图的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇文章提到了,新起的activity中的某个view是如何获取焦点的。这篇介绍一下,当在一个界面,按下方向键,焦点是如何查找的。
即第一篇文章中跑出来的问题:2.上下按键,焦点如何查找的,它怎么知道下一个获取焦点的是谁?
先总的说一下原理:新起一个界面的时候,会找到一个获取焦点的view(这个view可能是group或者最小的view),当按下方向键时,会进行遍历,根据方向,遍历这一方向布局中的view位置(所在位置是个矩形框),找到最合适的,然后把焦点设置上去。这里主要描述焦点查找,设置不再多说。

一、viewroot中的事件派发

需要input派发的知识,参考博文:
输入事件,在dispatch的时候,输入法不处理,则要拍发给具体的view了,这个过程在viewroot中。

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

            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;//输入法处理了,则直接返回,否则继续
            }
//...
              // Handle automatic focus changes.
            if (event.getAction() == KeyEvent.ACTION_DOWN) {//将键盘事件转换一下,上下左右专程对应的FOCUS_LEFT...
                int direction = 0;
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_LEFT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_RIGHT;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_UP:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_UP;
                        }
                        break;
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_DOWN;
                        }
                        break;
                    case KeyEvent.KEYCODE_TAB:
                        if (event.hasNoModifiers()) {
                            direction = View.FOCUS_FORWARD;
                        } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                            direction = View.FOCUS_BACKWARD;
                        }
                        break;
                }
                if (direction != 0) {
                    View focused = mView.findFocus();//拿到当前处于focus的view,需要以此view为出发点来查找某个方向上的焦点
                    if (focused != null) {
                        View v = focused.focusSearch(direction);//查找direction方向上的焦点,direction是上面转换了的(FOCUS_LEFT...)
                    //...
            return FORWARD;
        }

二、view和viewgroup的遍历

遍历,找到根节点:mView.findFocus函数走到view--viewgroup中

    public View focusSearch(@FocusRealDirection int direction) {
        if (mParent != null) {
            return mParent.focusSearch(this, direction);
        } else {
            return null;
        }
    }
主要是viewgroup中的。

    /**
     * Find the nearest view in the specified direction that wants to take
     * focus.
     *
     * @param focused The view that currently has focus
     * @param direction One of FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, and
     *        FOCUS_RIGHT, or 0 for not applicable.
     */
    @Override
    public View focusSearch(View focused, int direction) {
        if (isRootNamespace()) {//是根节点了,则开始根据方向查找和是获取焦点的view
            // 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);//调用focusfinder查找
        } else if (mParent != null) {
            return mParent.focusSearch(focused, direction);//不是根节点,需要继续遍历,找到根节点。
        }
        return null;
    }
遍历类似了,直接focusfinder中的findnextfocus

三、focusFinder

这里我们把过程分成几个步骤:

1.把根节点中的isfocusable的view都抓起来(arraylist)

    /**
     * Find the next view to take focus in root's descendants, starting from the view
     * that currently is focused.
     * @param root Contains focused. Cannot be null.
     * @param focused Has focus now.
     * @param direction Direction to look.
     * @return The next focusable view, or null if none exists.
     */
    public final View findNextFocus(ViewGroup root, View focused, int direction) {
        return findNextFocus(root, focused, null, direction);
    }
注释比较清楚。
跳到具体函数

    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        if (focused != null) {
            next = findNextUserSpecifiedFocus(root, focused, direction);//优先查找xml或者code中写定的查找顺序,
            //可以指定xml中的属性android:nextFocusUp或code中setNextFocusUpId
        } 
        if (next != null) {//如果xml中没有则根据view的rect继续查找 
           return next; 
        } 
        ArrayList<View> focusables = mTempList; 
        try { 
            focusables.clear();
            root.addFocusables(focusables, direction);//这里也是一个遍历,找到所有isfocusable的view,把这些view add进来,这里不再扩展了.
            if (!focusables.isEmpty()) {//这个arraylist中不是空的,就开始找最接近的view 
                next = findNextFocus(root, focused, focusedRect, direction, focusables);
            }
        } finally { focusables.clear(); } return next; }
上面代码看到,查找有先后,先xml中的写定的位置顺序,如果没有则在通过rect来。第一步xml中的查找比较简单,也不会出错,code就几个函数,灰常简单,不再多说,而且这种场景非常少。

2.抓起来,就要统一换上囚服了(统一坐标系)

    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
            int direction, ArrayList<View> focusables) {
        if (focused != null) {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
            }
            // fill in interesting rect from focused
            focused.getFocusedRect(focusedRect);
            root.offsetDescendantRectToMyCoords(focused, focusedRect);//需要将当前的rect转换,到root中的坐标系中。
                //rect实际是坐标点,这里需要转换都根root,viewgroup中的focussearch中是遍历到根节点,以便统一坐标并从全局统筹去做做焦点派发。不然在一个viewgroup里,焦点就永远出不去了。
           } else { //......这部分忽略 } 
        }
        switch (direction) {
            case View.FOCUS_FORWARD:
            case View.FOCUS_BACKWARD:
                return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
                        direction);
            case View.FOCUS_UP:
            case View.FOCUS_DOWN:
            case View.FOCUS_LEFT:
            case View.FOCUS_RIGHT:
                return findNextFocusInAbsoluteDirection(focusables, root, focused,
                        focusedRect, direction);
            default:
                throw new IllegalArgumentException("Unknown direction: " + direction);
        }
    }

3.fire in the hole:开始了详细的盘问

    View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
            Rect focusedRect, int direction) {
        // initialize the best candidate to something impossible
        // (so the first plausible view will become the best choice)
        mBestCandidateRect.set(focusedRect);//给最终找到焦点的view配套用的rect,记录最合适的view的坐标
        switch(direction) {//记录用的,先给它移动一个身位,以便于比较时记录最合适的位置。
            case View.FOCUS_LEFT:
                mBestCandidateRect.offset(focusedRect.width() + 1, 0);
                break;
            case View.FOCUS_RIGHT:
                mBestCandidateRect.offset(-(focusedRect.width() + 1), 0);
                break;
            case View.FOCUS_UP:
                mBestCandidateRect.offset(0, focusedRect.height() + 1);
                break;
            case View.FOCUS_DOWN:
                mBestCandidateRect.offset(0, -(focusedRect.height() + 1));
        }

        View closest = null;

        int numFocusables = focusables.size();
        for (int i = 0; i < numFocusables; i++) {//开始遍历囚犯名单 arraylist
            View focusable = focusables.get(i);

            // only interested in other non-root views
            if (focusable == focused || focusable == root) continue;

            // get focus bounds of other view in same coordinate system
            focusable.getFocusedRect(mOtherRect);
            root.offsetDescendantRectToMyCoords(focusable, mOtherRect);//统一坐标

            if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {//比较找多最合适的,方向上最近的
                mBestCandidateRect.set(mOtherRect);//找到了,更新
                closest = focusable;//找到了,更新
            }
        }
        return closest;
    }

















                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值