最近开始做android tv的应用,里面用到许多横向滚动的列表,查过资料后,我使用了SimpleRecycleView,代码如下:
package com.hisense.movienow.HorizontalView;
import android.content.Context;
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.View;
import android.widget.Scroller;
/**
* Created by wangchunmei.ex on 2018/11/13.
*/
public class SimpleRecycleView extends RecyclerView {
private static final String TAG = SimpleRecycleView.class.getSimpleName();
// 一个滚动对象
private Scroller mScroller;
private int mLastX = 0;
public SimpleRecycleView(Context context) {
super(context);
init(context);
}
public SimpleRecycleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SimpleRecycleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
// 一个初始化方法,传入了一个上下文对象,用来初始化滚动对象
private void init(Context context){
mScroller = new Scroller(context);
initView();
setItemAnimator(null);
}
/**
* 初始化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);
}
// 重写了计算滚动方法
@Override
public void computeScroll() {
if(mScroller!=null && mScroller.computeScrollOffset()){
scrollBy(mLastX - mScroller.getCurrX(), 0);
mLastX = mScroller.getCurrX();
postInvalidate();
}
}
/**
* 调用此方法滚动到目标位置,其中(fx, fy)表示最终要滚到的目标位置的坐标值
* duration表示期间滚动的耗时。
*
* @param fx 目标位置的X向坐标值
* @param fy 目标位置的Y向坐标值
* @param duration 滚动到目标位置所消耗的时间毫秒值
*/
@SuppressWarnings("unused")
public void smoothScrollTo(int fx, int fy,int duration) {
int dx = 0;
int dy = 0;
// 计算变化的位移量
if(fx != 0) {
dx = fx - mScroller.getFinalX();
}
if(fy!=0) {
dy = fy - mScroller.getFinalY();
}
Log.i(TAG, "fx:" + fx + ", getFinalX:" + mScroller.getFinalX() + ", dx:" + dx);
smoothScrollBy(dx, dy, duration);
}
/**
* 调用此方法设置滚动的相对偏移
*/
public void smoothScrollBy(int dx, int dy, int duration) {
if(duration > 0) {
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, duration);
} else {
// 设置mScroller的滚动偏移量
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
}
// 重绘整个view,重绘过程会调用到computeScroll()方法。
// 这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
invalidate();
}
/**
* 此方法用来检查自动调节
*
* @param position 要检查的位置
*/
@SuppressWarnings("unused")
public void checkAutoAdjust(int position){
int childCount = getChildCount();
// 获取可视范围内的选项的头尾位置
int firstVisibleItemPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
int lastVisibleItemPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
Log.d(TAG, "childCount:" + childCount + ", position:" + position + ", firstVisibleItemPosition:" + firstVisibleItemPosition
+ " lastVisibleItemPosition:" + lastVisibleItemPosition);
if(position == (firstVisibleItemPosition + 1) || position == firstVisibleItemPosition){
// 当前位置需要向右平移
leftScrollBy(position, firstVisibleItemPosition);
} else if (position == (lastVisibleItemPosition - 1) || position == lastVisibleItemPosition){
// 当前位置需要向左平移
rightScrollBy(position, lastVisibleItemPosition);
}
}
private void leftScrollBy(int position, int firstVisibleItemPosition){
View leftChild = getChildAt(0);
if(leftChild != null){
int startLeft = leftChild.getLeft();
int endLeft = (position == firstVisibleItemPosition ? leftChild.getWidth() : 0);
Log.d(TAG, "startLeft:" + startLeft + " endLeft" + endLeft);
autoAdjustScroll(startLeft, endLeft);
}
}
private void rightScrollBy(int position, int lastVisibleItemPosition){
int childCount = getChildCount();
View rightChild = getChildAt(childCount - 1);
if(rightChild != null){
int startRight = rightChild.getRight() - getWidth();
int endRight = (position == lastVisibleItemPosition ? (-1 * rightChild.getWidth()) : 0);
Log.d(TAG,"startRight:" + startRight + " endRight:" + endRight);
autoAdjustScroll(startRight, endRight);
}
}
/**
*
* @param start 滑动起始位置
* @param end 滑动结束位置
*/
private void autoAdjustScroll(int start, int end){
mLastX = start;
mScroller.startScroll(start, 0, end - start, 0);
postInvalidate();
}
/**
* 将指定item平滑移动到整个view的中间位置
* @param position 指定的item的位置
*/
public void smoothScrollMaster(int position) {
// 这个方法是为了设置Scroller的滚动的,需要根据业务需求,编写算法。
}
int position = 0;
public boolean dispatchKeyEvent(KeyEvent event) {
int dx = this.getChildAt(0).getWidth();
View focusView = this.getFocusedChild();
position = (int)focusView.getTag();
if(this.getChildAt(position) != null){
dx = this.getChildAt(position).getWidth();
}
Log.e(TAG,"dispatchKeyEvent ======dx="+dx+",postion="+position);
switch (event.getKeyCode()){
case KeyEvent.KEYCODE_DPAD_RIGHT:
if(event.getAction() == KeyEvent.ACTION_UP){
return true;
}else{
View rightView = FocusFinder.getInstance().findNextFocus(this,focusView,FOCUS_RIGHT);
if(rightView != null){
Log.e(TAG,"rightView != null");
rightView.requestFocusFromTouch();//获取焦点
position += 1;
if(position >= this.getChildCount()-1){
// checkAutoAdjust(position);
this.smoothScrollBy(dx,0);
}
return true;
}else{
//将滑动的动作放在不为空中做,只有在最后一项按右键才走这步,
// 如果什么都不做,直接return false的话,焦点到最后一项后直接滑到下一个page
this.smoothScrollBy(dx,0);
// checkAutoAdjust(position);
return true;
}
}
case KeyEvent.KEYCODE_DPAD_LEFT:
View leftView = FocusFinder.getInstance().findNextFocus(this,focusView,FOCUS_LEFT);
if(event.getAction() == KeyEvent.ACTION_UP){
return true;
}else{
if(leftView != null){
// checkAutoAdjust(position);
this.smoothScrollBy(-dx,0);
Log.e(TAG,"leftView != null");
leftView.requestFocusFromTouch();
position -= 1;
return true;
}else{
Log.e(TAG,"leftView = null");
this.smoothScrollBy(-dx,0);
return true;
}
}
}
return super.dispatchKeyEvent(event);
}
private void setViewPostion(View mNextFocused){
if(mNextFocused != null){
position = getChildAdapterPosition(mNextFocused);
}else{
position = getChildAdapterPosition(getFocusedChild());
}
}
}
使用过程中遇到的问题总结:
1. 当前可见的item显示正常,当按右键向右移动时,可见项以外的项,从第一项开始显示;
具体示例:一个横向列表有10条数据,一屏只能显示5个,按右键移动,从第6个item开始,又开始显示第一个item的内容,并且position的值也从0开始了;
解决方法:在给这个控件设置适配器的时候,将
simpleRecyclerAdapter.setHasStableIds(true);的true改为false
final SimpleRecyclerAdapter simpleRecyclerAdapter = new SimpleRecyclerAdapter(context,videoCateContents);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
holder.mImageView.setLayoutManager(linearLayoutManager);
simpleRecyclerAdapter.setHasStableIds(false);
holder.mImageView.setAdapter(simpleRecyclerAdapter);
holder.mImageView.setItemAnimator(null);