欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

解决RecyclerView聚焦时滚动到起始处的问题

程序员文章站 2022-05-04 20:01:24
...

最近在做EPG布局界面时,采用了一个Linearlayout布局嵌套多个RecyclerView来实现EPG通道信息展示的功能,但是在测试的时候发现,当RecyclerView左边子Item显示不全时,焦点从右侧移入时,RecyclerView会莫名其妙滚动到第一项,经过一番测试,发现是父布局LinearLayout聚焦在执行requestChildFocus函数时,聚焦的focusChild的index是-1,也就是没找到需要聚焦的子View,导致了RecyclerView莫名滚动到第一项,下面我们先从Android的焦点查找讲起。

当dispatchKeyEvent没有消耗掉KeyEvent,那么会由安卓系统来处理焦点移动,系统会通过view的focusSearch方法找到下一个获取焦点的View,然后调用requestFocus设置焦点。

 

1.view的focusFocus函数,不会直接去查找,而是会交给其parent(也就是ViewGroup)的focusSearch方法

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

ViewGroup的focusSearch()方法:

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

这里会判断是否为根布局,也就是顶层布局(DecoderView),如果是最后交给FocusFinder去查找

2.FocusFinder的findNextFocus函数

位于顶层的ViewGroup把自己和当前焦点(View)以及方向传入

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) {
        //1.优先从xml或者代码中指定focusid的View中找
        next = findNextUserSpecifiedFocus(root, focused, direction);
    }
    if (next != null) {
        return next;
    }
    ArrayList<View> focusables = mTempList;
    try {
        focusables.clear();
        root.addFocusables(focusables, direction);
        if (!focusables.isEmpty()) {
            // 2.其次,根据算法去找,原理就是找在方向上最近的View
            next = findNextFocus(root, focused, focusedRect, direction, focusables);
        }
    } finally {
        focusables.clear();
    }
    return next;
}

到这里,我们简单罗列下焦点的查找步骤:

focusSearch-》findNextFocus-》addFocusables-》findNextFocus

在没有消耗 dispatchKeyEvent的情况下:

FocusSearch 一层层上去,调用 FocusFinder.getInstance().findNextFocus… … 后,在 …addFocusables 下,将所有带焦点属性的 view 全部加到数组里面去,然后通用方向,位置等查找相近的view.

介绍到这里,我们已经有思路了,可以在LinearLayout再次聚焦时手动指定要聚焦的View,这样RecyclerView就不会莫名其妙滚动到第一项了,具体是重写addFocusables函数

@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
    //加一个标志位,用于手动聚焦操作
    if (needFocus) {
        needFocus = false;
        int childCount = getChildCount();
        if (childCount > focusIndex) {
            View view = getChildAt(focusIndex);
            if (view instanceof RecyclerView) {
                RecyclerView rv = (RecyclerView) view;
                RecyclerView.Adapter adapter = rv.getAdapter();
                if (adapter != null) {
                    views.add(view);
                    return;
                }
            }
        }
    }
    super.addFocusables(views, direction, focusableMode);
}