Android TV Recyclerview长按或连续按键,焦点丢失(或者焦点跳跃)

原因分析

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,处理上大同小异。

 (3)对RecyclerView添加滚动监听

RecyclerView.addOnScrollListener(gridScrollListener),原理就是监听可见条目在RecyclerView已加载数据中位置,在合适时候提前去加载下一页数据,避免等滑动到最后再去加载或请求下一页,请求数据时间过长.

这里是我项目中的实现思路和代码,给我们的列表添加滚动监听如下

复制代码
 val gridScrollListener: RecyclerView.OnScrollListener = object : RecyclerView.OnScrollListener() {
        @RequiresApi(Build.VERSION_CODES.N)
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            val lastVisible = gridLayoutManager.findLastVisibleItemPosition()
            val itemCount = gridLayoutManager.itemCount
        if (lastVisible + gridLayoutManager.spanCount >= itemCount - 1) {
        //当最后的一条可见item位置比目前所有item的条数+spancount之和还大时,此时提前加载下一页
                loadMoreConferenceList()//加载下一页数据
            }
        }
    }
复制代码

 

  自定义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);
    }
}
复制代码

 



自定义GridLayoutManagerpublic
复制代码
class ScrollControlGridLayoutManager extends GridLayoutManager{
  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没有显示),

则滑动到那个位置,并使其显示获取焦点

 
posted @   瓜的呱  阅读(773)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 【.NET】调用本地 Deepseek 模型
点击右上角即可分享
微信分享提示