防止 Recyclerview 失去焦点

Prevent Recyclerview from losing focus

我正在开发这个 android 电视应用程序,其中有一个 activity 包含一些按钮。单击按钮后,RecyclerView(在同一个 activity 中)被填充并自动聚焦。

问题是当使用 DPAD 向按钮的方向导航时,RecyclerView 失去对按钮的焦点。我想防止这种行为,即防止 RecyclerView 失去焦点到 activity 上的任何其他内容。后来,select另一个按钮的唯一方法是单击后退按钮,我可以做到,没问题。

以下是我尝试过但没有奏效的方法:

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/categories_rv"
        android:layout_width="@dimen/categories_list_width"
        android:layout_height="wrap_content"
        android:focusable="true"
        android:nextFocusForward="@id/categories_rv"
        android:nextFocusUp="@id/categories_rv"
        android:nextFocusRight="@id/categories_rv"
        android:nextFocusLeft="@id/categories_rv"
        android:nextFocusDown="@id/categories_rv"
        android:focusableInTouchMode="true" />

还有这个:

 binding.categoriesRv.setOnFocusChangeListener { v, hasFocus ->
     if (!hasFocus)
        v.requestFocus()
  }

在第二种方式中,FocusChangeListener 永远不会被调用,除非我在 RecyclerView 中使用 android:descendantFocusability="blocksDescendants",但这会阻止 RecyclerView 的项目被聚焦。

如果在 android 电视应用程序中使用 recyclerview,无论您做什么,它都会失去焦点。唯一的解决方案是使用 Leanback 片段。

您可以通过在您的控件和 recyclerview 上实现关键侦听器并手动管理它来实现这一点。

在您的适配器中添加以下方法 class 并定义您希望在每次点击时执行的每个步骤。它在我的应用程序中工作,我希望它也能帮助你。

@Override
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    recyclerView.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {

            LinearLayoutManager lm = ((LinearLayoutManager) recyclerView.getLayoutManager());

            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                    //return false or true; // whatever your case is
                } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) { 
                  return false;

                } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {

                } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {

                } else if (keyCode == KeyEvent.KEYCODE_ENTER) {

                } else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
                }
            }
            return false;
        }
    });


    recyclerView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (hasFocus) {


            } else if (!hasFocus) {

            }
        }
    });
}

首先我在Android电视上工作了3年对抗焦点管理,其实很简单,问题是...没有文档!

在Android-TV 上管理方向键导航的正确方法实际上是使用focusSearch 方法。这是做什么的?

DPad导航其实就是焦点在一些View之间变化。那么,当您按下 Dpad-Right 时,应该由谁来决定焦点应该放在哪里?包含视图的 ViewGroup(父级)可以!

一点背景知识:当一个视图不知道谁应该获得焦点时,它会要求 ViewGroup 做出决定。因此,在大多数情况下,当您在关注视图时按下 DpadRight,focusSearch 会在其容器 上被调用,方向 = FOCUS_RIGHT (FOCUS_END) 等等。

您的解决方案非常简单。你有两种方式: 1. 将 Recyclerview 放在 BrowseFrameLayout 中然后你可以执行以下操作

BrowseFrameLayout browseFrameLayout = view.findViewById(R.id.recyclerview_container);
RecyclerView recyclerView = view.findViewById(R.id.categories_rv);
theBrowseFrameLayout.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
    @Override
    public View onFocusSearch(View focused, int direction) {
        if (recyclerView.hasFocus())
            return focused; // keep focus on recyclerview! DO NOT return recyclerview, but focused, which is a child of the recyclerview
        else
            return null; // someone else will find the next focus
    }
});
  1. 您可以创建一个自定义视图,扩展您正在为 recyclerview 使用的任何容器,并执行相同的操作,这里是一个更复杂的示例,我们使用 UI 具有顶部菜单和下面的各种片段它 https://gist.github.com/RedLann/bfaffc06eb0f571234c6e46b697d633b

注意:通常在 xml 我们在 recyclerview android:descendantFocusability="afterDescendants" 上使用 注意:正如您所知,当视图第一次获得焦点时,会调用该视图的一个方法,即:

protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)

注意:如果使用这种方法,焦点管理就会变得非常容易,每个父视图 (ViewGroup/Container) 都会为其直接子视图管理焦点,当它不知道该做什么时,它可以委托到它自己的父视图组返回 super.focusSearch

我是如何解决问题的:

private int focusedPosition;

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    final ItemViewHolder viewHolder = (ItemViewHolder) holder;
    viewHolder.llContainer.setOnClickListener(v -> {
         сlickCallback.itemClick(position);
         focusedPosition = position;
    });
     
    if (focusedPosition == position) viewHolder.llContainer.requestFocus();
    ...
}