android TV端 新手入门必备RecyclerView快速滑动不丢焦点,不需要限速!!!.当前焦点自动居中.
TV端焦点乱飞一直是很头疼的事情,现在给大家推荐一个不需要限速的TvRecyclerView简单好用,让你的TV端告别卡顿.还能有效减少在低配置机型上ANR的几率.tv端焦点移动限速代码,某些特定条件下需要加上限速的朋友可以用以下代码,在BaseActivity中加上private long mLastKeyDownTime = 0;/*** 限速时间,可按需求增加或减...
·
TV端焦点乱飞一直是很头疼的事情,现在给大家推荐一个不需要限速的TvRecyclerView简单好用,让你的TV端告别卡顿.还能有效减少在低配置机型上ANR的几率.
tv端焦点移动限速代码,某些特定条件下需要加上限速的朋友可以用以下代码,在BaseActivity中加上
private long mLastKeyDownTime = 0;
/**
* 限速时间,可按需求增加或减少
*/
public int mTimeIntervalBetween = 150;
@Override
public boolean dispatchKeyEvent (KeyEvent event){
//兼容手柄确定键。
if(event.getKeyCode() == KeyEvent.KEYCODE_BUTTON_A){
return super.dispatchKeyEvent (event);
}
if (event.getAction () == KeyEvent.ACTION_DOWN){
long current = System.currentTimeMillis ();
boolean res;
if (current - mLastKeyDownTime < mTimeIntervalBetween ){
res = true;
return res;
} else{
// TODO: 2019/6/13 有操作后将无操作时间重置为0
mNoOperatingTime = 0;
mLastKeyDownTime = current;
boolean b = rewriteOnKeyDown (event.getKeyCode () , event);
if (b){
return true;
} else{
return super.dispatchKeyEvent (event);
}
}
}
return super.dispatchKeyEvent (event);
}
接下来是RecyclerView的代码,用此替换系统的RecyclerView可以让你在RecyclerView不管怎么飞速滑动都不会丢失焦点,让你体验飞一般的丝滑.还能让你当前获得焦点的那个居中显示.不需要的朋友可以把requestChildRectangleOnScreen方法注释掉就行.
废话不多说直接上代码
package com.hulian.newos.viw
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import com.blankj.utilcode.util.LogUtils;
import com.hulian.newos.Const;
import java.util.ArrayList;
/**
* ================================================
*
* @author 作 者:
* 版 本:1.0
* 创建日期:2020/1/2
* 描 述:
* 修订历史:
* ================================================
*/
public class TvXRecyclerView extends RecyclerView implements View.OnFocusChangeListener{
private int mSelectedPosition = 0;
private int mSelectedItemOffsetStart;
private int mSelectedItemOffsetEnd;
private View mChild;
/**
* 滑动次数
*/
public int mScrollNumber = 0;
/**
* 是否在滑动中
*/
public boolean mIsScrolling;
/**
* 最左边的view是否消耗掉按键,屏幕的上下左右
*/
private boolean mIsHandLastLeftKey = true, mIsHandLastRightKey = true, mIsHandLastUpKey = true, mIsHandLastDownKey = true;
// /**
// * 快速滑动不获焦开关
// */
// private boolean mIsLongPressDoesNotGainFocus = false;
/**
* 按钮事件
*/
private int mKeyEventCode;
/**
* 左右滑动的时候不进行rv拖动,默认为进行居中滑动
*/
private boolean mLeftRightNoScroll = false;
/**
* 子adapter当前选中的position
*/
private int mChildAdapterPosition;
/**
* 设置居中偏移量
*/
private int mDyOffset;
private final Rect mTempRect = new Rect ();
/**
* 设置居中偏移量
*
* @param dyOffset
*/
public void setDyOffset (int dyOffset){
mDyOffset = dyOffset;
}
public int getChildAdapterPosition (){
return mChildAdapterPosition;
}
// public void setLongPressDoesNotGainFocus (boolean longPressDoesNotGainFocus){
// mIsLongPressDoesNotGainFocus = longPressDoesNotGainFocus;
// }
public void setLeftRightNoScroll (boolean leftRightNoScroll){
mLeftRightNoScroll = leftRightNoScroll;
}
/**
* 是否拦截住不让出去
* @param mIsHandLastLeftKey
* @param mIsHandLastRightKey
* @param mIsHandLastUpKey
* @param mIsHandLastDownKey
*/
public void setHandLastKey (boolean mIsHandLastLeftKey , boolean mIsHandLastRightKey , boolean mIsHandLastUpKey , boolean mIsHandLastDownKey){
this.mIsHandLastLeftKey = mIsHandLastLeftKey;
this.mIsHandLastRightKey = mIsHandLastRightKey;
this.mIsHandLastUpKey = mIsHandLastUpKey;
this.mIsHandLastDownKey = mIsHandLastDownKey;
}
public TvXRecyclerView (Context context){
super (context);
init ();
}
public TvXRecyclerView (Context context , @Nullable AttributeSet attrs){
super (context , attrs);
init ();
}
public TvXRecyclerView (Context context , @Nullable AttributeSet attrs , int defStyle){
super (context , attrs , defStyle);
init ();
}
private void init (){
setPreserveFocusAfterLayout (false);
setDescendantFocusability (FOCUS_AFTER_DESCENDANTS);
setChildrenDrawingOrderEnabled (true);
// 自身不作onDraw处理
setWillNotDraw (true);
setHasFixedSize (true);
setOverScrollMode (View.OVER_SCROLL_NEVER);
setClipChildren (false);
setClipToPadding (false);
setFocusable (true);
setFocusableInTouchMode (true);
//启用子视图排序功能
//取消item动画
setItemAnimator (null);
}
@Override
public void requestChildFocus (View child , View focused){
if (null != child){
mChild = child;
mSelectedItemOffsetStart = ! isVertical () ? (getFreeWidth () - child.getWidth ()) : (getFreeHeight () - child.getHeight ());
mSelectedItemOffsetStart /= 2;
mSelectedItemOffsetEnd = mSelectedItemOffsetStart;
}
super.requestChildFocus (child , focused);
}
@Override
public void addFocusables (ArrayList <View> views , int direction , int focusableMode){
View view = getLayoutManager ().findViewByPosition (mChildAdapterPosition);
if (hasFocus () || mChildAdapterPosition < 0 || view == null){
super.addFocusables (views , direction , focusableMode);
} else if (view.isFocusable ()){
//将当前的view放到Focusable views列表中,再次移入焦点时会取到该view,实现焦点记忆功能
//实际上使用用处有但是没有想象中的好,也可以用
LogUtils.e ("实现焦点记忆功能" , views.size () , direction , focusableMode);
views.add (view);
} else{
super.addFocusables (views , direction , focusableMode);
}
}
private int getFreeWidth (){
return getWidth () - getPaddingLeft () - getPaddingRight ();
}
private int getFreeHeight (){
return getHeight () - getPaddingTop () - getPaddingBottom ();
}
@Override
protected int getChildDrawingOrder (int childCount , int i){
int position = mSelectedPosition;
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;
}
@Override
public void onDraw (Canvas c){
// mSelectedPosition = getChildAdapterPosition(getFocusedChild());
mSelectedPosition = indexOfChild (getFocusedChild ());
super.onDraw (c);
}
@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 - mSelectedItemOffsetStart);
final int offScreenRight = Math.max (0 , childRight - parentRight + mSelectedItemOffsetEnd);
final int offScreenTop = Math.min (0 , childTop - parentTop - mSelectedItemOffsetStart);
final int offScreenBottom = Math.max (0 , childBottom - parentBottom + mSelectedItemOffsetEnd);
final boolean canScrollHorizontal = getLayoutManager ().canScrollHorizontally ();
final boolean canScrollVertical = getLayoutManager ().canScrollVertically ();
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;
}
int dy;
if (canScrollVertical){
dy = offScreenTop != 0 ? offScreenTop : Math.min (childTop - parentTop , offScreenBottom);
} else{
dy = 0;
}
try{
if ((dx != 0 || dy != 0)){
if (mLeftRightNoScroll && (mKeyEventCode == KeyEvent.KEYCODE_DPAD_LEFT || mKeyEventCode == KeyEvent.KEYCODE_DPAD_RIGHT)){
return true;
} else{
mScrollNumber++;
if (immediate){
scrollBy (dx , dy + mDyOffset);
} else{
smoothScrollBy (dx , dy + mDyOffset , new DecelerateInterpolator (1.5f));
}
}
// 重绘
// .是为了选中item置顶,具体请参考getChildDrawingOrder方法
postInvalidate ();
return true;
}
} catch (Exception e){
e.printStackTrace ();
}
return false;
}
/**
* 判断是垂直,还是横向.
*/
private boolean isVertical (){
LayoutManager manager = getLayoutManager ();
if (manager != null){
LinearLayoutManager layout = (LinearLayoutManager) getLayoutManager ();
return layout.getOrientation () == LinearLayoutManager.VERTICAL;
}
return false;
}
@Override
public boolean dispatchKeyEvent (KeyEvent event){
try{
int keyCode = event.getKeyCode ();
mKeyEventCode = keyCode;
View mVideoFocusView = getFocusedChild ();
mChildAdapterPosition = getLayoutManager ().getPosition (mVideoFocusView);
LogUtils.e ("mChildAdapterPosition" + mChildAdapterPosition);
if (event.getAction () == KeyEvent.ACTION_DOWN){
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
|| keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_UP){
View nextFocusView = null;
try{
switch (keyCode){
case KeyEvent.KEYCODE_DPAD_LEFT:
if (mIsHandLastLeftKey){
nextFocusView = FocusFinder.getInstance ().findNextFocus (this , findFocus () , View.FOCUS_LEFT);
if (nextFocusView == null){
LogUtils.e ("keyEvent拦截======左");
return true;
}
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (mIsHandLastRightKey){
nextFocusView = FocusFinder.getInstance ().findNextFocus (this , findFocus () , View.FOCUS_RIGHT);
if (nextFocusView == null){
LogUtils.e ("keyEvent拦截======右");
return true;
}
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (mIsHandLastDownKey){
nextFocusView = FocusFinder.getInstance ().findNextFocus (this , findFocus () , View.FOCUS_DOWN);
if (nextFocusView == null){
LogUtils.e ("keyEvent拦截======下");
return true;
}
}
break;
case KeyEvent.KEYCODE_DPAD_UP:
if (mIsHandLastUpKey){
nextFocusView = FocusFinder.getInstance ().findNextFocus (this , findFocus () , View.FOCUS_UP);
if (nextFocusView == null){
LogUtils.e ("keyEvent拦截======上");
return true;
}
}
break;
default:
break;
}
} catch (Exception e){
e.printStackTrace ();
}
}
}
} catch (Exception e){
e.printStackTrace ();
}
return super.dispatchKeyEvent (event);
}
@Override
public void onScrollStateChanged (int state){
super.onScrollStateChanged (state);
switch (state){
case RecyclerView.SCROLL_STATE_SETTLING:
// TODO: 2020/4/23 滑动中
mIsScrolling = true;
break;
case RecyclerView.SCROLL_STATE_IDLE:
mIsScrolling = false;
// TODO: 2020/4/23 滑动停止
mScrollNumber = 0;
break;
default:
break;
}
}
public boolean isScrolling (){
return getScrollState () != SCROLL_STATE_IDLE;
}
/**
* 滚动到并选中某个位置
*
* @param position
* @param isRequestFocus 是否选中获焦
* @param isDelayed 是否需要延时(滑动距离过长的时候稍微加上延时能够更好的聚焦)
*/
public void setScrollingSelectPosition (final int position , boolean isRequestFocus , boolean isDelayed){
mSelectedPosition = position;
// TODO: 2020/6/23 滚动到第几个栏目修改
TvSmoothScroller scroller = new TvSmoothScroller (getContext () , mSelectedItemOffsetStart);
scroller.setTargetPosition (position);
getLayoutManager ().startSmoothScroll (scroller);
if (isRequestFocus){
postDelayed (new Runnable (){
@Override
public void run (){
try{
View view = getLayoutManager ().findViewByPosition (position);
view.requestFocus ();
} catch (Exception e){
e.printStackTrace ();
}
}
} , isDelayed ? 400 : 0);
}
}
/**
* 判断是否已经滑动到底部
*
* @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;
}
}
/**
* 是否是最右边的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;
}
@Override
public void onFocusChange (View v , boolean hasFocus){
LogUtils.e (hasFocus);
//判断是否开启焦点记忆
if (hasFocus){
setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
if (mChildAdapterPosition > 0){
View viewByPosition = getLayoutManager ().findViewByPosition (mChildAdapterPosition);
if (viewByPosition != null){
viewByPosition.requestFocus ();
}
}
} else{
setDescendantFocusability (ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
}
private class TvSmoothScroller extends LinearSmoothScroller{
private boolean mIsSmooth;
private int mOffset;
public TvSmoothScroller (Context context , int offset){
super (context);
mOffset = offset;
}
@Override
protected void onTargetFound (View targetView , State state , Action action){
if (null != getLayoutManager ()){
getDecoratedBoundsWithMargins (targetView , mTempRect);
mOffset = (getLayoutManager ().canScrollHorizontally () ? (getFreeWidth () - mTempRect.width ())
: (getFreeHeight () - mTempRect.height ())) / 2;
}
super.onTargetFound (targetView , state , action);
}
@Override
public int calculateDtToFit (int viewStart , int viewEnd , int boxStart , int boxEnd , int snapPreference){
int dt = boxStart - viewStart + mOffset;
return dt;
}
}
如果遇到版本高一点的recycleview需要更改他的layoutmanager滑动方式
package com.hulian.newos.views;
import android.content.Context;
import android.graphics.Rect;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import com.blankj.utilcode.util.LogUtils;
public class TVGridLayoutManager extends GridLayoutManager{
private TvXRecyclerView mRecyclerView;
public TVGridLayoutManager (TvXRecyclerView recyclerView , int spanCount){
super (recyclerView.getContext () , spanCount);
mRecyclerView = recyclerView;
}
@Override
public void onLayoutChildren (RecyclerView.Recycler recycler , RecyclerView.State state){
try{
super.onLayoutChildren (recycler , state);
} catch (IndexOutOfBoundsException e){
e.printStackTrace ();
}
}
/**
* 滑动更改LinearLayoutManager一样改这里
* @param parent
* @param child
* @param rect
* @param immediate
* @param focusedChildVisible
* @return
*/
@Override
public boolean requestChildRectangleOnScreen (RecyclerView parent , View child , Rect rect , boolean immediate , boolean focusedChildVisible){
return mRecyclerView.requestChildRectangleOnScreen (child , rect , immediate);
}
/**
* 禁用预测动画。在RecyclerView中有一个bug,导致视图
* 正在重新加载以从内部回收器堆栈中拉出无效的ViewHolders,如果
* 自ViewHolder被回收以来,适配器大小减少了。
*/
@Override
public boolean supportsPredictiveItemAnimations (){
return false;
}
}
更多推荐
已为社区贡献1条内容
所有评论(0)