Android TV RecyclerView 焦点处理及获取焦点的Item保持在中间
来源:互联网 发布:招网络分销方案 编辑:程序博客网 时间:2024/06/06 07:31
原生RecyclerView 在Tv中的焦点处理很不好,经常找不到焦点或者焦点丢失。原因是因为当item未显示时即未加载时时不能获取焦点的。所以当我们按上下键时经常丢失焦点或者焦点乱跳。要解决这个问题我们必须要手动控制RecyclerView 的按键和焦点移动。
package com.phicomm.ottbox.view;import android.content.Context;import android.graphics.Rect;import android.os.Build;import android.support.v4.view.ViewCompat;import android.support.v7.widget.GridLayoutManager;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.util.AttributeSet;import android.util.Log;import android.view.FocusFinder;import android.view.KeyEvent;import android.view.MotionEvent;import android.view.View;import com.phicomm.ottbox.base.BaseFragment;/** * Created by root on 17-9-7. */public class TvRecyclerView extends RecyclerView { private static final String TAG = "TvRecyclerView"; private int mPosition; private BaseFragment mBindFragment; public TvRecyclerView(Context context) { this(context, null); } public TvRecyclerView(Context context, AttributeSet attrs) { this(context, attrs, -1); } public TvRecyclerView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs, defStyle); } private void init(Context context, AttributeSet attrs, int defStyle) { initView(); //initAttr(context, attrs); } public void setBindFragment(BaseFragment fragment) { this.mBindFragment = fragment; } 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); } private int getFreeWidth() { return getWidth() - getPaddingLeft() - getPaddingRight(); } private int getFreeHeight() { return getHeight() - getPaddingTop() - getPaddingBottom(); } @Override protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); } @Override public boolean hasFocus() { return super.hasFocus(); } @Override public boolean isInTouchMode() { // 解决4.4版本抢焦点的问题 if (Build.VERSION.SDK_INT == 19) { return !(hasFocus() && !super.isInTouchMode()); } else { return super.isInTouchMode(); } } @Override public void requestChildFocus(View child, View focused) { super.requestChildFocus(child, focused); } @Override public 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; } @Override public int getBaseline() { return -1; } @Override public void setLayoutManager(LayoutManager layout) { super.setLayoutManager(layout); } /** * 判断是垂直,还是横向. */ private boolean isVertical() { LayoutManager manager = getLayoutManager(); if (manager != null) { LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager(); return layout.getOrientation() == LinearLayoutManager.VERTICAL; } return false; } @Override protected int getChildDrawingOrder(int childCount, int i) { View view = getFocusedChild(); if (null != view) { mPosition = getChildAdapterPosition(view) - getFirstVisiblePosition(); if (mPosition < 0) { return i; } else { if (i == childCount - 1) { if (mPosition > i) { mPosition = i; } return mPosition; } if (i == mPosition) { return childCount - 1; } } } return i; } public int getFirstVisiblePosition() { if (getChildCount() == 0) return 0; else return getChildAdapterPosition(getChildAt(0)); } public int getLastVisiblePosition() { final int childCount = getChildCount(); if (childCount == 0) return 0; else return getChildAdapterPosition(getChildAt(childCount - 1)); } /** * 设置为0,这样可以防止View获取焦点的时候,ScrollView自动滚动到焦点View的位置 */ protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { return 0; } @Override public boolean dispatchKeyEvent(KeyEvent event) { boolean result = super.dispatchKeyEvent(event); View focusView = this.getFocusedChild(); if (mBindFragment != null) { mBindFragment.setCacheViewFromContent(focusView); } if (focusView == null) { return result; } else { 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); if (rightView != null) { rightView.requestFocus(); return true; } else { return false; } 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 { return false; } case KeyEvent.KEYCODE_DPAD_DOWN: if (isVisBottom(this)) { this.smoothScrollToPosition(getLastVisiblePosition()); return result; } View downView = FocusFinder.getInstance().findNextFocus(this, focusView, View.FOCUS_DOWN); Log.i(TAG, " downView is null:" + (downView == null)); if (downView != null) { downView.requestFocus(); int downOffset = downView.getTop() + downView.getHeight() / 2 - getHeight() / 2; this.smoothScrollBy(0, downOffset); return true; } else { 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 (upView != null) { upView.requestFocus(); int upOffset = getHeight() / 2 - (upView.getBottom() - upView.getHeight() / 2); this.smoothScrollBy(0, -upOffset); return true; } else { Log.i(TAG, "tab cache view"); if (mBindFragment != null) { mBindFragment.setmCacheViewFromTab(focusView); } return result;//返回false,否则第一行按上键回不到导航栏 } } } } return result; } @Override public boolean onInterceptTouchEvent(MotionEvent e) { return super.onInterceptTouchEvent(e); } //防止Activity时,RecyclerView崩溃 @Override protected void onDetachedFromWindow() { if (getLayoutManager() != null) { super.onDetachedFromWindow(); } } /** * 是否是最右边的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; } public interface OnInterceptListener { boolean onIntercept(KeyEvent event); } /** * 判断是否已经滑动到底部 * * @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; } }}
重点是在dispatchKeyEvent中,让我们下一个item直接获取焦点。不再依赖系统处理。
BaseFragment 为与之绑定的fragment,作用是当按上键离开fragment到导航栏时的焦点位置。实现记录焦点功能。
阅读全文
0 0
- Android TV RecyclerView 焦点处理及获取焦点的Item保持在中间
- Android TV开发:设置全局焦点框及listview中item的焦点获取
- TV中RecyclerView添加item的点击事件和删除item之后获取焦点解决
- TV焦点的获取
- Android中RecyclerView在TV中处理控件焦点移动,EditText值的修改,CheckBox复用等问题解决
- TV listview及焦点处理
- RecyclerView 在tv端 焦点问题
- Android TV RecyclerView焦点移动飞框的实现
- Android TV应用 RecyclerView 焦点乱跑问题解决
- Android RecyclerView中item焦点乱跳问题(适用于PDA以及TV等带方向键的安卓设备)
- Android TV 开发-listview(GridView)使用键盘获取焦点时,选中上次失去焦点时的item,而不是就近的item
- Android TV ListView列表焦点保留?ListView如何获取焦点?
- Android TV 控件获取焦点特效
- Android 事件传递与焦点处理(tv)
- android tv开发基础知识焦点处理
- RecyclerView抢夺item焦点的问题
- android TV 焦点 问题
- android TV 焦点 问题
- 上传文件到指定目录
- AngularJS Bootstrap
- SVN创建分支/合并分支/切换分支
- listView的header的宽高的解析问题
- 杭电1001
- Android TV RecyclerView 焦点处理及获取焦点的Item保持在中间
- The study of literation in Python(20170911)
- Python 装饰器记录总结 (终极版)
- 锁机制 CLH锁和MCS锁
- [ACM] 常数和语言基础
- Ubuntu 16.0.4环境下GTK3.0的安装,测试及错误解决
- 在数组 arr 末尾添加元素 item。不要直接修改数组 arr,结果返回新的数组
- laravel ORM 只开启created_at的几种方法
- Integer == 的使用