在TV开发中RecycleView的使用是最让人头疼的经常会出现焦点丢失。因为当item未显示时不能获取焦点。所以当我们按上下键时经常丢失焦点或者焦点乱跳。要解决这个问题我们必须要手动控制RecyclerView 的按键和焦点移动。

所以我们这里需要需要自定义RecycleView。

代码如下,各个方法作用在注视中已添加:

public class TvRecyclerView extends RecyclerView
{//正常跟随滚动private static final int SCROLL_NORMAL = 0;//居中滚动private static final int SCROLL_FOLLOW = 1;//滚动模式private int scrollModel;//当前选中的positionprivate int mSelectedPosition = 0;//下一个聚焦的Viewprivate View mNextFocused;public TvRecyclerViewNew(Context context){this(context, null);}public TvRecyclerViewNew(Context context, AttributeSet attrs){this(context, attrs, -1);}public TvRecyclerViewNew(Context context, AttributeSet attrs, int defStyle){super(context, attrs, defStyle);init(context, attrs, defStyle);}/*** 初始化** @param context* @param attrs* @param defStyle*/private void init(Context context, AttributeSet attrs, int defStyle){initView();initAttr(attrs);}/*** 初始化View* 为避免recycleview焦点混乱常用的一些设置*/private void initView(){setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);setHasFixedSize(true);setWillNotDraw(true);setOverScrollMode(View.OVER_SCROLL_NEVER);setChildrenDrawingOrderEnabled(true);setClipChildren(false);setClipToPadding(false);setClickable(false);setFocusable(true);setFocusableInTouchMode(true);/**防止RecyclerView刷新时焦点不错乱bug的步骤如下:(1)adapter执行setHasStableIds(true)方法(2)重写getItemId()方法,让每个view都有各自的id(3)RecyclerView的动画必须去掉*/setItemAnimator(null);}/*** 初始化样式* 是否居中滚动* @param attrs*/private void initAttr(AttributeSet attrs){TypedArray typeArray = getContext().obtainStyledAttributes(attrs, R.styleable.TvRecyclerView);scrollModel = typeArray.getInteger(R.styleable.TvRecyclerView_scrollMode, 0);}/*** 恢复回收之前的状态* @param state*/@Overrideprotected void onRestoreInstanceState(Parcelable state){Bundle bundle = (Bundle) state;Parcelable superData = bundle.getParcelable("super_data");super.onRestoreInstanceState(superData);setItemSelected(bundle.getInt("select_pos", 0));}/*** 回收之前保存状态* @return*/@Overrideprotected Parcelable onSaveInstanceState(){Bundle bundle = new Bundle();Parcelable superData = super.onSaveInstanceState();bundle.putParcelable("super_data", superData);bundle.putInt("select_pos", mSelectedPosition);return bundle;}/*** 解决4.4版本抢焦点的问题* @return*/@Overridepublic boolean isInTouchMode(){if (Build.VERSION.SDK_INT == 19){return !(hasFocus() && !super.isInTouchMode());} else{return super.isInTouchMode();}}@Overridepublic void requestChildFocus(View child, View focused){super.requestChildFocus(child, focused);}@Overridepublic boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate){final int parentLeft = getPaddingLeft();final int parentRight = getWidth() - getPaddingRight();final int parentTop = getPaddingTop();final int parentBottom = getHeight() - getPaddingBottom();final int childLeft = child.getLeft() + rect.left;final int childTop = child.getTop() + rect.top;final int childRight = childLeft + rect.width();final int childBottom = childTop + rect.height();final int offScreenLeft = Math.min(0, childLeft - parentLeft);final int offScreenRight = Math.max(0, childRight - parentRight);final int offScreenTop = Math.min(0, childTop - parentTop);final int offScreenBottom = Math.max(0, childBottom - parentBottom);final boolean canScrollHorizontal = getLayoutManager().canScrollHorizontally();final boolean canScrollVertical = getLayoutManager().canScrollVertically();// Favor the "start" layout direction over the end when bringing one side or the other// of a large rect into view. If we decide to bring in end because start is already// visible, limit the scroll such that start won't go out of bounds.final int dx;if (canScrollHorizontal){if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL){dx = offScreenRight != 0 ? offScreenRight: Math.max(offScreenLeft, childRight - parentRight);} else{dx = offScreenLeft != 0 ? offScreenLeft: Math.min(childLeft - parentLeft, offScreenRight);}} else{dx = 0;}// Favor bringing the top into view over the bottom. If top is already visible and// we should scroll to make bottom visible, make sure top does not go out of bounds.final int dy;if (canScrollVertical){dy = offScreenTop != 0 ? offScreenTop : Math.min(childTop - parentTop, offScreenBottom);} else{dy = 0;}if (dx != 0 || dy != 0){if (immediate){scrollBy(dx, dy);} else{smoothScrollBy(dx, dy);}postInvalidate();return true;}return false;}/*** 判断是垂直,还是横向.*/private boolean isVertical(){LayoutManager manager = getLayoutManager();if (manager != null){LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager();return layout.getOrientation() == LinearLayoutManager.VERTICAL;}return false;}/*** 滚动的相关响应* computeScroll在父控件执行drawChild时,会调用这个方法*/@Overridepublic void computeScroll(){super.computeScroll();//滚动后更新当前选中的positionif (mNextFocused != null){mSelectedPosition = getChildAdapterPosition(mNextFocused);} else{mSelectedPosition = getChildAdapterPosition(getFocusedChild());}}/*** 返回迭代的绘制子类索引。如果你想改变子类的绘制顺序就要重写该方法* 提示:为了能够调用该方法,你必须首先调用setChildrenDrawingOrderEnabled(boolean)来允许子类排序** @param childCount 子类个数* @param i 当前迭代顺序* @return 绘制该迭代子类的索引*/@Overrideprotected int getChildDrawingOrder(int childCount, int i){View view = getFocusedChild();if (null != view){int position = getChildAdapterPosition(view) - getFirstVisiblePosition();if (position < 0){return i;} else{if (i == childCount - 1){if (position > i){position = i;}return position;}if (i == position){return childCount - 1;}}}return i;}@Overridepublic boolean dispatchKeyEvent(KeyEvent event){boolean result = super.dispatchKeyEvent(event);View focusView = this.getFocusedChild();if (focusView == null){return result;} else{if (event.getAction() == KeyEvent.ACTION_UP){//不能拦截KeyEvent.KEYCODE_BACK//否则onBackPress不会触发if(event.getKeyCode() == KeyEvent.KEYCODE_BACK){return super.dispatchKeyEvent(event);}else {return true;}} else{switch (event.getKeyCode()){case KeyEvent.KEYCODE_DPAD_RIGHT:View rightView = mNextFocused = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_RIGHT);setViewPosition(mNextFocused);if (rightView != null){rightView.requestFocus();return true;} else{return false;}case KeyEvent.KEYCODE_DPAD_LEFT:View leftView = mNextFocused = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_LEFT);setViewPosition(mNextFocused);if (leftView != null){mSelectedPosition = getChildAdapterPosition(leftView);} else{mSelectedPosition = getChildAdapterPosition(getFocusedChild());}if (leftView != null){leftView.requestFocus();return true;} else{return false;}case KeyEvent.KEYCODE_DPAD_DOWN:View downView = mNextFocused = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_DOWN);setViewPosition(mNextFocused);if (downView != null){downView.requestFocus();if (scrollModel == SCROLL_NORMAL){//跟随滚动直接返回truereturn true;} else{//居中滚动计算出滚动距离,将view滚动到中间int downOffset = downView.getTop() + downView.getHeight() / 2 - getHeight() / 2;this.smoothScrollBy(0, downOffset);return true;}} else{return isBottomEdge(getLayoutManager().getPosition(this.getFocusedChild()));}case KeyEvent.KEYCODE_DPAD_UP:View upView = mNextFocused = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_UP);setViewPosition(mNextFocused);if (upView != null){upView.requestFocus();if (scrollModel == SCROLL_NORMAL){return true;} else{int upOffset = getHeight() / 2 - (upView.getBottom() - upView.getHeight() / 2);this.smoothScrollBy(0, -upOffset);return true;}} else{return isTopEdge(getLayoutManager().getPosition(this.getFocusedChild())) ;}}}}return result;}private void setViewPosition(View mNextFocused){if(mNextFocused != null){mSelectedPosition = getChildAdapterPosition(mNextFocused);}else {mSelectedPosition = getChildAdapterPosition(getFocusedChild());}}//防止Activity时,RecyclerView崩溃@Overrideprotected void onDetachedFromWindow(){if (getLayoutManager() != null){super.onDetachedFromWindow();}}/*** 设置选中的item* @param position*/public void setItemSelected(int position){if (mSelectedPosition == position){return;}if (position >= getAdapter().getItemCount()){position = getAdapter().getItemCount() - 1;}mSelectedPosition = position;requestLayout();}/*** 是否是最右边的item,如果是竖向,表示右边,如果是横向表示下边** @param childPosition* @return*/public boolean isRightEdge(int childPosition){LayoutManager layoutManager = getLayoutManager();if (layoutManager instanceof GridLayoutManager){GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;GridLayoutManager.SpanSizeLookup spanSizeLookUp = gridLayoutManager.getSpanSizeLookup();int totalSpanCount = gridLayoutManager.getSpanCount();int totalItemCount = gridLayoutManager.getItemCount();int childSpanCount = 0;for (int i = 0; i <= childPosition; i++){childSpanCount += spanSizeLookUp.getSpanSize(i);}if (isVertical()){if (childSpanCount % gridLayoutManager.getSpanCount() == 0){return true;}} else{int lastColumnSize = totalItemCount % totalSpanCount;if (lastColumnSize == 0){lastColumnSize = totalSpanCount;}if (childSpanCount > totalItemCount - lastColumnSize){return true;}}} else if (layoutManager instanceof LinearLayoutManager){if (isVertical()){return true;} else{return childPosition == getLayoutManager().getItemCount() - 1;}}return false;}/*** 是否是最左边的item,如果是竖向,表示左方,如果是横向,表示上边** @param childPosition* @return*/public boolean isLeftEdge(int childPosition){LayoutManager layoutManager = getLayoutManager();if (layoutManager instanceof GridLayoutManager){GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;GridLayoutManager.SpanSizeLookup spanSizeLookUp = gridLayoutManager.getSpanSizeLookup();int totalSpanCount = gridLayoutManager.getSpanCount();int childSpanCount = 0;for (int i = 0; i <= childPosition; i++){childSpanCount += spanSizeLookUp.getSpanSize(i);}if (isVertical()){if (childSpanCount % gridLayoutManager.getSpanCount() == 1){return true;}} else{if (childSpanCount <= totalSpanCount){return true;}}} else if (layoutManager instanceof LinearLayoutManager){if (isVertical()){return true;} else{return childPosition == 0;}}return false;}/*** 是否是最上边的item,以recyclerview的方向做参考** @param childPosition* @return*/public boolean isTopEdge(int childPosition){LayoutManager layoutManager = getLayoutManager();if (layoutManager instanceof GridLayoutManager){GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;GridLayoutManager.SpanSizeLookup spanSizeLookUp = gridLayoutManager.getSpanSizeLookup();int totalSpanCount = gridLayoutManager.getSpanCount();int childSpanCount = 0;for (int i = 0; i <= childPosition; i++){childSpanCount += spanSizeLookUp.getSpanSize(i);}if (isVertical()){if (childSpanCount <= totalSpanCount){return true;}} else{if (childSpanCount % totalSpanCount == 1){return true;}}} else if (layoutManager instanceof LinearLayoutManager){if (isVertical()){return childPosition == 0;} else{return true;}}return false;}/*** 是否是最下边的item,以recyclerview的方向做参考** @param childPosition* @return*/public boolean isBottomEdge(int childPosition){LayoutManager layoutManager = getLayoutManager();if (layoutManager instanceof GridLayoutManager){GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;GridLayoutManager.SpanSizeLookup spanSizeLookUp = gridLayoutManager.getSpanSizeLookup();int itemCount = gridLayoutManager.getItemCount();int childSpanCount = 0;int totalSpanCount = gridLayoutManager.getSpanCount();for (int i = 0; i <= childPosition; i++){childSpanCount += spanSizeLookUp.getSpanSize(i);}if (isVertical()){//最后一行item的个数int lastRowCount = itemCount % totalSpanCount;if (lastRowCount == 0){lastRowCount = gridLayoutManager.getSpanCount();}if (childSpanCount > itemCount - lastRowCount){return true;}} else{if (childSpanCount % totalSpanCount == 0){return true;}}} else if (layoutManager instanceof LinearLayoutManager){if (isVertical()){return childPosition == getLayoutManager().getItemCount() - 1;} else{return true;}}return false;}/*** 判断是否已经滑动到底部** @param recyclerView* @return*/private boolean isVisBottom(RecyclerView recyclerView){LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();int visibleItemCount = layoutManager.getChildCount();int totalItemCount = layoutManager.getItemCount();if (visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1){return true;} else{return false;}}public int getFirstVisiblePosition(){if (getChildCount() == 0)return 0;elsereturn getChildAdapterPosition(getChildAt(0));}public int getLastVisiblePosition(){final int childCount = getChildCount();if (childCount == 0)return 0;elsereturn getChildAdapterPosition(getChildAt(childCount - 1));}private int getFreeWidth(){return getWidth() - getPaddingLeft() - getPaddingRight();}private int getFreeHeight(){return getHeight() - getPaddingTop() - getPaddingBottom();}public int getSelectedPosition(){return mSelectedPosition;}public void setSelectionPostion(int selectionPostion){mSelectedPosition = selectionPostion;}
}

最后一点不要忘记在attrs.xml中添加TvRecycelview样式:

<!--TvRecycelvie滚动--><attr name="scrollMode" ><enum name="normalScroll" value="0"/><enum name="followScroll" value="1"/></attr><!--TvRecycelview样式--><declare-styleable name="TvRecyclerView"><attr name="scrollMode"/></declare-styleable>

扫码关注公众号“伟大程序猿的诞生“,更多干货新鲜文章等着你~

公众号回复“资料获取”,获取更多干货哦~

有问题添加本人微信号“fenghuokeji996” 或扫描博客导航栏本人二维码

Android TV开发总结【RecycleView】相关推荐

  1. android tv 云播放器,Android TV开发总结(六)构建一个TV app的直播节目实例

    近年来,Android TV的迅速发展,传统的有线电视受到较大的冲击,在TV上用户同样也可以看到各个有线电视的直播频道,相对于手机,这种直播节目,体验效果更佳,尤其是一样赛事节目,大屏幕看得才够痛快, ...

  2. android 仿 tv 菜单,Android TV 开发之仿泰捷视频最新 TV 版 Metro UI 效果

    Some Android TV related Sample 更多TV相关,欢迎关注公众号: Android TV开发交流群:135622564 1.Imitation of tai jie late ...

  3. Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑

    原文:Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑 版权声明:我已委托"维权骑士"(rightknights.com)为我的文章进行维权行动.转载务必 ...

  4. 【Android TV 开发】焦点处理 ( 父容器与子组件焦点获取关系处理 | 不同电视设备上的兼容问题 | 触摸获取焦点 | 按键获取焦点 )

    Android TV 开发系列文章目录 [Android TV 开发]安卓电视调试 ( 开启网络远程调试 ) [Android TV 开发]焦点处理 ( 父容器与子组件焦点获取关系处理 | 不同电视设 ...

  5. android tv 菜单键,Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑

    前言:关于<TV Metro界面(仿泰捷视频TV版)源码解析>由于都是相关代码,就不发公众号了,有兴趣的可以看链接:http://blog.csdn.net/hejjunlin/artic ...

  6. android 按键分析,Android TV开发按键与焦点深入分析(四)

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 前面三篇都是从源码的角度分析按键事件.焦点变换的原理,作为应用层的开发者, 分析源码都是带着实际的开发困惑的,要不然谁没 ...

  7. Android TV开发:APP安装、ICON图标问题

    使用AndroidX版本的Android Studio开发的面向TV的APK,安装后,在电视默认主屏没有显示该APP的ICON,是怎么回事? 一开始没有注意到电视的Android版本,安装APK时出现 ...

  8. 聊聊真实的 Android TV 开发技术栈

    智能电视越来越普及了,华为说四月发布智能电视跳票了,一加也说今后要布局智能电视,在智能电视方向,小米已经算是先驱了.但是还有不少开发把智能电视简单的理解成手机屏幕的放大,其实这两者并不一样. 一.序 ...

  9. Android TV 开发有关PopupWindow的KeyListener(手机也能用)

    转载请标明原地址:Android TV 开发有关PopupWindow的KeyListener(手机也能用)_高磊的专栏-CSDN博客 现在这个公司主要是做智能电视视频方面.有硬件电视盒子,APP开发 ...

  10. Android TV开发总结(四)通过RecycleView构建一个TV app列表页(仿腾讯视频TV版)

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52854131 前言:昨晚看锤子手 ...

最新文章

  1. CDN中,字体文件的跨域问题和解决
  2. php怎么刷新缓存,ZZ PHP立即刷新缓存(输出)的方法
  3. <java并发编程实践>读书笔记一
  4. 计算机创建任务计划,win7系统创建任务计划的方法 如何创建任务计划
  5. 【机器学习】机器学习可视化利器--Yellowbrick
  6. 图解javascript中this指向
  7. Notification之 - Android5.0实现原理(二)
  8. JS的自定义事件(观察者模式)
  9. 【CodeForces - 527C】Glass Carving(线段树或者SBT或者set)
  10. exls导入数据库 php_PHP读取excel文件并导入数据库
  11. BZOJ2424 [HAOI2010]订货
  12. 周末巨献:100+诡异的数据集,20万Eclipse Bug、死囚遗言
  13. Selenium FirePath的安装和使用
  14. 1.爬虫基础——了解html什么是爬虫
  15. 学习笔记----网站的优化(五)---CDN加速
  16. mysql命令行导入csv_MySQL命令行导入CSV文件
  17. 使用SharePoint管理中心管理服务
  18. 西门子博途系列学习笔记SCL(一)
  19. 【文档】AOA-with-DW1000_V1.1
  20. 利用QT实现瀑布图、Lofar谱图、色谱图,热力图(二)

热门文章

  1. 面试题:+=(python中列表+=操作)
  2. pscp使用详解 Windows与Linux文件互传工具
  3. SpringMVC学习笔记(1)-SpringMVC介绍
  4. sparse-to-dense.pytorch 代码主流程
  5. tensorflow学习笔记(3)梯度下降法进行曲线拟合和线性回归
  6. Django搭建的个人博客
  7. 博文搬家到公众号了~~~
  8. 【贪心 哈夫曼树】bzoj2923: [Poi1998]The lightest language
  9. SqlServer 的一个坑
  10. Int.Parse()、Convert.toInt32()和(int)区别