防止RecyclerView失去焦点

3
我正在开发一款Android TV应用程序,其中一个活动包含一些按钮。单击按钮后,RecyclerView(在同一个活动中)会被填充并自动聚焦。
问题是,当使用DPAD向按钮的方向导航时,RecyclerView会失去焦点而转移到按钮上。我希望防止这种行为,即防止RecyclerView失去焦点到活动上的其他内容。稍后,选择另一个按钮的唯一方法是点击返回按钮,这个我可以做到,没有问题。
这是我尝试过但未成功的方法:
<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()
  }

在第二种方法中,除非在RecyclerView中使用android:descendantFocusability ="blocksDescendants",否则FocusChangeListener不会被调用,但这会防止RecyclerView的项目获得焦点。
4个回答

23

首先,我在AndroidTv上工作了3年,与焦点管理作斗争,实际上相当简单,问题是......没有文档!

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

DPad导航实际上意味着焦点在一些视图之间变化。那么当你按下Dpad-Right时,谁应该决定焦点应该去哪里呢?包含这些视图的ViewGroup(父级)应该决定!

一些背景知识:当一个视图不知道焦点应该给谁时,它会请求ViewGroup来做出决定。因此,在大多数情况下,当您在一个视图上聚焦时,当您按下DpadRight时,其容器上调用focusSearch,并带有direction = 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"

注意:只要您知道,当View第一次获得焦点时,会调用该视图的一个方法,即:

protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)

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


1
太好了!这正是我为我的电视应用程序所寻找的。你真是我的救星。 - Muhammad Saad Rafique
你是一个救命恩人,谢谢。 - Mohammad Sommakia
它对我不起作用。我尝试了第一种方法,使用BrowseFrameLayoutHorizontalGridView作为BrowseFrameLayout的子项。 - AkshayT
关于第二个选项,mContainer是什么?假设我已经创建了一个自定义的LinearLayout作为父级,在其中添加此代码。而recycleView将成为自定义LinearLayout的子级。 - AkshayT
这对我来说不起作用,不幸的是。我认为问题不在于孩子或父母是否知道需要关注什么,而在于子视图是否能够聚焦。当你的Recyclerview项目特别大时,无法通过向下按键列表并聚焦尚未回收或在屏幕上可见的项目。因此,除非以某种方式允许它缓慢滚动以使项目恢复或创建,否则焦点将从Recyclerview中移开。或者增加可见视图的数量。 - 6rchid

1
我如何解决问题:
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();
    ...
}

0
无论你在Android TV应用中使用RecyclerView做什么,它都会失去焦点。唯一的解决方案是使用Leanback片段。

0

您可以通过在控件和RecyclerView上实现键监听器并手动管理此事来实现此目的。

在适配器类中添加以下方法,并定义每个要在每次单击时执行的步骤。它在我的应用程序中有效,我希望它也能帮助您。

@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) {

            }
        }
    });
}

1
不幸的是,这也没有起作用。当我按下按钮时,既没有触发FocusChangeListener也没有触发OnKeyListener - hiddeneyes02
在 XML 中,将 android:focusable="false" 设置为您不想获得焦点的控件,您可以分享您的代码吗? - Andrain

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