概述

在RecyclerView之前,对于线性布局和网格布局用的基本上是ListView和GridView,到RecyclerView,就不需要这么麻烦了,RecyclerView对于职责划分的很明确,布局相关的就只需要LayoutManager,继承LayoutManager就可以实现你想要的布局,比如android为我们提供的一下几个布局:

  1. 线性布局:LinearLayoutManager
  2. 网格布局:GridLayoutManager
  3. 瀑布流布局:StaggerGridLayoutManager

这些布局管理都直接或间接实现了LayoutManager,这也就是LayoutManager的主要作用:布局管理。
LayoutManager是RecyclerView的内部类,RecyclerView把它的测量和布局工作都转交给了LayoutManager,如果你想要的布局android没有给你提供,那么就可以考虑去继承LayoutManager去实现。

常见方法

  • findFirstCompletelyVisibleItemPosition()
  • findFirstVisibleItemPosition()
  • findLastCompletelyVisibleItemPosition()
  • findLastVisibleItemPosition()
  • onSaveInstanceState()
  • onRestoreInstanceState()

前面四个方法好理解,看名字应该就能知道,找到最前最后显示item的位置,其中含有Completely的方法返回的是完全可见的第一个item,而不含的则是真正的第一个item,这个item可能只显示了一部分,这个显示需要注意一点,比如使用的LinearLayoutManager布局,这个可见指的是布局方向上的可见。
onSaveInstanceState():
对于onSaveInstanceState()这个方法肯定很多人都见过,那就是在Activity中就有这个方法,当Activity异常销毁(比如由于内存过大)时就会调用到这个方法,而在LayoutManager中提供的这个方法是一个空方法,在他的子类中提供了实现,这里看下在LinearLayoutManager中的实现:

    @Override
    public Parcelable onSaveInstanceState() {
        if (mPendingSavedState != null) {
            return new SavedState(mPendingSavedState);
        }
        SavedState state = new SavedState();
        // RecyclerView中是否含有子view,如果有就会保存子view的状态
        if (getChildCount() > 0) {
            ensureLayoutState();
            boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
            state.mAnchorLayoutFromEnd = didLayoutFromEnd;
            //判断布局是否是倒序
            if (didLayoutFromEnd) {
            // 倒序布局
                final View refChild = getChildClosestToEnd();
                state.mAnchorOffset = mOrientationHelper.getEndAfterPadding()
                        - mOrientationHelper.getDecoratedEnd(refChild);
                state.mAnchorPosition = getPosition(refChild);
            } else {
            // 顺序布局,可以看到,state中主要是保存两个变量,一个是当前显示第一个view所在的position,另一个是当前这个
            // view没有显示的偏移量
                final View refChild = getChildClosestToStart();
                state.mAnchorPosition = getPosition(refChild);
                state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild)
                        - mOrientationHelper.getStartAfterPadding();
            }
        } else {
            state.invalidateAnchor();
        }
        return state;
    }

这里可以看出主要是保存了两个变量,保存的这两个变量有什么用呢?主要是在RecyclerView被remove后再次添加进来时可以恢复上一次显示item的位置,什么意思呢?先来看下下面这个界面:
RecyclerView(三):LayoutManager职责及相关方法
可以看到这个布局的最外层是一个RecyclerView,里面的子item还有RecyclerView,当我们往上滑的时候,子RecyclerView最终是会被reMove()掉的,子RecyclerView中的子item的显示的位置信息是不会被记录的,当再次往回滑的时候,子RecyclerView显示的总是会是第一个,如果想往回滑的时候,子RecyclerView显示的总是上次滑动到的位置,那么要怎么去是想呢?这时候就可以用到上面提到的onSaveInstanceState(),在这个方法里正好会保存RecyclerView的位置信息,当我们往回滑添加的之前被回收的RecyclerView时,那就可以将上次保存的位置信息取出来再次添加进去就可以了,思路是有了,接下来就是如何在代码中去实现了,首先是回收的时候保存信息,关于item的回收,想到的就是Adapter的onViewRecycled()方法,接下来就是去恢复它的位置信息了,我想到的就是onBindViewHolder()中可以去恢复它的位置信息,毕竟reMove()的item都会调用到这个方法(detach掉的view本身就会保留它本身的信息,不需要我们维护),下面是一些实现的关键代码:
1.确定当前的item当中是否含有RecyclerView:

    RecyclerView findNestedRecyclerView(@NonNull View view) {
        if (!(view instanceof ViewGroup)) {
            return null;
        }
        if (view instanceof RecyclerView) {
            return (RecyclerView) view;
        }
        final ViewGroup parent = (ViewGroup) view;
        final int count = parent.getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView descendant = findNestedRecyclerView(child);
            if (descendant != null) {
                return descendant;
            }
        }
        return null;
    }

2.回收时保存当前RecyclerView中item的位置信息:onViewRecycled()

		private SparseArray<Parcelable> rvState = new SparseArray<>();
        @Override
        public void onViewRecycled(@NonNull MyViewHolder holder) {
            super.onViewRecycled(holder);
            RecyclerView nestedRecyclerView = findNestedRecyclerView(holder.itemView);
            if (nestedRecyclerView != null) {
                Parcelable parcelable = nestedRecyclerView.getLayoutManager().onSaveInstanceState();
                // 保存时key为当前item所处的位置position,值为子RecyclerView的子item的显示位置信息
                rvState.put(holder.getAdapterPosition(),parcelable);
            }
        }

3.重新绑定时恢复RecyclerView中item的位置信息,onBindViewHolder():

        @Override
        public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, int i) {
            Log.d(TAG, "onBindViewHolder: position = "+i);
            // 这里type=0的item的布局中就有RecyclerView
            if (getItemViewType(i) == 0) {
                myViewHolder.rv.setLayoutManager(new LinearLayoutManager(TestRVActivity.this,RecyclerView.HORIZONTAL,false));
                // 获取当前position之前回收时的位置信息,如果不为null,那么就恢复之前的位置信息
                Parcelable parcelable = rvState.get(i);
                if (parcelable != null) {
                    rvState.remove(i);
                    // 
                    myViewHolder.rv.getLayoutManager().onRestoreInstanceState(parcelable);
                }
                if (myViewHolder.rv.getAdapter() == null) {
                    myViewHolder.rv.setAdapter(new MyChildAdapter());
                }
                return;
            }
        }

到这里就基本上完了,如果还想更深入的学习,可以自己花点时间在研究下源码。如果有不懂的欢迎一起讨论学习。

相关文章: