Android TV中RecyclerView循环切换

前言

最近,在做一个菜单功能,其实就是一个RecyclerView的列表,需要做循环。因为在TV上,涉及焦点问题,所以跟手机还是有些许不同,遇到了一些问题,网上只有一篇相关,所以,完成了功能之后,自己来总结分享一下。

解决方案

第一种:
在adapter里面设置第一个和最后一个view的key监听事件,scrollToPosition,然后给recyclerView添加OnScrollListener,然后根据不同的方向,选择第一个还是最后一个view来请求焦点。

这种是参看Android TV中实现RecyView循环功能,不过也是有点问题,如果recyclerview没有滚动的话就有问题。(解决呢就是,判断一下是否能滚动,也就是子view的总高度是否大于recycler的高度,是滚动之后requestFocus,还是直接requestFocus)

第二种:(推荐,最简单的一种)
在adapter里面设置第一个和最后一个view的key监听事件,然后scrollToPosition,然后requestFocus,不过这里需要延迟执行

public class MenuMainAdapter extends RecyclerView.Adapter<MenuMainAdapter.ViewHolder> {

  private OnCircleListener mOnCircleListener;

    @Override
    public void onBindViewHolder(@NonNull MenuMainAdapter.ViewHolder holder, int position) {
      ...
        if (mOnCircleListener != null) {
            holder.itemView.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    if (holder.getAdapterPosition() == 0) {
                        if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
                            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                                mOnCircleListener.onUpKey();
                                return true;
                            }
                        }
                    } else if (holder.getAdapterPosition() == data.size() - 1) {
                        if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
                            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                                mOnCircleListener.onDownKey();
                                return true;
                            }
                        }
                    }
                    return false;
                }
            });
        }
    }


    public interface OnCircleListener {
        void onUpKey();

        void onDownKey();
    }

    public void setOnCircleListener(OnCircleListener mOnCircleListener) {
        this.mOnCircleListener = mOnCircleListener;
    }

  adapter.setOnCircleListener(new MenuMainAdapter.OnCircleListener() {
            @Override
            public void onUpKey() {
                recyclerView.scrollToPosition(adapter.getItemCount() - 1);
                getSafetyHandler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        requestFocus(adapter.getItemCount() - 1);
                    }
                }, 100);
            }

            @Override
            public void onDownKey() {
                recyclerView.scrollToPosition(0);
                getSafetyHandler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        requestFocus(0);
                    }
                }, 100);
            }
        });
  public void requestFocus(int position) {
        View view = recyclerView.getChildAt(position);
        LinearLayoutManager llM = (LinearLayoutManager) recyclerView.getLayoutManager();
        if (view != null) {
            view.requestFocus();
        } else if (llM.findViewByPosition(position) != null) {
            llM.findViewByPosition(position).requestFocus();
        } else {
            recyclerView.requestFocus();
        }
    }

过程分析

看到上面的第二种解决方法里的requestFocus方法

  public void requestFocus(int position) {
        View view = recyclerView.getChildAt(position);
        LinearLayoutManager llM = (LinearLayoutManager) recyclerView.getLayoutManager();
        if (view != null) {
            view.requestFocus();
        } else if (llM.findViewByPosition(position) != null) {
            llM.findViewByPosition(position).requestFocus();
        } else {
            recyclerView.requestFocus();
        }
    }
为什么要延迟100毫秒?
原因:

首先我们先说几个注意的问题:
1、recyclerView的childrenCount个数跟adapter的data个数不一定是相等的(因为复用)
2、getChildAt使用adapter的position是不可靠的,可能获取到的为null,如果data有100个,一屏幕只能显示10个,那么childrenCount只有10个。如果超过了,自然获取为null
3、layoutManager的findViewByPosition方法也是不可靠的,可能获取到的为null,可以看一下源码

public View findViewByPosition(int position) {
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                ViewHolder vh = getChildViewHolderInt(child);
                if (vh == null) {
                    continue;
                }
                if (vh.getLayoutPosition() == position && !vh.shouldIgnore()
                        && (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) {
                    return child;
                }
            }
            return null;
        }

这里可以看出,它是从childView里面去找现在显示的子view绑定的holder是否跟position相等,如果是才返回,不然返回null。所以,如果position对应的view如果没有显示出来(或者说被自己绑定在使用)就返回null。

我们延迟100毫秒,就是想要保证对应position的view(比如最后一个),已经显示在界面上了,然后再去让它requestFocus(position),才能找到它本身绑定的view,进行requesFocus,才能够正常显示。这个100毫秒,也可以自行调测,找出一个完美的值。

看第一种方法里面,为啥要给recyclerView添加onScrollListener并且在IDLE的时候去处理请求focus,就是想要在滚动完毕之后再去请求,但是操作有点复杂了。

最后还要注意一点:

有些人想要使用recycler的SmoothScrollToPotion,这个方法呢,也可以用,如果你的遥控器操作不会一直按着,然后让recycler飞快地滚动这种情况呢,是可以用的。
如果是有的话,那么使用起来就会有一些小问题,会出现乱滚的现象,因为smooth的滚动,不是即时的,是会有一定时间的,所以,这个时间差就会导致代码调用出现一些问题。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RecyclerViewAndroid的一个高级视图组件,用于展示大量数据的列表和网格视图。它拥有比ListView更多的灵活性和性能。 在Android Studio使用RecyclerView需要以下步骤: 1. 在build.gradle文件添加RecyclerView依赖: ``` implementation 'androidx.recyclerview:recyclerview:1.1.0' ``` 2. 在布局文件添加RecyclerView: ``` <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 3. 创建RecyclerView的Adapter类来管理数据和视图: ``` public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { private List<MyData> mData; public MyAdapter(List<MyData> data) { mData = data; } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_item_layout, parent, false); return new MyViewHolder(view); } @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { MyData data = mData.get(position); holder.mTextView.setText(data.getText()); holder.mImageView.setImageResource(data.getImageResource()); } @Override public int getItemCount() { return mData.size(); } public static class MyViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public ImageView mImageView; public MyViewHolder(@NonNull View itemView) { super(itemView); mTextView = itemView.findViewById(R.id.text); mImageView = itemView.findViewById(R.id.image); } } } ``` 4. 在Activity或Fragment设置RecyclerView的LayoutManager和Adapter: ``` RecyclerView recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); List<MyData> data = getData(); MyAdapter adapter = new MyAdapter(data); recyclerView.setAdapter(adapter); ``` 以上是使用RecyclerView的基本步骤,具体可以根据需要进行更多的自定义和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值