Android TV端的(RecyclerView)水平滚动焦点错乱问题
package com.hhzt.iptv.ui.customview; import android.content.Context;
import android.content.res.TypedArray;
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.hhzt.iptv.R;
import com.hhzt.iptv.lvb_w.interfaces.OnInterceptListener;
import com.hhzt.iptv.lvb_w.utils.VirturlKeyPadCtr; /**
* Created by Administrator on 2017/6/7.
*/ public class TvRecyclerView extends RecyclerView {
private static final String TAG = "TvRecyclerView"; private int position; //焦点是否居中
private boolean mSelectedItemCentered; private int mSelectedItemOffsetStart; private int mSelectedItemOffsetEnd; //分页的时候使用
private int mLoadMoreBeforehandCount = 0; 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);
} 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 void initAttr(Context context, AttributeSet attrs) {
if (attrs != null) {
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.TvRecyclerView);
/**
* 如果是towWayView的layoutManager
*/
final String name = a.getString(R.styleable.TvRecyclerView_tv_layoutManager); mSelectedItemCentered = a.getBoolean(R.styleable.TvRecyclerView_tv_selectedItemCentered, false); mLoadMoreBeforehandCount = a.getInteger(R.styleable.TvRecyclerView_tv_loadMoreBeforehandCount, 0); mSelectedItemOffsetStart = a.getDimensionPixelSize(R.styleable.TvRecyclerView_tv_selectedItemOffsetStart, 0); mSelectedItemOffsetEnd = a.getDimensionPixelSize(R.styleable.TvRecyclerView_tv_selectedItemOffsetEnd, 0); a.recycle();
}
} 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();
} //return super.isInTouchMode();
} @Override
public void requestChildFocus(View child, View focused) { if (null != child) {
if (mSelectedItemCentered) {
mSelectedItemOffsetStart = !isVertical() ? (getFreeWidth() - child.getWidth()) : (getFreeHeight() - child.getHeight());
mSelectedItemOffsetStart /= 2;
mSelectedItemOffsetEnd = mSelectedItemOffsetStart;
}
}
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 - 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(); // 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);
}
// 重绘是为了选中item置顶,具体请参考getChildDrawingOrder方法
postInvalidate();
return true;
} return false;
} @Override
public int getBaseline() {
return -1;
} public int getSelectedItemOffsetStart() {
return mSelectedItemOffsetStart;
} public int getSelectedItemOffsetEnd() {
return mSelectedItemOffsetEnd;
} @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;
} /**
* 设置选中的Item距离开始或结束的偏移量;
* 与滚动方向有关;
* 与setSelectedItemAtCentered()方法二选一
*
* @param offsetStart
* @param offsetEnd 从结尾到你移动的位置.
*/
public void setSelectedItemOffset(int offsetStart, int offsetEnd) {
setSelectedItemAtCentered(false);
mSelectedItemOffsetStart = offsetStart;
mSelectedItemOffsetEnd = offsetEnd;
} /**
* 设置选中的Item居中;
* 与setSelectedItemOffset()方法二选一
*
* @param isCentered
*/
public void setSelectedItemAtCentered(boolean isCentered) {
this.mSelectedItemCentered = isCentered;
} @Override
protected int getChildDrawingOrder(int childCount, int i) {
View view = getFocusedChild();
if (null != view) { position = getChildAdapterPosition(view) - getFirstVisiblePosition();
if (position < 0) {
return i;
} else {
if (i == childCount - 1) {//这是最后一个需要刷新的item
if (position > i) {
position = i;
}
return position;
}
if (i == position) {//这是原本要在最后一个刷新的item
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));
} /***********
* 按键加载更多 start
**********/ private OnLoadMoreListener mOnLoadMoreListener; public interface OnLoadMoreListener {
void onLoadMore();
} public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.mOnLoadMoreListener = onLoadMoreListener;
} private OnInterceptListener mInterceptLister; public void setOnInterceptListener(OnInterceptListener listener) {
this.mInterceptLister = listener;
} /**
* 设置为0,这样可以防止View获取焦点的时候,ScrollView自动滚动到焦点View的位置
*/ protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
return 0;
} @Override
public void onScrollStateChanged(int state) {
if (state == SCROLL_STATE_IDLE) {
// 加载更多回调
if (null != mOnLoadMoreListener) {
if (getLastVisiblePosition() >= getAdapter().getItemCount() - (1 + mLoadMoreBeforehandCount)) {
mOnLoadMoreListener.onLoadMore();
}
}
}
super.onScrollStateChanged(state);
} @Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInterceptLister != null && mInterceptLister.onIntercept(event)) {
return true;
} boolean result = super.dispatchKeyEvent(event);
View focusView = this.getFocusedChild();
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:
if(VirturlKeyPadCtr.isFastDoubleClick()){
return true;
}else {
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;
} @Override
public boolean onInterceptTouchEvent(MotionEvent e) {
return super.onInterceptTouchEvent(e);
} /**
* 设置默认选中.
*/ public void setSelectedPosition(int pos) {
this.smoothScrollToPosition(pos);
} //防止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 static boolean isFastDoubleClick() {
long time = System.currentTimeMillis();
long timeD = time - lastClickTime;
if ( 0 < timeD && timeD < 200) {
return true;
}
lastClickTime = time;
return false;
}
Android TV端的(RecyclerView)水平滚动焦点错乱问题的更多相关文章
- Android为TV端助力:RecyclerView更新数据时焦点丢失
1.adapter的setHasStableIds设置成true 2.重写adapter的getItemId方法 @Override public long getItemId(int positio ...
- Android RecyclerView 水平滚动+自动循环轮播
主要处理的地方: 1.RecyclerView中Adapter的item个人可以无限轮询. 2.RecyclerView自动滑动 3.手指按下时滑动停止,手指抬起后继续自动滑动 public clas ...
- Android为TV端助力(转载)
作者地址http://www.jianshu.com/u/63915ef020e2 针对Android Tv的自定义RecyclerView 作者 wenju_song 关注 2016.12.09 1 ...
- 用RecyclerView实现水平滚动和网格视图
建立RecyclerViewActivity.java文件 1 public class RecyclerViewActivity extends AppCompatActivity { 2 priv ...
- Android为TV端助力 转载:RecyclerView分页加载
package com.android.ryane.pulltoloaddata_recyclerview; import android.os.Handler;import android.os.L ...
- Android为TV端助力 listview与recyclerview上下联动
首先是主布局fragment里面的xml文件 <?xml version="1.0" encoding="utf-8"?><RelativeL ...
- [转]HorizontalScrollView介绍--支持水平滚动的android布局容器
类概述 用 于布局的容器,可以放置让用户使用滚动条查看的视图层次结构,允许视图结构比手机的屏幕大.HorizontalScrollView是一种 FrameLayout(框架布局),其子项被滚动查看时 ...
- Android中如何实现多行、水平滚动的分页的Gridview?
功能要求: (1)比如每页显示2X2,总共2XN,每个item显示图片+文字(点击有链接). 如果单行水平滚动,可以用Horizontalscrollview实现. 如果是多行水平滚动,则结合Grid ...
- Android为TV端助力 转载自jguangyou的博客,XML基本属性大全
android:layout_width 指定组件布局宽度 android:layout_height 指定组件布局高度 android:alpha 设置组件透明度 android:backgroun ...
随机推荐
- Dubbo与Nginx区别
Dubbo的负载均衡已经是服务层面的了,和nginx的负载均衡还在http请求层面完全不同.至于二者哪个优秀,当然没办法直接比较. 涉及到负载均衡就涉及到你的业务,根据业务来选择才是最适合的. dub ...
- Java笔试题:给定一个ReadOnlyClass的对象roc,能否把这个对象的age值改成30?
在Java笔试面试中,经常会遇到代码题,今天我们就来看一则Java代码笔试题. 有如下代码: Class ReadOnlyClass { private Integer age=20; public ...
- python glob的安装和使用
基本概念 glob是python自己带的一个文件操作相关模块,用它可以查找符合自己目的的文件,类似于Windows下的文件搜索,支持通配符操作.*,?,[]这三个通配符,*代表0个或多个字符,?代表一 ...
- Linux下RabbitMQ服务器搭建
系统环境 操作系统:CentOS6.9 erlang:OTP 19.3 rabbitmq:rabbitmq-server 3.6.12 安装步骤 安装erlang 1,安装预环境 通过yum安装以下组 ...
- 关于HotSpot VM以及Java语言的动态编译 你可能想知道这些
目录 1 HotSpot VM的历史 2 HotSpot VM 概述 2.1 编译器 2.2 解释器 2.3 解释型语言 VS 编译型语言 3 动态编译 3.1 什么是动态编译 3.2 HotSpot ...
- 前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply
一.快捷位置和尺寸属性 DOM已经提供给我们计算后的样式,但是还是觉得不方便,因为计算后的样式属性值都是字符串类型. 不能直接参与运算. 所以DOM又提供了一些API:得到的就是number类型的数据 ...
- C#3.0智能的编译器
智能的编译器 在C#3.0中,编译器变的越来越智能,我们不用提供给它完整的信息,仅需要提供必要的信息,编译器就可以进行推断为我们补全未提供的信息 自动实现的属性 在之前我们生成一个类时需要有一个字段, ...
- 数据结构之哈希(hash)表
最近看PHP数组底层结构,用到了哈希表,所以还是老老实实回去看结构,在这里去总结一下. 1.哈希表的定义 这里先说一下哈希(hash)表的定义:哈希表是一种根据关键码去寻找值的数据映射结构,该结构通过 ...
- 复杂的QR_code
Topic Link http://ctf5.shiyanbar.com/stega/QR_code.png 1)打开链接之后发现是一个二维码,扫描之后出现一个字符串 secret is here 2 ...
- Linux基础知识第三讲,拷贝文件跟移动文件命令
目录 Linux基础知识第三讲,拷贝文件跟移动文件命令 一丶常用命令 1.tree命令常用选项 2.cp复制文件命令 3.mv 命令的使用 Linux基础知识第三讲,拷贝文件跟移动文件命令 一丶常用命 ...