ViewPager2的使用:基本使用、源码分析、多种切换动画

7,576 阅读3分钟

前言

ViewPager2是ViewPager的改进版本,提供了一些增强功能:

  • 垂直方向的支持
  • 可动态修改Fragment集合
  • 从右到左支持 当然,这是官方新推的库,后续会获得更好的支持~这篇文章主要从基本使用、增强功能使用、viewpager到viewpager2的升级、通过ViewPager2.PageTransformer实现多种切换动画等几个方面进行介绍。

使用

基本使用

  1. 引入viewpager2库
// module的build.gradle文件
implementation("androidx.viewpager2:viewpager2:1.0.0")
  1. 在布局中添加viewpager2
<androidx.viewpager2.widget.ViewPager2
    android:id="@+id/viewPager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
  1. 继承FragmentStateAdapter创建adapter
class DemoAapdater(activity: FragmentActivity) : FragmentStateAdapter(activity) {

    override fun getItemCount(): Int {
        return 6
    }

    override fun createFragment(position: Int): Fragment {
        val demoFragment = DemoFragment()
        demoFragment.arguments = Bundle().apply {
            putInt("TEXT", position)
        }
        return demoFragment
    }
}
  1. 为viewpager2设置adapter
demoAapdater = DemoAapdater(this)
viewPager2.adapter = demoAapdater

通过简单四步,已经可以正常使用viewpager2啦~

配合TabLayout使用

很多业务场景,需要搭配TabLayout使用的。viewpager2对于tablayout的配合也进行了调整:

tabLayout = findViewById(R.id.tabLayout)
TabLayoutMediator(tabLayout, viewPager2) { tab, position ->
    tab.text = "PAGE $position"
}.attach()

增强功能使用

  1. 切换横竖屏滚动
cbDirection.setOnCheckedChangeListener { _, isChecked ->
    if (isChecked) {
        viewPager2.orientation = ViewPager2.ORIENTATION_HORIZONTAL
    } else {
        viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
    }
}
  1. 是否允许用户滑动切换
cbScroll.setOnCheckedChangeListener { buttonView, isChecked ->
    viewPager2.isUserInputEnabled = isChecked
}
  1. 通过其他view控制viewpager2的滑动
public boolean fakeDragBy(@SuppressLint("SupportAnnotationUsage") @Px float offsetPxFloat) {
    return mFakeDragger.fakeDragBy(offsetPxFloat);
}

直接通过TabLayoutMediator的TabConfigurationStrategy参数即可为tabitem设置title,跟viewpager的用法完全不同。

源码分析

viewpager2


public final class ViewPager2 extends ViewGroup

private void initialize(Context context, AttributeSet attrs) {
    mAccessibilityProvider = sFeatureEnhancedA11yEnabled
            ? new PageAwareAccessibilityProvider()
            : new BasicAccessibilityProvider();

    mRecyclerView = new RecyclerViewImpl(context);
    mRecyclerView.setId(ViewCompat.generateViewId());
    mRecyclerView.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);

    mLayoutManager = new LinearLayoutManagerImpl(context);
    mRecyclerView.setLayoutManager(mLayoutManager);
    mRecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
    setOrientation(context, attrs);

    mRecyclerView.setLayoutParams(
            new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());

    // Create ScrollEventAdapter before attaching PagerSnapHelper to RecyclerView, because the
    // attach process calls PagerSnapHelperImpl.findSnapView, which uses the mScrollEventAdapter
    mScrollEventAdapter = new ScrollEventAdapter(this);
    // Create FakeDrag before attaching PagerSnapHelper, same reason as above
    mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
    mPagerSnapHelper = new PagerSnapHelperImpl();
    mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
    // Add mScrollEventAdapter after attaching mPagerSnapHelper to mRecyclerView, because we
    // don't want to respond on the events sent out during the attach process
    mRecyclerView.addOnScrollListener(mScrollEventAdapter);
    ....
    ....
    attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
}

首先ViewPager2继承于Viewgroup,看到最后一句:attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());可以发现将RecyclerView添加到ViewPager2的第一个childView。那是不是viewpager2的功能其实就是通过RecyclerView实现的呢?

public void setAdapter(@Nullable @SuppressWarnings("rawtypes") Adapter adapter) {
    final Adapter<?> currentAdapter = mRecyclerView.getAdapter();
    mAccessibilityProvider.onDetachAdapter(currentAdapter);
    unregisterCurrentItemDataSetTracker(currentAdapter);
    mRecyclerView.setAdapter(adapter);
    mCurrentItem = 0;
    restorePendingState();
    mAccessibilityProvider.onAttachAdapter(adapter);
    registerCurrentItemDataSetTracker(adapter);
}

public void setOrientation(@Orientation int orientation) {
    mLayoutManager.setOrientation(orientation);
    mAccessibilityProvider.onSetOrientation();
}

上边抽取了两个函数,setAdapter最终是设置给了mRecyclerView,setOrientation最终也是给了mLayoutManager。这里再次印证了开始的想法~那通过上边的分析,FragmentStateAdapter应该也是继承于RecyclerView.Adapter?

FragmentStateAdapter

public abstract class FragmentStateAdapter extends
        RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter

查看源码可以发现,FragmentStateAdapter确实是继承于RecyclerView.Adapter。重新实现了RecyclerView.ViewHolder

public final class FragmentViewHolder extends ViewHolder {
    private FragmentViewHolder(@NonNull FrameLayout container) {
        super(container);
    }

    @NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {
        FrameLayout container = new FrameLayout(parent.getContext());
        container.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT));
        container.setId(ViewCompat.generateViewId());
        container.setSaveEnabled(false);
        return new FragmentViewHolder(container);
    }

    @NonNull FrameLayout getContainer() {
        return (FrameLayout) itemView;
    }
}

另外,FragmentStateAdapter实现了StatefulAdapter接口:

public interface StatefulAdapter {
    /** Saves adapter state */
    @NonNull Parcelable saveState();

    /** Restores adapter state */
    void restoreState(@NonNull Parcelable savedState);
}

该接口主要定义了两个函数,一个用于保存状态,一个用于恢复状态。主要在ViewPager2中被调用:

@SuppressWarnings("ConstantConditions")
@Nullable
@Override
protected Parcelable onSaveInstanceState() {
    ...

    if (mPendingAdapterState != null) {
        ss.mAdapterState = mPendingAdapterState;
    } else {
        Adapter<?> adapter = mRecyclerView.getAdapter();
        if (adapter instanceof StatefulAdapter) {
            ss.mAdapterState = ((StatefulAdapter) adapter).saveState();
        }
    }

    return ss;
}


private void restorePendingState() {
    ...
    if (mPendingAdapterState != null) {
        if (adapter instanceof StatefulAdapter) {
            ((StatefulAdapter) adapter).restoreState(mPendingAdapterState);
        }
        mPendingAdapterState = null;
    }
    ...
}

ViewPager2会在资源不足回收前保存fragment的状态,以便后续的状态恢复

ViewPager到ViewPager2的迁移

关于这块,官方有详细的说明:官方说明

  • Adapter的变更
  • TabLayout关联的调整:不再通过setupWithViewPager(), 而是使用TabLayoutMediator实例

多种切换动画

Sets a ViewPager2.PageTransformer that will be called for each attached page whenever the scroll position is changed. This allows the application to apply custom property transformations to each page, overriding the default sliding behavior.
Note: setting a ViewPager2.PageTransformer disables data-set change animations to prevent conflicts between the two animation systems. Setting a null transformer will restore data-set change animations.
Params:
transformer – PageTransformer that will modify each page's animation properties
See Also:
MarginPageTransformer, CompositePageTransformer

public void setPageTransformer(@Nullable PageTransformer transformer) 

我们可以通过自定义PageTransformer来实现不同的页面切换效果。官方也给出了两个具体的例子:官方例子 下边给出已实现的一些效果图(参考华为桌面的切换效果),具体实现请看demo

1.gif

2.gif

3.gif

4.gif

5.gif

6.gif

总结

Viewpager2的使用基本介绍到这里,其实官方也提供了一个完整的demo:官方demo,里边还介绍了viewpager2嵌套滚动视图的滑动冲突解决方案,有这方面需求的可以看下具体解决思路。

最后,按照惯例,附上该文章对应的demo:gitee-demo