导航到列表末尾时,如何保持 RecyclerView 的最后一项焦点?
How to keep the last item's focus of RecyclerView when navigating to the end of the list?
我在我的电视开发中使用了水平方向的 RecyclerView,它由方向键控制从左到右导航列表。导航到列表的最右侧时,RecyclerView 的最后一项总是失去焦点。
那么如何在导航到列表末尾时保持最后一项的焦点?
我深入研究了RecyclerView的源代码,我在LayoutManager中找到了onInterceptFocusSearch方法,在RecyclerView的内部class。
/**
* This method gives a LayoutManager an opportunity to intercept the initial focus search
* before the default behavior of {@link FocusFinder} is used. If this method returns
* null FocusFinder will attempt to find a focusable child view. If it fails
* then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)}
* will be called to give the LayoutManager an opportunity to add new views for items
* that did not have attached views representing them. The LayoutManager should not add
* or remove views from this method.
*
* @param focused The currently focused view
* @param direction One of { @link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
* {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
* {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
* @return A descendant view to focus or null to fall back to default behavior.
* The default implementation returns null.
*/
public View onInterceptFocusSearch(View focused, int direction) {
return null ;
}
这让 LayoutManager 有机会在使用 FocusFinder 的默认行为之前拦截初始焦点搜索。
所以我重写了下面的 onInterceptFocusSearch ,并为我的 RecylerView 使用了 CustomGridLayoutManager,它工作起来很迷人。
public class CustomGridLayoutManager extends android.support.v7.widget.GridLayoutManager {
public CustomGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super (context, attrs, defStyleAttr, defStyleRes);
}
public CustomGridLayoutManager(Context context, int spanCount) {
super (context, spanCount);
}
public CustomGridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout) {
super (context, spanCount, orientation, reverseLayout);
}
@Override
public View onInterceptFocusSearch(View focused, int direction) {
int pos = getPosition(focused);
int count = getItemCount();
int orientation = getOrientation();
**********
do some logic
what i did was return the focused View when the focused view is the last item of RecyclerView.
**********
return super .onInterceptFocusSearch(focused, direction);
}
}
受this issues启发,还有另一种解决方法:
在 RecyclerView.Adapter<ViewHolder>
int focusPos;
@Override
public void onBindViewHolder(ComposeViewHolder holder,
final int position) {
....
if (focusPos == position) { // focus last clicked view again
holder.imageView.requestFocus();
}
....
holder.imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
....
focusPos = position;
notifyDataSetChanged();
}
});
}
如果您使用的是 BaseGridView
的子类,例如 HorizontalGridView
或 VerticalGridView
,请设置一个 onKeyInterceptListener
来吞下列表末尾的移动键.例如,使用 HorizontalGridView
:
grid.setOnKeyInterceptListener { event ->
val focused = grid.focusedChild
event?.keyCode == KEYCODE_DPAD_RIGHT && grid.layoutManager.getPosition(focused) == grid.adapter.itemCount-1
}
如果您直接使用 RecyclerView
,则使用带有自定义 LinearLayoutManager
的 onInterceptFocusSearch
。例如,对于 LinearLayoutManager.VERTICAL
列表:
list.layoutManager = object: LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) {
override fun onInterceptFocusSearch(focused: View?, direction: Int): View? {
if (direction == View.FOCUS_DOWN) {
val pos = getPosition(focused)
if (pos == itemCount-1)
return focused
}
if (direction == View.FOCUS_UP) {
val pos = getPosition(focused)
if (pos == 0)
return focused
}
return super.onInterceptFocusSearch(focused, direction)
}
}
我正在为 Kotlin 添加一个答案。
如果您在每个地方都将覆盖基本布局,那么您的代码会变得复杂得多,尤其是因为在使用 TV 时您需要向布局管理器添加一些行为。
我用 D-PAD 在小米电视棒和 X62 Max 上检查过这个,也用模拟器,它有效。
所以我建议创建一个 class 这样的:
class TvLinearLayoutManager(
context: Context?,
orientation: Int,
reverseLayout: Boolean) : LinearLayoutManager(context, orientation, reverseLayout) {
override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
return if (
// This prevent focus jumping near border items
(getPosition(focused)==itemCount-1 && direction == View.FOCUS_RIGHT) ||
(getPosition(focused)==0 && direction == View.FOCUS_LEFT)
)
focused
else
super.onInterceptFocusSearch(focused, direction)
}}
我在我的电视开发中使用了水平方向的 RecyclerView,它由方向键控制从左到右导航列表。导航到列表的最右侧时,RecyclerView 的最后一项总是失去焦点。
那么如何在导航到列表末尾时保持最后一项的焦点?
我深入研究了RecyclerView的源代码,我在LayoutManager中找到了onInterceptFocusSearch方法,在RecyclerView的内部class。
/**
* This method gives a LayoutManager an opportunity to intercept the initial focus search
* before the default behavior of {@link FocusFinder} is used. If this method returns
* null FocusFinder will attempt to find a focusable child view. If it fails
* then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)}
* will be called to give the LayoutManager an opportunity to add new views for items
* that did not have attached views representing them. The LayoutManager should not add
* or remove views from this method.
*
* @param focused The currently focused view
* @param direction One of { @link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
* {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
* {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
* @return A descendant view to focus or null to fall back to default behavior.
* The default implementation returns null.
*/
public View onInterceptFocusSearch(View focused, int direction) {
return null ;
}
这让 LayoutManager 有机会在使用 FocusFinder 的默认行为之前拦截初始焦点搜索。
所以我重写了下面的 onInterceptFocusSearch ,并为我的 RecylerView 使用了 CustomGridLayoutManager,它工作起来很迷人。
public class CustomGridLayoutManager extends android.support.v7.widget.GridLayoutManager {
public CustomGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super (context, attrs, defStyleAttr, defStyleRes);
}
public CustomGridLayoutManager(Context context, int spanCount) {
super (context, spanCount);
}
public CustomGridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout) {
super (context, spanCount, orientation, reverseLayout);
}
@Override
public View onInterceptFocusSearch(View focused, int direction) {
int pos = getPosition(focused);
int count = getItemCount();
int orientation = getOrientation();
**********
do some logic
what i did was return the focused View when the focused view is the last item of RecyclerView.
**********
return super .onInterceptFocusSearch(focused, direction);
}
}
受this issues启发,还有另一种解决方法:
在 RecyclerView.Adapter<ViewHolder>
int focusPos;
@Override
public void onBindViewHolder(ComposeViewHolder holder,
final int position) {
....
if (focusPos == position) { // focus last clicked view again
holder.imageView.requestFocus();
}
....
holder.imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
....
focusPos = position;
notifyDataSetChanged();
}
});
}
如果您使用的是 BaseGridView
的子类,例如 HorizontalGridView
或 VerticalGridView
,请设置一个 onKeyInterceptListener
来吞下列表末尾的移动键.例如,使用 HorizontalGridView
:
grid.setOnKeyInterceptListener { event ->
val focused = grid.focusedChild
event?.keyCode == KEYCODE_DPAD_RIGHT && grid.layoutManager.getPosition(focused) == grid.adapter.itemCount-1
}
如果您直接使用 RecyclerView
,则使用带有自定义 LinearLayoutManager
的 onInterceptFocusSearch
。例如,对于 LinearLayoutManager.VERTICAL
列表:
list.layoutManager = object: LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) {
override fun onInterceptFocusSearch(focused: View?, direction: Int): View? {
if (direction == View.FOCUS_DOWN) {
val pos = getPosition(focused)
if (pos == itemCount-1)
return focused
}
if (direction == View.FOCUS_UP) {
val pos = getPosition(focused)
if (pos == 0)
return focused
}
return super.onInterceptFocusSearch(focused, direction)
}
}
我正在为 Kotlin 添加一个答案。
如果您在每个地方都将覆盖基本布局,那么您的代码会变得复杂得多,尤其是因为在使用 TV 时您需要向布局管理器添加一些行为。
我用 D-PAD 在小米电视棒和 X62 Max 上检查过这个,也用模拟器,它有效。
所以我建议创建一个 class 这样的:
class TvLinearLayoutManager(
context: Context?,
orientation: Int,
reverseLayout: Boolean) : LinearLayoutManager(context, orientation, reverseLayout) {
override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
return if (
// This prevent focus jumping near border items
(getPosition(focused)==itemCount-1 && direction == View.FOCUS_RIGHT) ||
(getPosition(focused)==0 && direction == View.FOCUS_LEFT)
)
focused
else
super.onInterceptFocusSearch(focused, direction)
}}