android 焦点获取流程

android一般都是手机或者平板,一般都是点击的时候获取焦点,当我们添加遥控或手柄支持焦点移动时,这个时候焦点的查找就比较明显了,那么Android的焦点是怎么查找的呢。

我们从handleImeFinishedEvent(ViewRootImpl.java)开始了解焦点的查找流程,handleImeFinishedEven是由dispatchImeFinishedEvent触发,dispatchImeFinishedEvent又是由InputMethodManager触发来的,

handleImeFinishedEvent中跟焦点相关的代码:

if (direction != 0) {
                View focused = mView.findFocus();//当前拥有焦点的控件
                if (focused != null) {
                    View v = focused.focusSearch(direction);//根据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));
                            finishInputEvent(q, true);
                            return;
                        }
                    }

                    // Give the focused view a last chance to handle the dpad key.
                    if (mView.dispatchUnhandledMove(focused, direction)) {//以前的控件 焦点改变事件
                        finishInputEvent(q, true);
                        return;
                    }
                }

这里先获取当前焦点控件,然后根据direction获取下一个最佳的控件,获取控件后调用他的requestFocus,并给前面的焦点控件一个机会处理失去焦点事件,看一下focusSearch

    public View focusSearch(int direction) {
        if (mParent != null) {//父控件不为空,调用它的focusSearch
            return mParent.focusSearch(this, direction);
        } else {
            return null;
        }
    }

一直调用parent的focusSearch,最终到

    public View focusSearch(View focused, int direction) {
        if (isRootNamespace()) {//已经是Root层 (installDecor  mDecor.setIsRootNamespace(true);)
            // 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) {//继续调用父控件的focusSearch
            return mParent.focusSearch(focused, direction);
        }
        return null;
    }

如果已经是根控件,调用FocusFinder的findNextFocus,最终调用它的findNextFocus

    private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
        View next = null;
        if (focused != null) {
            next = findNextUserSpecifiedFocus(root, focused, direction);//是xml里通过android:nextFocusUp="..."等或者代码特别指定的焦点顺序
        }
        if (next != null) {//已经找到
            return next;
        }
        ArrayList<View> focusables = mTempList;//mTempList
        try {
            focusables.clear();
            root.addFocusables(focusables, direction);//获取所有可以获取焦点的控件
            if (!focusables.isEmpty()) {
                next = findNextFocus(root, focused, focusedRect, direction, focusables);//查找下一个焦点控件
            }
        } finally {
            focusables.clear();
        }
        return next;
    }


先看一下该控件是否已经设置过它的焦点移动事件,indNextUserSpecifiedFocus就是干这个事的,此方法先去判断特定Id值是否存在,若存在则查询出Id对应的view.其实这些Id就是xml里通过android:nextFocusUp="..."等或者代码特别指定的焦点顺序.所以在此过程先判断,若存在,说明下个焦点已经找到,直接返回.,未找到,则调用findNextFocus继续查找

    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);
        } else {
            if (focusedRect == null) {
                focusedRect = mFocusedRect;
                // make up a rect at top left or bottom right of root
                switch (direction) {
                    case View.FOCUS_RIGHT:
                    case View.FOCUS_DOWN:
                        setFocusTopLeft(root, focusedRect);
                        break;
                    case View.FOCUS_FORWARD:
                        if (root.isLayoutRtl()) {
                            setFocusBottomRight(root, focusedRect);
                        } else {
                            setFocusTopLeft(root, focusedRect);
                        }
                        break;

                    case View.FOCUS_LEFT:
                    case View.FOCUS_UP:
                        setFocusBottomRight(root, focusedRect);
                        break;
                    case View.FOCUS_BACKWARD:
                        if (root.isLayoutRtl()) {
                            setFocusTopLeft(root, focusedRect);
                        } else {
                            setFocusBottomRight(root, focusedRect);
                        break;
                    }
                }
            }
        }

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

调用findNextFocusInAbsoluteDirection查找下一个焦点控件

    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);//设置mBestCandidateRect
        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++) {//查找最佳的焦点控件
            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)) {//比较和Best哪个更好
                mBestCandidateRect.set(mOtherRect);
                closest = focusable;//更合适
            }
        }
        return closest;//返回
    }

根据焦点控件的区域去查找一个合适的,具体查找,比较那个合适比较复杂,暂时还没看懂。

关于移动的时候有时候没有焦点,我们可以查看在findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction)这个函数中,root.addFocusables(focusables, direction);/可能获取到了很多有焦点的控件,最后找到的合适的控件可能不是我们想要的,这时我们可以把某些控件设置为不能获取焦点,这样我们移动焦点时,可能是我们想要的焦点(偷懒的做法)

        mDaySpinner.setFocusable(false);
        mMonthSpinner.setFocusable(false);
        mYearSpinner.setFocusable(false);
        mYearSpinnerInput.setSelection(0, 4); //获取焦点时选中的text




  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值