RecyclerView的项失去焦点

16
在我的Fire TV应用中,我正在使用具有水平布局的recyclerview。使用dpad滚动是有效的,同时项目也获得了焦点。 但当我按住按钮时,会触发许多keydown事件,导致快速滚动,而项目失去焦点,无法再滚动,因为我recyclerview上方的另一个Textview正在获得焦点。 这看起来像一个bug。有没有解决方法?

3
对于那些会遇到同样问题的人,我找到了一个解决方法。我不再使用RecyclerView了,而是换成了这个HorizontalListView库:https://github.com/sephiroth74/HorizontalVariableListView,它满足了我的需求。 - pawlinsky
8个回答

9

我认为@tmm1的回答是目前为止最好的答案。我已经成功地实现了这个解决方法,解决了在用户使用DPAD快速滚动时,RecyclerView中加载的元素失去焦点的问题。

在我的RecyclerView适配器中,我使用LayoutInflater来膨胀我的元素视图,如下所示:

@Override
public ListItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
    View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_keyboard_key, viewGroup, false);
    return new ListItemViewHolder(itemView);
}

我的 item_keyboard_key.xml 最初是这样定义的

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_keyboard_key_layout"
    android:orientation="vertical"
    android:gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/item_keyboard_key_text"
        android:text="A"/>

</FrameLayout>

然后我创建了一个新的自定义FrameLayout类(FocusFixFrameLayout),扩展了FrameLayout,并将其用作根布局。我的item_keyboard_key.xml文件变成了:

<?xml version="1.0" encoding="utf-8"?>
<no.bluebit.views.FocusFixFrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_keyboard_key_layout"
    android:orientation="vertical"
    android:gravity="center"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/item_keyboard_key_text"
        android:text="A"/>

</no.bluebit.views.FocusFixFrameLayout>

我的FocusFixFrameLayout类如下所示。
public class FocusFixFrameLayout extends FrameLayout {

    public FocusFixFrameLayout(@NonNull Context context) {
        super(context);
    }

    public FocusFixFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public FocusFixFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public FocusFixFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
}

    @Override
    public void clearFocus() {
        if(this.getParent() != null)
            super.clearFocus();
    }
}

1
嗨,哈佛。这真的解决了我的问题。非常感谢你。你是救命恩人)) - Serg Burlaka
2
这解决了一些设备上的问题,但在其他设备上仍然存在(例如,在API 21上的Fire OS 5设备上)。 - joe1806772
我在使用这个时遇到了"java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.view.ViewGroup.getTouchscreenBlocksFocus()' on a null object reference"的错误。自从这个答案发布以来有什么变化吗? - Gustav P Svensson
3
我尝试使用我的ConstraintLayout,但它不起作用。 在某些时候,回收视图和整个屏幕完全失去焦点。 - kirkadev
1
尝试对我的RelativeLayout和RecyclerView进行调整后,它们开始比以前更经常失去焦点。 - PavelGP
对我完全没用。 - Trevor

5

我也面临着同样的问题,在我的Activity中(包含RecyclerView),我采用以下方式解决:

    private long mLastKeyDownTime = 0;
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        long current = System. currentTimeMillis();
        boolean res = false;
        if (current - mLastKeyDownTime < 300 ) {
            res = true;
        } else {
            res = super.onKeyDown(keyCode, event);
            mLastKeyDownTime = current;
        }
        return res;
    }

当你按下dpad上的按钮时,它可以避免快速滚动,并且在我的RecyclerView中焦点工作正常。


2
我在我的项目中测试了这个,但它并没有真正起作用。它会减轻问题,但问题仍然存在。 - Håvard Bakke
1
抱歉,但这并没有帮助。 - Serg Burlaka
1
如何在某些片段或视图中使用键监听器而不使用Activity的on方法? - A_rmas

5

parent 是从哪里来的? - mco
parent is defined on FrameLayout/ViewGroup/View - tmm1
我认为它没有在任何地方被定义,我的意思是有一个getParent()方法但是没有公共/受保护的parent变量。 - mco
2
我正在使用 Kotlin,所以 parent 调用了 getParent() - tmm1
嗨!对我来说,焦点没有移动到RecyclerView之外,但在某个时候,没有任何项具有焦点,似乎完全重置了。 - kirkadev

1

当我使用notifyItemChanged(position)时,我遇到了同样的问题。

通过这样发送一个空载荷和位置notifyItemChanged(position, Any())来解决问题。


0

2023年更新的答案:

当使用DPAD时,Recyclerview会失去焦点,这是一个已知的bug。我使用的一个小技巧是重写onBackPressed并添加以下内容:

@Override
public void onBackPressed() {
    Log.i("MainActivity", "getCurrentFocus(): "+getCurrentFocus());
    }

这个黑科技或代码行让我知道当我失去焦点时,通过按下返回键,你会得到一个带有类似提示的东西,告诉你谁拥有当前的焦点:
getCurrentFocus(): androidx.recyclerview.widget.RecyclerView{12c0bcc VFED.VC.. .F...... 0,0-1843,864 #7f0a016a app:id/recycler_view}

通过这个方法,你可以知道哪个视图获取了焦点,并且你可以为该视图添加一个onFocusChange监听器,或者完全禁用该视图的焦点功能。重要的是,在RecyclerView失去焦点时,你要找出谁获得了焦点。


0

尝试使用notifyItemChanged(position),这对我很有效。我在适配器内部调用了适配器,并且我的子适配器有多个EditText,当我调用notifyDataSetChanged时会失去焦点。因此,我用notifyItemChanged(Position)替换了它。

notifyItemChanged(position);

0
这是一个持续存在的问题,但我将在Android TV上分享我的解决方法。我已经尝试将项目包装在自定义FrameLayout中,并覆盖clearFocus,正如许多人建议的那样,但对于较大的RecyclerView项目,这并不总是有效。

当使用d-pad滚动RecyclerView时,焦点会丢失,因为它试图聚焦列表上的下一项,而该项似乎没有显示在屏幕上,也没有被回收,因此它会聚焦在屏幕上的其他现有视图。

所以这是我的方法:

override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
    val pos = getPosition(focused)
    val count = itemCount

    if (direction == View.FOCUS_DOWN && pos + 1 < count) {
        scrollToPosition(pos + 1)
    }
    return super.onInterceptFocusSearch(focused, direction)
}

我在自定义的LinearLayoutManager中重写了这个方法,用于垂直RecyclerView,以便将列表预滚动到当前焦点项旁边的下一项,以确保始终有可用的下一项可以聚焦。虽然不是最好的解决方案,但它能够工作并且避免了焦点重定向。
另一个选项是在滚动列表时增加可见项的数量 - 我不确定如何实现这一点。如果有人有更好的想法,请分享一下。希望这对某人有所帮助!

-1

我在为Android TV和leanback库工作的项目中遇到了同样的问题。

好消息是,流行的应用程序(如YouTube等)没有这个问题。我找到了有用的资源。经过调查,我制定了一个对我来说方便的解决方案。关键是使用Presenter而不是Adapter。以下是最简单的示例:

//Presenter class
public class ItemViewPresenter extends Presenter {

    @Override
    public final ViewHolder onCreateViewHolder(ViewGroup parent) {

        //I use data binding
        ItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item, parent, false);
        return new MyViewHolder(binding);
    }

    @Override
    public final void onBindViewHolder(ViewHolder viewHolder, Object item) {
        String text = (String) item;
        ((MyViewHolder) viewHolder).bind(text);
    }

    @Override
    public final void onUnbindViewHolder(ViewHolder viewHolder) {
    }

    private class MyViewHolder extends Presenter.ViewHolder {

        private final ItemBinding binding;

        private DiscoveryViewHolder(ItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }

        private void bind(String text) {

            binding.textView.setText(text);
        }
    }
}

//... 
private void setupRecyclerView() {

    RecyclerView recyclerView = _baseLayout.findViewById(R.id.recyclerView);

    ArrayObjectAdapter arrayObjectAdapter = new ArrayObjectAdapter(new ItemViewPresenter());

    ItemBridgeAdapter bridgeAdapter = new ItemBridgeAdapter(arrayObjectAdapter);

    recyclerView.setAdapter(bridgeAdapter);

    LinearLayoutManager layoutManager = new LinearLayoutManager(_baseLayout.getContext(), LinearLayoutManager.HORIZONTAL, false);
    recyclerView.setLayoutManager(layoutManager);

    List<Strings> textList = new ArrayList(1);
    textList.add("1");

    //add objects
    arrayObjectAdapter.addAll(0, textList);

    //clear objects
    //arrayObjectAdapter.clear();
}

3
问题是关于 RecyclerView 而不是 Leanback 库。 - A_rmas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接