ViewPager2使用方法及原理

197 阅读3分钟

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();
}