[Android学习笔记]理解焦点处理原理的相关记录

焦点处理相关记录

以下所涉及的焦点部分,只是按键移动部分,不明确包含Touch Focus部分

需解决问题

控件的下一个焦点是哪?

分析思路

当用户通过按键(遥控器等)触发焦点切换时,事件指令会通过底层进行一系列处理。 在ViewRootImpl.java中有一个方法,deliverKeyEventPostIme(...),因为涉及到底层代码,所以没有详细的跟踪分析此方法的调用逻辑,根据网上的资料,按键相关的处理会经过此方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void deliverKeyEventPostIme(QueuedInputEvent q) {
    ...
    // Handle automatic focus changes.
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        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;
            ...
        }
        if (direction != 0) {
            View focused = mView.findFocus();
            if (focused != null) {
                View v = focused.focusSearch(direction);
                if (v != null && v != focused) {
                   .....
                    if (v.requestFocus(direction, mTempRect)) {
                        ...finishInputEvent(q, true);
                        return;
                    }
                }
                ...
            }
        }
由此方法可以看出,最主要的两个核心过程:
1
2
View v = focused.focusSearch(direction);
v.requestFocus(direction, mTempRect)
接下来详细的分析下,看看过程中进行了什么操作

具体分析

在具体分析前,首先我们先明确下相关变量的定义

View mView : 主体View[DecorView]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//一般把主View“DecorView”添加到WindowManagerImpl中(通过addView)
//WindowManagerImpl.java
    private void addView(View view...) {
        ViewRootImpl root;
        root = new ViewRootImpl(view.getContext());
        ...
        root.setView(view, wparams, panelParentView);
        ...
    }<br>
//ViewRootImpl.java
public void setView(View view....) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            ...
        }
    ...
    }
}
所以mView是一个DecorView类型的变量.

View focused :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    View focused = mView.findFocus();<br>
    //PhoneWindow.java
    private final class DecorView extends FrameLayout implements RootVie.... {
        ...
    }<br>
    //FrameLayout.java
    public class FrameLayout extends ViewGroup {
        ...
    }<br>
    //ViewGroup.java
    //mFocused记录的是当前被焦点选中的view
    @Override
    public View findFocus() {
    if (DBG) {
        System.out.println("Find focus in " + this + ": flags="
                + isFocused() + ", child=" + mFocused);
    }
    if (isFocused()) {
        return this;
    }
    if (mFocused != null) {
        return mFocused.findFocus();
    }
    return null;
}
所以最终得到的focused为当前页面中得到焦点的view.

在明确的相关变量后,我们开始View v = focused.focusSearch(direction)的具体分析.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//View.java
public View focusSearch(int direction) {
//如果存在父控件,则执行父控件的focusSearch方法
   if (mParent != null) {
         return mParent.focusSearch(this, direction);
     } else {
         return null;
     }
 }
 //ViewGroup.java
 public View focusSearch(View focused, int direction) {
     //判断是否为顶层布局,若是则执行对应方法,若不是则继续向上寻找,说明会从内到外的一层层进行判断,直到最外层的布局为止
     if (isRootNamespace()) {
         return FocusFinder.getInstance().findNextFocus(this, focused, direction);
     } else if (mParent != null) {
         return mParent.focusSearch(focused, direction);
     }
     return null;
 }

说明在这个过程中,其实是从里层开始一直遍历到最外层布局,然后在最外层布局将处理交给了FocusFinder中的方法.

1
FocusFinder.getInstance().findNextFocus(this, focused, direction);
那我们来看看此方法具体做了什么操作
1
2
3
4
//FocusFinder.java
public final View findNextFocus(ViewGroup root, View focused, int direction) {
    return findNextFocus(root, focused, null, direction);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//FocusFinder.java
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
    View next = null;
    if (focused != null) {
        next = findNextUserSpecifiedFocus(root, focused, direction);
    }
    if (next != null) {
        return next;
    }
    ArrayList<View> focusables = mTempList;
    try {
        focusables.clear();
        root.addFocusables(focusables, direction);
        if (!focusables.isEmpty()) {
            next = findNextFocus(root, focused, focusedRect, direction, focusables);
        }
    } finally {
        focusables.clear();
    }
    return next;
}
发现在findNextFocus的执行过程的开始,先执行了findNextUserSpecifiedFocus(...)方法,由代码可以看出,此方法先去判断特定Id值是否存在,若存在则查询出Id对应的view.其实这些Id就是xml里通过android:nextFocusUp="..."等或者代码特别指定的焦点顺序.所以在此过程先判断,若存在,说明下个焦点已经找到,直接返回.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//FocusFinder.java
private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
    // check for user specified next focus
    View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
    if (userSetNextFocus != null && userSetNextFocus.isFocusable()
            && (!userSetNextFocus.isInTouchMode()
                    || userSetNextFocus.isFocusableInTouchMode())) {
        return userSetNextFocus;
    }
    return null;
}<br>
//View.java
View findUserSetNextFocus(View root, int direction) {
    switch (direction) {
        case FOCUS_LEFT:
            if (mNextFocusLeftId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusLeftId);
        case FOCUS_RIGHT:
            if (mNextFocusRightId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusRightId);
        case FOCUS_UP:
            if (mNextFocusUpId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusUpId);
        case FOCUS_DOWN:
            if (mNextFocusDownId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusDownId);
        case FOCUS_FORWARD:
            if (mNextFocusForwardId == View.NO_ID) return null;
            return findViewInsideOutShouldExist(root, mNextFocusForwardId);
        case FOCUS_BACKWARD: {
            if (mID == View.NO_ID) return null;
            final int id = mID;
            return root.findViewByPredicateInsideOut(this, new Predicate<View>() {
                @Override
                public boolean apply(View t) {
                    return t.mNextFocusForwardId == id;
                }
            });
        }
    }
    return null;
}
如果上面过程没有查询到,则会执行到findNextFocus(...)方法.在这个方法中,先通过offsetDescendantRectToMyCoords(...)方法获得焦点控件的位置矩阵.然后通过比较得到下一个焦点的控件。具体的比较规则可以查看findNextFocusInRelativeDirection(...)方法与findNextFocusInAbsoluteDirection(...)方法.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//FocusFinder.java
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);
    }
}

结论

查找焦点的过程,主要是从View的focusSearch(...)方法开始,从当前焦点开始逐层往外,最终在最外层布局执行FocusFinder中的核心方法来获得下个焦点所在的视图view.

如果需要指定跳转,可以在逐层focusSearch(...)的时候,返回特定的view

[Android学习笔记]理解焦点处理原理的相关记录,布布扣,bubuko.com

[Android学习笔记]理解焦点处理原理的相关记录

上一篇:Android Ubuntu 12.04 源码环境搭建


下一篇:基于android的远程视频监控系统