项目中要实现一个竖向的RecycleView嵌套一个横向的RecycleView,展示多个类别的影片推荐。上下拉动列表的过程中,每一项的Item会出现间隔拉大的问题。解决方法如下:
从网上查到的资料来看,是因为添加了分隔线的原因,分隔线多次重复加载,造成间隔越来越大。项目中外层的RecycleView没有添加分隔线,只在内层添加了,所以只帖内层代码:
外层RecycleView的适配器:
package com.hisense.movienow.HorizontalView;
import android.content.Context;
import android.content.Intent;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.hisense.movienow.DetailActivity;
import com.hisense.movienow.MovieCategory;
import com.hisense.movienow.R;
import com.hisense.movienow.bean.VideoCateContent;
import java.util.List;
/**
* Created by wang on 2018/11/13.
*/
public class ListRecyclerAdapter extends RecyclerView.Adapter {
private static final String TAG = ListRecyclerAdapter.class.getSimpleName();
private LayoutInflater mInflater;
private List mListOfApps;
List videoCateContents;
// private int currentPosition = 0;
private Context context;
int proPosition = -1;
SpaceItemDecoration divider;
public ListRecyclerAdapter(Context context, List mListOfApps){
mInflater = LayoutInflater.from(context);
this.context = context;
this.mListOfApps = mListOfApps;
divider = new SpaceItemDecoration(18,18);
}
public void setMList(List movieCategories){
this.mListOfApps = movieCategories;
}
@SuppressWarnings("unused")
public void setData(List mListOfApps){
this.mListOfApps = mListOfApps;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.list_view_layout, parent, false);
ViewHolder vh = new ViewHolder(view);
vh.mImageView = (SimpleRecycleView) view.findViewById(R.id.movie_gridview);
vh.mTextView = (TextView) view.findViewById(R.id.category_title);
vh.mImageView.addItemDecoration(divider);
return vh;
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
// currentPosition = position;
MovieCategory videoCateContent = mListOfApps.get(position);
holder.mTextView.setText(videoCateContent.getCategory_title());
videoCateContents = videoCateContent.getMovieItemList();
SimpleRecyclerAdapter simpleRecyclerAdapter = new SimpleRecyclerAdapter(context,videoCateContents);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
holder.mImageView.setLayoutManager(linearLayoutManager);
// 网上找到的方法,但不管用,会报数组越界
// divider = (SpaceItemDecoration) holder.mImageView.getItemDecorationAt(0);
// if(divider == null){
// holder.mImageView.addItemDecoration(new SpaceItemDecoration(18,18));
// }
// simpleRecyclerAdapter.setPosition(0);
holder.mImageView.setAdapter(simpleRecyclerAdapter);
simpleRecyclerAdapter.setOnItemClickListener(new SimpleRecyclerAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Intent intent = new Intent(context,DetailActivity.class);
intent.putExtra("media_id",videoCateContents.get(position).getId());
context.startActivity(intent);
}
});
simpleRecyclerAdapter.setOnItemSelectListener(new SimpleRecyclerAdapter.OnItemSelectListener() {
@Override
public void onItemSelect(View view, int subposition) {
Log.e(TAG,"SimpleRecycleView======="+","+position+",proPosition="+proPosition);
if(position != proPosition){
holder.mImageView.getChildAt(0).requestFocus();
}
proPosition = position;
}
});
// // 设置itemView可以获得焦点,外层的RecycleView不能有焦点
holder.itemView.setFocusable(false);
holder.itemView.setTag(position);
}
@Override
public int getItemCount() {
return mListOfApps.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
SimpleRecycleView mImageView;
TextView mTextView;
ViewHolder(View itemView) {
super(itemView);
}
}
}
外层RecycleView的布局文件:
自定义的横向的RecycleView:
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 wang 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();
if(this.getChildAt(position) != null){
dx = this.getChildAt(position).getWidth();
}
Log.e(TAG,"dispatchKeyEvent ======dx="+dx+",postion="+position);
View focusView = this.getFocusedChild();
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);
}
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);
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);
}
}
SimpleRecycleView的适配器:
package com.hisense.movienow.HorizontalView;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.hisense.movienow.R;
import com.hisense.movienow.bean.VideoCateContent;
import com.hisense.movienow.widget.BoundsImageView;
import java.util.List;
/**
* Created by wang on 2018/11/13.
*/
public class SimpleRecyclerAdapter extends RecyclerView.Adapter {
private static final String TAG = SimpleRecyclerAdapter.class.getSimpleName();
private LayoutInflater mInflater;
private List mListOfApps;
private int currentPosition = 0;
private Context context;
public SimpleRecyclerAdapter(Context context, List mListOfApps){
mInflater = LayoutInflater.from(context);
this.context = context;
this.mListOfApps = mListOfApps;
}
public void setPosition(int position) {
currentPosition = position;
}
@SuppressWarnings("unused")
public void setData(List mListOfApps){
this.mListOfApps = mListOfApps;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.movie_item_layout, parent, false);
ViewHolder vh = new ViewHolder(view);
vh.mImageView = (BoundsImageView) view.findViewById(R.id.movie_image);
vh.mTextView = (TextView) view.findViewById(R.id.movie_title);
return vh;
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
VideoCateContent videoCateContent = mListOfApps.get(position);
Glide.with(context).load(videoCateContent.getPoster()).into(holder.mImageView);
holder.mTextView.setText(videoCateContent.getTitle());
ViewGroup.LayoutParams params = holder.mImageView.getLayoutParams();
ViewGroup.LayoutParams txtParams = holder.mTextView.getLayoutParams();
if(videoCateContent.getPoster_type() == 0){//竖海报
params.width = 166;
txtParams.width = 166;
}else{
params.width = 444;
txtParams.width = 444;
}
holder.mImageView.setLayoutParams(params);
holder.mTextView.setLayoutParams(txtParams);
// 设置itemView可以获得焦点
holder.itemView.setFocusable(true);
holder.itemView.setTag(position);
holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
currentPosition = (int) holder.itemView.getTag();
ViewCompat.animate(holder.itemView).scaleX(1.10f).scaleY(1.10f).start();
Log.e(TAG,"SimpleRecycleView======="+currentPosition+","+position);
mOnItemSelectListener.onItemSelect(holder.itemView, currentPosition);
holder.mImageView.setBorderColor(context.getResources().getColor(R.color.white));
} else {
ViewCompat.animate(holder.itemView).scaleX(1.0f).scaleY(1.0f).start();
holder.mImageView.setBorderColor(context.getResources().getColor(R.color.trans));
}
}
});
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickListener.onItemClick(v, currentPosition);
}
});
holder.itemView.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// mOnItemKeyListener.OnItemKey(v, keyCode, event, currentPosition);
return false;
}
});
}
@Override
public int getItemCount() {
return mListOfApps.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
LinearLayout ll_movieItem;
BoundsImageView mImageView;
TextView mTextView;
ViewHolder(View itemView) {
super(itemView);
}
}
private OnItemSelectListener mOnItemSelectListener;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
private OnItemKeyListener mOnItemKeyListener;
public interface OnItemSelectListener {
void onItemSelect(View view, int position);
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public interface OnItemLongClickListener {
void onItemLongClick(View view, int position);
}
public interface OnItemKeyListener {
void OnItemKey(View view, int keyCode, KeyEvent event, int position);
}
public void setOnItemSelectListener(OnItemSelectListener listener){
mOnItemSelectListener = listener;
}
public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
this.mOnItemClickListener = mOnItemClickListener;
}
public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
this.mOnItemLongClickListener = mOnItemLongClickListener;
}
public void setOnItemKeyListener(OnItemKeyListener mOnItemKeyListener) {
this.mOnItemKeyListener = mOnItemKeyListener;
}
}
最内层的布局文件:
这是一个android tv的项目所以增加了一些按键处理,注意事项:
1. 外层RecycleView不能获得焦点,只在布局文件中加入android:focusable="false"的话不起作用,得在代码中加入recycleView.setFocusable(false);
2. 要解决间隔拉大的问题,要在外层适配器初始化的时候就将ItemDecoration定义好,在onCreateViewHolder中添加到recycleView上。即holder.mImageView.addItemDecoration(divider);在onBindViewHolder中只添加适配器即可。