原因分析
RecyclerView设置适配器后,将数据填充进去,并不会将所有item的view都创建出来,一般只会创建一个屏幕的Item,当长按或者快速按下键时,Recyclerview来不及创建即将获取焦点的view,导致焦点丢失
解决方法
有两种思路:
(1)控制按键速度
这里有两种具体实现策略:
一种是记录上次按下时间,然后在下次按下时间时去计算与上次按下时间的间隔,如果间隔小于某个值(比如1秒),我们就不处理该次按下事件(具体是在dispatchKeyEvent、或者onKeyDown、或者onKeyUp中return true),
这里需要注意的是,只有Activity有这些处理方法,Fragment的按键拦截也需要在依附的Activity中进行处理
另一种是是在dispatchKeyEvent、或者onKeyDown、或者onKeyUp中,拿到KeyEvent。通过KeyEvent.getRepeatCount()计算是否是长按,当getRepeatCount()大于0则是长按,值越大表示用户长按事件越长。
然后我们可以拦截长按事件(同样return true)
(2)对Recyclerview设置LayoutManager,在LayoutManager中控制焦点
在RecyclerView的LayoutManager中,有这样一个方法onInterceptFocusSearch(View focused, int direction),这个方法就是用于寻找焦点的。当遇到长按或者连续按键焦点飞掉的情况时,需要重载RecyclerView的LayoutManager,重写此方法。
在实践中有两种具体情况:网格布局和线性布局,其实就是对RecyclerView设置LinearLayoutManager或GridLayoutManager,处理上大同小异。
自定义LinearLayoutManager
public class ScrollControlLayoutManager extends LinearLayoutManager {
private static final String TAG = "ScrollControlLayoutMana";
public ScrollControlLayoutManager(Context context) {
super(context);
}
public ScrollControlLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public ScrollControlLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
@Override
public View onInterceptFocusSearch(View focused, int direction) {
int count = getItemCount();//获取item的总数
int fromPos = getPosition(getFocusedChild());//当前焦点的位置
int lastVisibleItemPos = findLastVisibleItemPosition();//最新的已显示的Item的位置
switch (direction) {//根据按键逻辑控制position
case View.FOCUS_RIGHT:
case View.FOCUS_DOWN:
fromPos++;
break;
case View.FOCUS_LEFT:
case View.FOCUS_UP:
fromPos--;
break;
}
Log.i(TAG, "onInterceptFocusSearch , fromPos = " + fromPos + " , count = " + count+" , lastVisibleItemPos = "+lastVisibleItemPos);
// if(fromPos < 0 || fromPos > count ) {
// //如果下一个位置<0,或者超出item的总数,则返回当前的View,即焦点不动
// return focused;
// } else {
// //如果下一个位置大于最新的已显示的item,即下一个位置的View没有显示,则滑动到那个位置,让他显示,就可以获取焦点了
// if (fromPos >= 0 && fromPos < count && fromPos > lastVisibleItemPos) {
// scrollToPosition(fromPos);
// }
// }
if (fromPos >= 0 && fromPos < count && fromPos > lastVisibleItemPos) {
scrollToPosition(fromPos);
}
return super.onInterceptFocusSearch(focused, direction);
}
}
自定义GridLayoutManager
public class ScrollControlGridLayoutManager extends GridLayoutManager {标签:count,TV,焦点,lastVisibleItemPos,int,fromPos,Android,public,View From: https://www.cnblogs.com/terrorists/p/18110021
private static final String TAG = "ScrollControlLayoutMana";
public ScrollControlGridLayoutManager(Context context, int span) {
super(context, span);
}
public ScrollControlGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
@Override
public View onInterceptFocusSearch(View focused, int direction) {
int count = getItemCount();//获取item的总数
int fromPos = getPosition(getFocusedChild());//当前焦点的位置
int lastVisibleItemPos = findLastVisibleItemPosition();//最新的已显示的Item的位置
switch (direction) {//根据按键逻辑控制position
case View.FOCUS_RIGHT:
case View.FOCUS_DOWN:
fromPos+=getSpanCount();
break;
case View.FOCUS_LEFT:
case View.FOCUS_UP:
fromPos-=getSpanCount();
break;
}
Log.i(TAG, "onInterceptFocusSearch , fromPos = " + fromPos + " , count = " + count+" , lastVisibleItemPos = "+lastVisibleItemPos);
// if(fromPos < 0 || fromPos > count ) {
// //如果下一个位置<0,或者超出item的总数,则返回当前的View,即焦点不动
// return focused;
// } else {
// //如果下一个位置大于最新的已显示的item,即下一个位置的View没有显示,则滑动到那个位置,让他显示,就可以获取焦点了
// if (fromPos < count && fromPos > lastVisibleItemPos) {
// scrollToPosition(fromPos);
// }
// }
if (fromPos >= 0 && fromPos > lastVisibleItemPos) {
scrollToPosition(fromPos);
}
return super.onInterceptFocusSearch(focused, direction);
}}
总结一下核心逻辑就是在下一次滚动前,计算即将显示的item位置,如果下一个位置大于最新的已显示的item,即下一个位置的View没有显示,
则滑动到那个位置(防止),让他显示,就可以获取焦点了