概述
在RecyclerView之前,对于线性布局和网格布局用的基本上是ListView和GridView,到RecyclerView,就不需要这么麻烦了,RecyclerView对于职责划分的很明确,布局相关的就只需要LayoutManager,继承LayoutManager就可以实现你想要的布局,比如android为我们提供的一下几个布局:
- 线性布局:LinearLayoutManager
- 网格布局:GridLayoutManager
- 瀑布流布局: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,里面的子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;
}
}
到这里就基本上完了,如果还想更深入的学习,可以自己花点时间在研究下源码。如果有不懂的欢迎一起讨论学习。