前言:网上看了很多博客,各种奇奇怪怪的方法都有,但是并没有什么卵用,写这篇博客主要就是记录一下关于要实现标题所述功能的一种实现方式(提供一种思路)
场景分析
在 Android TV 的开发过程中,经常要跟焦点打交道,焦点问题也是一个很烦人的问题,一个常见的需求是要有焦点记忆功能,也即焦点移动到列表中的某一项中,焦点移出去了,在回来时焦点还要定位到原来的子项上。这种需求常见于是列表是用 listview 或者 recyclerview 实现的。
下面是我画的一个草图:
简单说一下这个布局:左侧是一个用 RecyclerView 实现的菜单导航栏,右侧是一个随左侧导航栏选项点击变化而切换的 fragment 。
当我现在选中第 6 项按遥控器右键键时,焦点会落在右侧 fragment 左上角第一个控件上,那么此时再按左键,我希望焦点能够继续回到第 6 项这个位置,但根据系统的焦点分发原则,选项 1 会拿到焦点,这就给我带来了困扰,也是这篇博客要解决的问题。
实现思路
当焦点从 RecyclerView 上移出的时候,需要保存 RecyclerView 当前聚焦的 View ,下次 RecyclerVlew 将要获得焦点的时候,主动聚焦到上—次聚焦的 View 上。
听上去很简单,但是实现起来却比较棘手,这里肯定是重写 RecyclerView 会比较方便的,因为要让 RecyclerView 整个控件拿到焦点后,重写分发焦点给子项的逻辑代码。
具体代码实现
- 设置RecyclerView在优先于它的子View获取焦点
public void init(){
setItemAnimator(null);
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
setChildrenDrawingOrderEnabled(true);
this.setFocusable(true);
}
- 重写 RecycleView 的 requestChildFocus()方法记录获得焦点的 View
@Override
public void requestChildFocus(View child,View focus){
if(child!=null){
super.requestChildFocus(child, focus);
mLastFocusView = focus;
//取得获取焦点的item的position
mLastFocusPosition = getChildViewHolder(child).getAbsoluteAdapterPosition();
}
}
- 在 RecyclerView 内部焦点切换(相当于重写派发焦点)
@Override
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
LogUtil.i("views = " + views);
LogUtil.i("lastFocusView = " + mLastFocusView + " mLastFocusPosition = " + mLastFocusPosition);
if (this.hasFocus() || mLastFocusView == null) {
//在recyclerview内部焦点切换
super.addFocusables(views, direction, focusableMode);
} else {
//将当前的view放到Focusable views列表中,再次移入焦点时会取到该view,实现焦点记忆功能
views.add(getLayoutManager().findViewByPosition(mLastFocusPosition));
LogUtil.i("views.add(lastFocusView)");
}
}
当然需要先定义变量来保存上次获得焦点的位置:
private int mLastFocusPosition = 0;
private View mLastFocusView = null;
然后在布局文件中将<androidx.recyclerview.widget.RecyclerView>
替换成你自定义的这个 MyRecyclerView
就能看到修改完的效果啦
附:如果需要回到上次点击的位置,可以提供 setter 接口,自己在外部为 mLastFocusView 及 mLastFocusPosition 赋值。