重写RecyclerView让其在TV上运行
2017-03-19 21:46
176 查看
原生RecyclerView使用时的局限性
在手机端使用RecyclerView相信都很简单,但是在TV和盒子端就会出现不少的问题,例如焦点显示不全,无法定位到某个position等等
下面总结下在TV端使用使用RecyclerView时踩过的坑
(一) 解决RecyclerView刷新数据的时候,焦点错乱问题
下面是google官方给出的解决方案
1. adapter调用setHasStableIds(true)方法
2. 重写getItemId()方法,让每个view都有各自的id
3. RecyclerView的动画必须去掉
(二)解决长按遥控器,让RecyclerView快速滑动,焦点错乱问题
1. 由于长按遥控器的时候,RecyclerView找不到findFocusView为null,此时我就把RecyclerView的onKeyUp事件给屏蔽了;
2. 通过FocusFind类找到view,并让view请求焦点;
3.focusFind焦点失败之后,如果是让其滑动item的宽度(左后滑动)或者高度(上下滑动),因为有可能滑到下面还有可能有item,否则RecyclerView将不会继续滑动
(三)解决RecyclerView 定位到某个item不获取焦点问题
RecyclerView仅仅提供了一个smoothScrollToPosition(int position)方法,而且该方法,能滑到指定的position位置,但是该position处的item并没有获取焦点。
想起在使用LeanBack中的RecyclerView时,你会发现它提供了专门定位到某个position,并获取焦点的方法。但是然后沉下心把源码给看了,终于明白了其中的原理,原来它在定位到指定位置的item后,找到目标的item,并让其主动请求焦点。完美解决
(1) RecyclerView的smoothScrollToPosition方法,最终执行的是LayoutManger的smoothScrollToPosition方法。下面是RecyclerView的
smoothScrollToPosition的源代码
(2) RecyclerView弹性滑动是通过SmoothScroller来实现的,SmoothScroller中有onStart和onStop的回调,大胆猜测一下,onStop肯定是滑动结束的回调,于是找到targetView,并让其请求焦点,问题得以解决
为了方便大家我把代码上传到了github:https://github.com/Fredlxy/TVRecyclerView
在手机端使用RecyclerView相信都很简单,但是在TV和盒子端就会出现不少的问题,例如焦点显示不全,无法定位到某个position等等
下面总结下在TV端使用使用RecyclerView时踩过的坑
(一) 解决RecyclerView刷新数据的时候,焦点错乱问题
下面是google官方给出的解决方案
1. adapter调用setHasStableIds(true)方法
2. 重写getItemId()方法,让每个view都有各自的id
public abstract class BaseRecyclerAdapter<VH extends BaseRecyclerViewHolder, T> extends RecyclerView.Adapter<BaseRecyclerViewHolder> { public LayoutInflater mInflate; public List<T> mDataList; public BaseRecyclerAdapter(Context context, List<T> dataList) { this.mInflate = LayoutInflater.from(context); this.mDataList = dataList; setHasStableIds(true); } public void setDataList(List<T> dataList) { this.mDataList = dataList; } public void addDataList(List<T> dataList) { if (dataList != null && dataList.size()>0) { this.mDataList.addAll(dataList); } } @Override public int getItemCount() { return mDataList != null ? mDataList.size() : 0; } @Override public long getItemId(int position) { return position; } public List<T> getDataList() { return mDataList; } @Override public void onBindViewHolder(BaseRecyclerViewHolder holder, int position) { onBindBaseViewHolder((VH) holder, position); } public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); protected abstract void onBindBaseViewHolder(VH viewHolder, int position); /** * @param hasStableIds 有多个observer的话会报错 */ @Override public void setHasStableIds(boolean hasStableIds) { super.setHasStableIds(hasStableIds); } }
3. RecyclerView的动画必须去掉
setItemAnimator(null);
(二)解决长按遥控器,让RecyclerView快速滑动,焦点错乱问题
1. 由于长按遥控器的时候,RecyclerView找不到findFocusView为null,此时我就把RecyclerView的onKeyUp事件给屏蔽了;
2. 通过FocusFind类找到view,并让view请求焦点;
3.focusFind焦点失败之后,如果是让其滑动item的宽度(左后滑动)或者高度(上下滑动),因为有可能滑到下面还有可能有item,否则RecyclerView将不会继续滑动
@Override public boolean dispatchKeyEvent(KeyEvent event) { if (mInterceptLister != null && mInterceptLister.onIntercept(event)) { return true; } boolean result = super.dispatchKeyEvent(event); View focusView = this.findFocus(); if (focusView == null) { return result; } else { int dy = 0; int dx = 0; if (getChildCount() > 0) { View firstView = this.getChildAt(0); dy = firstView.getHeight(); dx = firstView.getWidth(); } if (event.getAction() == KeyEvent.ACTION_UP) { return true; } else { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_RIGHT: View rightView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT); Log.i(TAG, "rightView is null:" + (rightView == null)); if (rightView != null) { rightView.requestFocus(); return true; } else { this.smoothScrollBy(dx, 0); return true; } case KeyEvent.KEYCODE_DPAD_LEFT: View leftView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT); Log.i(TAG, "leftView is null:" + (leftView == null)); if (leftView != null) { leftView.requestFocus(); return true; } else { this.smoothScrollBy(-dx, 0); return true; } case KeyEvent.KEYCODE_DPAD_DOWN: View downView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_DOWN); Log.i(TAG, " downView is null:" + (downView == null)); if (downView != null) { downView.requestFocus(); return true; } else { this.smoothScrollBy(0, dy); return true; } case KeyEvent.KEYCODE_DPAD_UP: View upView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_UP); Log.i(TAG, "upView is null:" + (upView == null)); if (event.getAction() == KeyEvent.ACTION_UP) { return true; } else { if (upView != null) { upView.requestFocus(); return true; } else { this.smoothScrollBy(0, -dy); return true; } } } } } return result; }
(三)解决RecyclerView 定位到某个item不获取焦点问题
RecyclerView仅仅提供了一个smoothScrollToPosition(int position)方法,而且该方法,能滑到指定的position位置,但是该position处的item并没有获取焦点。
想起在使用LeanBack中的RecyclerView时,你会发现它提供了专门定位到某个position,并获取焦点的方法。但是然后沉下心把源码给看了,终于明白了其中的原理,原来它在定位到指定位置的item后,找到目标的item,并让其主动请求焦点。完美解决
(1) RecyclerView的smoothScrollToPosition方法,最终执行的是LayoutManger的smoothScrollToPosition方法。下面是RecyclerView的
smoothScrollToPosition的源代码
public void smoothScrollToPosition(int position) { if (mLayoutFrozen) { return; } if (mLayout == null) { Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " + "Call setLayoutManager with a non-null argument."); return; } mLayout.smoothScrollToPosition(this, mState, position); }
(2) RecyclerView弹性滑动是通过SmoothScroller来实现的,SmoothScroller中有onStart和onStop的回调,大胆猜测一下,onStop肯定是滑动结束的回调,于是找到targetView,并让其请求焦点,问题得以解决
/** * Created by liuyu on 17/2/8. * fix issue: RecyclerView 进行item定位的时候,item没有获取焦点 */ public class TvGridLayoutManager extends GridLayoutManager { public TvGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public TvGridLayoutManager(Context context, int spanCount) { super(context, spanCount); } public TvGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) { super(context, spanCount, orientation, reverseLayout); } /** * Base class which scrolls to selected view in onStop(). */ abstract class GridLinearSmoothScroller extends LinearSmoothScroller { public GridLinearSmoothScroller(Context context) { super(context); } /** * 滑动完成后,让该targetPosition 处的item获取焦点 */ @Override protected void onStop() { super.onStop(); View targetView = findViewByPosition(getTargetPosition()); if (targetView != null) { targetView.requestFocus(); } super.onStop(); } } /** * RecyclerView的smoothScrollToPosition方法最终会执行smoothScrollToPosition * @param recyclerView * @param state * @param position */ @Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { GridLinearSmoothScroller linearSmoothScroller = new GridLinearSmoothScroller(recyclerView.getContext()) { @Override public PointF computeScrollVectorForPosition(int targetPosition) { return computeVectorForPosition(targetPosition); } }; linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); } public PointF computeVectorForPosition(int targetPosition) { return super.computeScrollVectorForPosition(targetPosition); } }
为了方便大家我把代码上传到了github:https://github.com/Fredlxy/TVRecyclerView
相关文章推荐
- RecyclerView重写网格的布局管理器
- TV端使用RecyclerView时遇到的问题
- 通过重写OnScrollListener来监听RecyclerView是否滑动到底部
- android TV 通过按键控制RecyclerView中的item的选中,移动,点击功能
- recyclerview的点击事件的重写
- 通过重写OnScrollListener来监听RecyclerView是否滑动到底部
- AndroidTV开发(十一)Android Tv Launcher自定义RecyclerView
- AndroidTV开发(十一)Android Tv Launcher自定义RecyclerView
- Android RecyclerView中item焦点乱跳问题(适用于PDA以及TV等带方向键的安卓设备)
- RecyclerView重写线性布局管理器
- Android TV横向滚动网格布局——RecyclerView的使用
- Android Tv 焦点总结 TvRecyclerView
- TV中RecyclerView添加item的点击事件和删除item之后获取焦点解决
- RecyclerView 在tv端 焦点问题
- Android中RecyclerView在TV中处理控件焦点移动,EditText值的修改,CheckBox复用等问题解决
- 通过重写OnScrollListener来监听RecyclerView是否滑动到底部
- 重写recyclerview,支持下拉刷新、下拉加载更多、addHeader、addFoot、setEmptyView
- Android TV RecyclerView焦点移动飞框的实现
- android TV开发之RecyclerView的使用以及自动加载
- 【Android游戏开发十九】(必看篇)SurfaceView运行机制详解—剖析Back与Home按键及切入后台等异常处理!