昨天学习了下LayoutManager,发现真的是很牛逼,既然滑动和绘制都是LayoutManager控制,那么自定义一个LayoutManager来实现界面无限循环滚动不是一件很简单的事么,来试试吧
Android LayoutManager学习 https://blog.csdn.net/zxt0601/article/details/52956504
回顾
之前学习的时候,简单版的LinearLayoutManager的流程是
- 在onLayoutChildren填充子View(根据界面的大小确定填充数量)
- 滑动时
- 在滑动方向填充新的子View,如果到达了头和尾就不填充了
- 执行滑动操作,如果滑动到了头和尾就滑动停止
- 回收已经离开可视界面的子View
思路
上面的操作需要判断边界,要实现无限循环就是在把上面的边界拿掉
- 在滑动方向填充新的子View,如果到达了头和尾就继续填充
- 执行滑动操作,没有头尾,可以不停的往一个方向滑动
- 回收已经离开可视界面的子View,这个还是要继续回收的,不然会爆炸的
滑动
现在的滑动就没有边界了,直接就是填充,滚动,回收
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
Log.d("feifeifei","getChildCount() " + getChildCount() + " recycler.getScrapList().size() " + recycler.getScrapList().size());
//界面向下滚动的时候,dy为正,向上滚动的时候dy为负
//填充
fill(dy,recycler,state);
//滚动
offsetChildrenVertical(dy*-1);
//回收已经离开界面的
recycleOut(dy,recycler,state);
return dy;
}
填充
只看向下滚动.假设我们adapter的count是10
- 可视界面最下面的子View的adapter的位置如果是7,我们就获取第8位添加到尾部.之后一个一个往下加
- 如果界面最下方的子View已经是最后一个View,就把把位置为0的子View添加到尾部,这样往下滚动的时候就可以一直循环了,添加完了后最下面的就是adapter位置为0的子view,再往下滑动就是需要添加位置1的了
向上滚动就是-1,到达位置0就添加位置10,这样向上滚动也可以不停的循环
private void fill(int dy, RecyclerView.Recycler recycler, RecyclerView.State state){
//向下滚动
if (dy > 0){
//先在底部填充
View lastView = getChildAt(getChildCount() -1);
int lastPos = getPosition(lastView);
if (lastView.getBottom() - dy < getHeight()){
View scrap;
if (lastPos == getItemCount() -1){
scrap = recycler.getViewForPosition(0);
}else {
scrap = recycler.getViewForPosition(lastPos+1);
}
addView(scrap);
measureChildWithMargins(scrap,0,0);
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap,0,lastView.getBottom(),width,lastView.getBottom()+height);
}
}else {
//向上滚动
//现在顶部填充
View firstView = getChildAt(0);
int layoutPostion = getPosition(firstView);
if (firstView.getTop() >= 0 ){
View scrap ;
if (layoutPostion == 0){
scrap = recycler.getViewForPosition(getItemCount()-1);
}else {
scrap = recycler.getViewForPosition(layoutPostion -1);
}
addView(scrap,0);
measureChildWithMargins(scrap,0,0);
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap,0,firstView.getTop() - height,width,firstView.getTop());
}
}
}
回收
回收还是和之前的代码一样,超出了可视界面就回收
可以爽快的往一个方向死命的滑动了,看看效果

完整代码
/**
* 界面无线循环的LayoutManager
*/
public class CustomLayoutManagerInfinite extends RecyclerView.LayoutManager {
private final String TAG = CustomLayoutManagerInfinite.class.getSimpleName();
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
}
// 1 在RecyclerView初始化时,会被调用两次。
// 2 在调用adapter.notifyDataSetChanged()时,会被调用。
// 3 在调用setAdapter替换Adapter时,会被调用。
// 4 在RecyclerView执行动画时,它也会被调用。
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
Log.d(TAG,"onLayoutChildren ");
if (getItemCount() == 0){
detachAndScrapAttachedViews(recycler);
return;
}
//state.isPreLayout()是支持动画的
if (getItemCount() == 0 && state.isPreLayout()){
return;
}
//将当前Recycler中的view全部移除并放到报废缓存里,之后优先重用缓存里的view
detachAndScrapAttachedViews(recycler);
int actualHeight = 0;
for (int i = 0 ;i < getItemCount() ; i++){
View scrap = recycler.getViewForPosition(i);
addView(scrap);
measureChildWithMargins(scrap,0,0);
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap,0,actualHeight,width,actualHeight+height);
actualHeight+=height;
//超出界面的就不画了,也不add了
if (actualHeight > getHeight()){
break;
}
}
}
@Override
public boolean canScrollVertically() {
return true;
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
Log.d("feifeifei","getChildCount() " + getChildCount() + " recycler.getScrapList().size() " + recycler.getScrapList().size());
//界面向下滚动的时候,dy为正,向上滚动的时候dy为负
//填充
fill(dy,recycler,state);
//滚动
offsetChildrenVertical(dy*-1);
//回收已经离开界面的
recycleOut(dy,recycler,state);
return dy;
}
private void fill(int dy, RecyclerView.Recycler recycler, RecyclerView.State state){
//向下滚动
if (dy > 0){
//先在底部填充
View lastView = getChildAt(getChildCount() -1);
int lastPos = getPosition(lastView);
if (lastView.getBottom() - dy < getHeight()){
View scrap;
if (lastPos == getItemCount() -1){
scrap = recycler.getViewForPosition(0);
}else {
scrap = recycler.getViewForPosition(lastPos+1);
}
addView(scrap);
measureChildWithMargins(scrap,0,0);
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap,0,lastView.getBottom(),width,lastView.getBottom()+height);
}
}else {
//向上滚动
//现在顶部填充
View firstView = getChildAt(0);
int layoutPostion = getPosition(firstView);
if (firstView.getTop() >= 0 ){
View scrap ;
if (layoutPostion == 0){
scrap = recycler.getViewForPosition(getItemCount()-1);
}else {
scrap = recycler.getViewForPosition(layoutPostion -1);
}
addView(scrap,0);
measureChildWithMargins(scrap,0,0);
int width = getDecoratedMeasuredWidth(scrap);
int height = getDecoratedMeasuredHeight(scrap);
layoutDecorated(scrap,0,firstView.getTop() - height,width,firstView.getTop());
}
}
}
private void recycleOut(int dy, RecyclerView.Recycler recycler, RecyclerView.State state){
for (int i = 0 ; i <getChildCount() ;i++){
View view = getChildAt(i);
if (dy >0){
if (view.getBottom()-dy <0){
Log.d("feifeifei","recycleOut " + i);
removeAndRecycleView(view,recycler);
}
}else {
if (view.getTop()-dy > getHeight()){
Log.d("feifeifei","recycleOut " + i);
removeAndRecycleView(view,recycler);
}
}
}
}
}
网友评论