防止 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
}
});
- 您可以创建一个自定义视图,扩展您正在为 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();
...
}
我正在开发这个 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
}
});
- 您可以创建一个自定义视图,扩展您正在为 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();
...
}