最近预研EPG一些界面功能的时候需要实现一种节目列表选中自动居中放大的功能,查找一些网上的资料,发现大多数是重写RecyclerView的布局管理器的smoothScrollToPosition函数,该方法通过修改滑动Scroller里面的偏移量来达到居中的效果,不过在测试的过程中我发现当按住遥控器一会再松开时,列表会滚动一段时间再停止,无法实现即停的效果,界面操作起来不是很友好。
经过一些资料的搜索,发现可以在适配器里面做居中操作,具体做法是Item添加一个聚焦的监听,然后对当前Item的坐标以及RecyclerView的位置计算出需要偏移的长度,最后调用RecyclerView的smoothScrollBy函数进行滚动。下面是实现步骤:
1.RecyclerView修改子类绘制顺序
在RecyclerView构造函数中添加以下代码,以允许子类修改绘制顺序
setChildrenDrawingOrderEnabled(true);
重写getChildDrawingOrder函数,主要是修改子Item绘制顺序
@Override
protected int getChildDrawingOrder(int childCount, int position) {
View focusedView = getFocusedChild();
if (null != focusedView) {
int pos = indexOfChild(focusedView);
/* 这是最后一个需要刷新的item */
if (position == childCount - 1) {
if (pos > position) {
pos = position;
}
return pos;
} else if (pos == position) {
/* 这是原本要在最后一个刷新的item */
return childCount - 1;
}
}
return position;
}
2.在Adapter里面做一些聚焦滚动操作
首先可以在ViewHolder的构造函数对聚焦的Item设置聚焦监听
itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
int[] amount = getScrollAmount(rv, itemView);//计算需要滑动的距离
rv.smoothScrollBy(amount[0], amount[1]);
ofFloatAnimator(itemView, 1f, 1.3f);
}else{
ofFloatAnimator(itemView, 1.3f, 1f);
}
}
});
/**
* 计算需要滑动的距离,使焦点在滑动中始终居中
*
* @param recyclerView
* @param view
*/
private static int[] getScrollAmount(RecyclerView recyclerView, View view) {
int[] out = new int[2];
int position = recyclerView.getChildAdapterPosition(view);
final int parentLeft = recyclerView.getPaddingLeft();
final int parentTop = recyclerView.getPaddingTop();
final int parentRight = recyclerView.getWidth() - recyclerView.getPaddingRight();
final int parentBottom = recyclerView.getHeight() - recyclerView.getPaddingBottom();
final int childLeft = view.getLeft() - view.getScrollX();
final int childTop = view.getTop() - view.getScrollY();
//item左边距减去Recyclerview不在屏幕内的部分,加当前Recyclerview一半的宽度就是居中
final int dx = childLeft - parentLeft - ((parentRight - view.getWidth()) / 2);
//同上
final int dy = childTop - parentTop - (parentBottom - view.getHeight()) / 2;
out[0] = dx;
out[1] = dy;
return out;
}
/**
* @param view 作用的View
* @param start 开始大小
* @param end 结束大小
*/
private static void ofFloatAnimator(View view, float start, float end) {
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(700);//动画时间
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", start, end);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", start, end);
animatorSet.setInterpolator(new DecelerateInterpolator());//插值器
animatorSet.play(scaleX).with(scaleY);//组合动画,同时基于x和y轴放大
animatorSet.start();
}
3.为RecyclerView前后加入间隙,以便滚动到第一个或者最后一个可以居中
新建一个类继承RecyclerView.ItemDecoration,重写getItemOffsets函数
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
if (position > 0 && state.getItemCount() > 0) {
//第一项左边或者顶部留间隙
if (orientation == RecyclerView.HORIZONTAL) {
outRect.set(startGap, 0, 0, 0);
} else if (orientation == RecyclerView.VERTICAL) {
outRect.set(0, startGap, 0, 0);
}
} else if (position == state.getItemCount() - 1) {
//最后一项右边或者底部流间隙
if (orientation == RecyclerView.HORIZONTAL) {
outRect.set(0, 0, endGap, 0);
} else if (orientation == RecyclerView.VERTICAL) {
outRect.set(0, 0, 0, endGap);
}
} else {
outRect.set(0, 0, 0, 0);
}
}