ViewPager2
缓存
setOffscreenPageLimit
通过setOffscreenPageLimit可以设置当前屏幕外预加载两侧item的页数
如果一个item占一页,那么设置的就是显示的个数
默认是不预加载
原理
RecyclerViewImpl extends RecyclerView
ViewPager2内部使用的RecyclerView是其子类RecyclerViewImpl
并重新实现了calculateExtraLayoutSpace方法!
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
@NonNull int[] extraLayoutSpace) {
int pageLimit = getOffscreenPageLimit();
if (pageLimit == OFFSCREEN_PAGE_LIMIT_DEFAULT) {
super.calculateExtraLayoutSpace(state, extraLayoutSpace);
return;
}
//设置两侧额外显示的item的页数
//getPageSize()返回recyclerview显示到屏幕上的大小
final int offscreenSpace = getPageSize() * pageLimit;
extraLayoutSpace[0] = offscreenSpace;
extraLayoutSpace[1] = offscreenSpace;
}
该方法设置的值extraLayoutSpace在recyclerview滑动时会将这个值放到滑动方向上需要加载的范围
transformer
setTransformer可以设置滑动动画
对滑动事件的监听
mScrollEventAdapter = new ScrollEventAdapter(this);//this代表viewpager2
mRecyclerView.addOnScrollListener(mScrollEventAdapter);
滑动时会直接进入到recyclerview,viewpager2不处理事件
而且他的大小就是viewpager2的大小
//和viewpager构造方法一起执行
//这个方法不会进行requestlayout,适合在第一次measure之前调用
attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
tranfromer发生过程:
- RecyclerView滑动事件产生开始滑动
- 滑动事件产生后通知listener->mScrollEventAdapter
- mScrollEventAdapter.onScrolled(RecyclerView recyclerView, int dx, int dy)
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
//更新第一个可见item的在child数组里面的位置赋值给mScrollValues.mPosition
updateScrollEventValues();
if (mDispatchSelected) {
//非主动滑动时调用,比如正在fling
.....
} else if (mAdapterState == STATE_IDLE) {
}
//tranformer入口
//会调用mPageTransformerAdapter的onscrolled方法
dispatchScrolled(mScrollValues.mPosition == NO_POSITION ? 0 : mScrollValues.mPosition,
mScrollValues.mOffset, mScrollValues.mOffsetPx);
}
}
//mPageTransformerAdapter处理transformer
mPageTransformerAdapter = new PageTransformerAdapter(mLayoutManager);
mPageChangeEventDispatcher.addOnPageChangeCallback(mPageTransformerAdapter);
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//position是第一个可视的item的在adapter数据集中的位置
if (mPageTransformer == null) {
return;
}
float transformOffset = -positionOffset;
for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
View view = mLayoutManager.getChildAt(i);
//view在整个adapter数据中的位置
int currPos = mLayoutManager.getPosition(view);
float viewOffset = transformOffset + (currPos - position);
//所以0表示当前view,-1代表左边的一个view,1代表右边的view,-2则代表距离当前view2页的左边view。viewOffset负数大小取决于OffscreenPageLimit
mPageTransformer.transformPage(view, viewOffset);
}
}
每次只滑动一页
mPagerSnapHelper = new PagerSnapHelperImpl();
mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
通过PagerSnapHelper在滑动结束时,fling时期进行处理,每次滑动只滑动一页。
PagerSnapHelper不是viewpager2特有的,而是recyclerviewd的扩展功能,调用attachToRecyclerView方法就可以实现
adapter
public void setAdapter(@Nullable Adapter adapter) {
mRecyclerView.setAdapter(adapter);
mCurrentItem = 0;
}
所以我们可以直接使用RecyclerView.Adapter,如果直接使用fragment是不可以的,所以viewpager2有专门的FragmentStateAdapter用来封装fragment到view。
FragmentStateAdapter
创建view的过程:
public final FragmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return FragmentViewHolder.create(parent);
}
static FragmentViewHolder create(ViewGroup parent) {
//parent就是recyclerView
//创建一个空的framelayout
FrameLayout container = new FrameLayout(parent.getContext());
//使该view大小和viewpager2一样大
container.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
container.setId(ViewCompat.generateViewId());
container.setSaveEnabled(false);
//返回一个空view
return new FragmentViewHolder(container);
}
//在bind时期,将fragment通过createview方法产生的view放进上一步生成的空FrameLayout中,也就是说使用view代替fragment。
public final void onBindViewHolder(final @NonNull FragmentViewHolder holder, int position) {
.......
mItemIdToViewHolder.put(itemId, viewHolderId); // this might overwrite an existing entry
ensureFragment(position);
final FrameLayout container = holder.getContainer();
//在当前holder第一次onBindViewHolder执行后,会调用addview方法,会调用holder.itemView的AttachedToWindow方法,也就是说第一次onBindViewHolder并不会填充fragment的view视图
if (ViewCompat.isAttachedToWindow(container)) {
if (container.getParent() != null) {
throw new IllegalStateException("Design assumption violated.");
}
container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (container.getParent() != null) {
//保证只执行一次
container.removeOnLayoutChangeListener(this);
//将fragment.getView放到holer中的itemView(也就是Framelayout)
placeFragmentInViewHolder(holder);
}
}
});
}
gcFragments();
}
真正添加view的地方,他会在bind后执行
mFragmentManager.registerFragmentLifecycleCallbacks(
new FragmentManager.FragmentLifecycleCallbacks() {
// TODO(b/141956012): Suppressed during upgrade to AGP 3.6.
@SuppressWarnings("ReferenceEquality")
@Override
public void onFragmentViewCreated(@NonNull FragmentManager fm,
@NonNull Fragment f, @NonNull View v,
@Nullable Bundle savedInstanceState) {
if (f == fragment) {
fm.unregisterFragmentLifecycleCallbacks(this);
addViewToContainer(v, container);
}
}
}, false);
因为当视图真正adview进recyclerview时,也就是bind后,才会去执行fragment的onCreateView
@Override
public final void onViewAttachedToWindow(@NonNull final FragmentViewHolder holder) {
placeFragmentInViewHolder(holder);
gcFragments();
}