学不动也要学!ViewPager2 新特性

Posted CSDN云计算

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学不动也要学!ViewPager2 新特性相关的知识,希望对你有一定的参考价值。

作者 | tech-bus.丹卿

来源 | 程序员八十

前 言

浏览android开发者官网的时候,发现Google竟然曾经悄悄推出过一个新的控件:ViewPager2;从名字上看就知道是ViewPager的升级版本,看了下推出这个控件的时间,早在2019年2月7号Google就已经发布了,之后再进行一波迭代更新,在2019年11月20号进行了正式的发布;

可以关联下Google曾经在2018年推出的Jetpack,旨在帮助开发者轻松构建更稳定健壮及可维护的Android开发生态解决方案,就可想而知此次推出ViewPager2,算是Google往被寄予厚望的Jetpack全家桶中又增添一员猛将;既然是升级版,相比ViewPager,它究竟有哪些新功能和特性呢?让我们接着往下看。

一、ViewPager2的新特性

ViewPager2新特性

  • 基于RecyclewView实现。这意味着ViewPager2将继承RecyclewView的优点,同样完全支持RecyclerView的相关配置功能

  • 支持垂直方向滑动。只需要一个参数就可以改变滑动方向;

  • 支持关闭用户输入和模拟用户滑动。通过设置setUserInputEnabled()来设置是否禁用用户滑动行为;通过fakeDragBy(float offsetPx)模拟用户滑动页面;

  • 支持多个PageTransformer

  • 支持DiffUtil,通过添加数据集合实现局部数据刷新和Item动画。

  • 支持RTL布局

和ViewPager进行对比

ViewPager2相比ViewPager做了哪些改变呢,下面对不同点进行了罗列:

  • 采用registerOnPageChangeCallback() 代替addPageChangeListener()监听页面的变化

  • 因为ViewPager2.java这个类被申明为fianl,无法继承它进行二次改造,不过可以copy一份进行修改加工

  • 采用FragmentStateAdapter 替代FragmentStatePagerAdapter,采用 RecyclerView.Adapter 替代PagerAdapter;

  • offScreenPageLimit新参数 新体验

二、Android ViewPager2的使用

ViewPager2 位于 androidx 包下,也就是它不像 ViewPager 一样被内置在系统源码中。因此,使用 ViewPager2 需要额外的添加依赖库。

另外,android support 中不包含 ViewPager2,也就是要使用 ViewPager2 必须迁移到 androidx 才可以。

① 添加依赖

目前ViewPager2的最新稳定版本是1.0.0。

dependencies 
       implementation "androidx.viewpager2:viewpager2:1.0.0"
    

② 在XML文件中申明

<androidx.viewpager2.widget.ViewPager2
       android:id="@+id/view_pager"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

③ 自定义Adapter

ViewPager2 是用 RecyclerView来实现的,因此它的 Adapter 其实就是 RecyclerView 的 Adapter。

class MyAdapter extends RecyclerView.Adapter<MyAdapter.PagerViewHolder> 
        private List<Integer> mList;

        public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) 
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_page, parent, false);
            return new PagerViewHolder(view);
        

        @Override
        public void onBindViewHolder(@NonNull PagerViewHolder holder, int position) 
            holder.bindData(position);
        

        @Override
        public int getItemCount() 
            return mList.size();
        

        public void setList(List<Integer> list)
            this.mList = list;
        

        class PagerViewHolder extends RecyclerView.ViewHolder 
            private TextView mTextView = itemView.findViewById(R.id.tv_text);
            private String[] colors = new String[]"#CCFF99", "#41F1E5", "#8D41F1", "#FF99CC";

            public PagerViewHolder(@NonNull View itemView) 
                super(itemView);
            

            private void bindData(int index) 
                mTextView.setText(index);
                mTextView.setBackgroundColor(Color.parseColor(colors[index]));
            
        
    

item_page布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:gravity="center">

      <TextView
        android:id="@+id/tv_text"
        android:background="@color/colorPrimaryDark"
        android:layout_width="match_parent"
        android:layout_height="280dp"
        android:gravity="center"
        android:textColor="#ffffff"
        android:textSize="22sp" />
    </LinearLayout>

④ 绑定ViewPager和Adpater

ViewPager2 viewPager2 = findViewById(R.id.view_pager);
        MyAdapter myAdapter = new MyAdapter();
        myAdapter.setList(data)
        viewPager2.setAdapter(myAdapter);

通过上述接入,就可以完成一个ViewPager滑动页简单的效果,如下:

⑤ 设置竖向滑动

只需要一行代码就能设置ViewPager2进行竖向方向的滑动:

viewPager2.setOrientation(ViewPager2.ORIENTATION_VERTICAL);

而这一点,如果是换做之前的ViewPager是比较难达到的效果,可以来看下效果:

⑥ 页面滑动监听事件

上面说到viewPager2监听页面滑动只需要重写我们需要的方法即可

viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() 
            @Override
            public void onPageSelected(int position) 
                super.onPageSelected(position);
            
        );

⑦ 禁止用户滑动和模拟用户滑动

涉及 API:setUserInputEnabled() 与 fakeDragBy()。

我们知道,在使用 ViewPager 的时候想要禁止用户滑动需要重写 ViewPager 的 onInterceptTouchEvent()。而 ViewPager2 被声明为了 final,我们无法再去继承 ViewPager2。

那么我们应该怎么禁止 ViewPager2 的滑动呢?其实在 ViewPager2 中已经为我们提供了这个功能,只需要通过 setUserInputEnabled() 即可实现。

viewPager2.setUserInputEnabled(false);

同时 ViewPager2 新增了一个 fakeDragBy() 的方法。通过这个方法可以来模拟拖拽。在使用 fakeDragBy() 前需要先 beginFakeDrag() 方法来开启模拟拖拽。

fakeDragBy() 会返回一个 boolean 值:

true 表示有 fake drag 正在执行;false 表示当前没有 fake drag 在执行。我们通过代码来尝试下:

private void fakeDragBy(ViewPager2 viewPager2) 
        viewPager2.beginFakeDrag();
        if (viewPager2.fakeDragBy(100f)) 
            viewPager2.endFakeDrag();
        
    

需要注意到是 fakeDragBy() 接受一个 float 的参数,当参数值为正数时表示向前一个页面滑动,当值为负数时表示向下一个页面滑动。

下面来看下效果图:

演示图中禁止了用户输入,通过按钮点击可以模拟用户滑动。

⑧ offScreenPageLimit()

在viewpager中也可以设置预加载页面参数,但是就算我们去设置了0,ViewPager的内部还是会强制改成,这也是被广大开发者诟病的地方;

而在ViewPager2中对这点进行了优化,可以通过源码发现:

# VewPager2
    
    private @OffscreenPageLimit int mOffscreenPageLimit = OFFSCREEN_PAGE_LIMIT_DEFAULT;

    /**
     * Value to indicate that the default caching mechanism of RecyclerView should be used instead
     * of explicitly prefetch and retain pages to either side of the current page.
     * @see #setOffscreenPageLimit(int)
     */
    public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1;

     /** @hide */
    @SuppressWarnings("WeakerAccess")
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    @Retention(SOURCE)
    @IntDef(OFFSCREEN_PAGE_LIMIT_DEFAULT)
    @IntRange(from = 1)
    public @interface OffscreenPageLimit 
    

可以看到,将默认预加载页面数量设置为默认值-1,这里可以理解为不进行页面预加载操作;而且如果要设置预加载页面数量,可以通过设置setOffscreenPageLimit值来进行赋值,注意这里将其注解标注为大于等于1;

public void setOffscreenPageLimit(@OffscreenPageLimit int limit) 
      if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) 
        throw new IllegalArgumentException(
                "Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
      
      mOffscreenPageLimit = limit;
      // Trigger layout so prefetch happens through getExtraLayoutSize()
      mRecyclerView.requestLayout();
    

下面就让我们看下设置不同的mOffscreenPageLimit数量会有啥不同的效果:

首先我们在 ViewPager 中添加多个 Fragment,并且 setOffscreenPageLimit() 使用默认值,然后再 Fragment 声明周期中打印出日志,代码不再贴出,直接看日志打印的内容:

可以看到只有第一个fragment初始化了,那么当我们滑动到下个fragment,看下日志:

看到只有当滑动到第二个fragment时候,页面才开始加载;因此当采用默认值-1时,滑动过程中不会发生预加载

接下来我们将 offscreenPageLimit 值改为 1,再来看下输出日志:

此时可以看到 offscreenPageLimit 设置为 1 后,会预加载进来一个页面,和 ViewPager 几乎是一样的效果。

总之,ViewPager2 对于 ViewPager 的预加载机制做了优化,使得体验上变得更好。

看到这里,很多人都会觉得,就这就这?ViewPager2难道只多了这么点功能?别急,下面这个新动效才是亮点;

三、PageTransformer

ViewPager2 的 Transformer 功能相比于 ViewPager 有了很大的扩展。

ViewPager2 不仅可以通过 PageTransformer 用来设置页面动画,还可以同时添加多个 PageTransformer。接下来我们就来看看 ViewPager2 的 PageTransformer 吧!

首先介绍一下 CompositePageTransformer

从名字上可以看出,它是一个组合了的PageTransformer,其实CompositePageTransformer实现了 PageTransformer 的接口,内部维护了一个 PageTransformer的List集合;

那么看看如何使用这个CompositePageTransformer

CompositePageTransformer compositePageTransformer = new CompositePageTransformer();
   compositePageTransformer.addTransformer(new ScaleInTransformer());
   compositePageTransformer.addTransformer(new MarginPageTransformer(10));
   viewPager2.setPageTransformer(compositePageTransformer);

上面代码中,我们给CompositePageTransformer设置了一个自定义页面缩放的ScaleInTransformer和一个页边距的MarginPageTransformer,可以看下效果:

     

自定义页面缩放的ScaleInTransformer代码如下:

class ScaleInTransformer implements ViewPager2.PageTransformer 
        static final float DEFAULT_MIN_SCALE = 0.85f;
        static final float DEFAULT_CENTER = 0.5f;

        @Override
        public void transformPage(@NonNull View view, float position) 
            view.setElevation(-abs(position));
            int pageWidth = view.getWidth();
            int pageHeight = view.getHeight();

            view.setPivotY((pageHeight / 2));
            view.setPivotX((pageWidth / 2));
            if (position < -1) 
                view.setScaleX(DEFAULT_MIN_SCALE);
                view.setScaleY(DEFAULT_MIN_SCALE);
                view.setPivotX(pageWidth);
             else if (position <= 1) 
                if (position < 0) 
                    float scaleFactor = (1 + position) * (1 - DEFAULT_MIN_SCALE) + DEFAULT_MIN_SCALE;
                    view.setScaleX(scaleFactor);
                    view.setScaleY(scaleFactor);
                    view.setPivotX(pageWidth * (DEFAULT_CENTER + DEFAULT_CENTER * -position));
                 else 
                    float scaleFactor = (1 - position) * (1 - DEFAULT_MIN_SCALE) + DEFAULT_MIN_SCALE;
                    view.setScaleX(scaleFactor);
                    view.setScaleY(scaleFactor);
                    view.setPivotX(pageWidth * ((1 - position) * DEFAULT_CENTER));
                
             else 
                view.setPivotX(0f);
                view.setScaleX(DEFAULT_MIN_SCALE);
                view.setScaleY(DEFAULT_MIN_SCALE);
            
        
    

viewpager2一屏多页的实现

查看google官方文档,可以看到官方实现一屏多页给出的代码是通过给recyclerview设置padding来实现的,代码如下:

RecyclerView recyclerView = (RecyclerView) viewPager2.getChildAt(0);
    int padding = 20;
    recyclerView.setPadding(padding, 0, padding, 0);
    recyclerView.setClipToPadding(false);

    CompositePageTransformer compositePageTransformer = new CompositePageTransformer();
    compositePageTransformer.addTransformer(new ScaleInTransformer());
    compositePageTransformer.addTransformer(new MarginPageTransformer(10));
    viewPager2.setPageTransformer(compositePageTransformer);

最后来看下效果:

四、Fragment中如何使用ViewPager2

上文讲述到在 ViewPager2 中采用 FragmentStateAdapter 代替了原先的 FragmentStatePagerAdapter,那么接下来看下如何搭配Fragment去使用ViewPager2

4.1 xml布局文件中添加ViewPager2

<androidx.viewpager2.widget.ViewPager2
         android:id="@+id/vp_fragment"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_above="@id/rg_tab" />

4.2 实现 FragmentStateAdapter

要使用FragmentStateAdapter最好的方式就是去自定义一个自己的Adapter,让我们来看下代码:

class CustomAdapterFragmentPager extends FragmentStateAdapter 

        final int PAGE_HOME = 0;
        final int PAGE_FIND = 1;
        final int PAGE_INDICATOR = 2;
        final int PAGE_OTHERS = 3;

        public CustomAdapterFragmentPager(@NonNull FragmentActivity fragmentActivity) 
            super(fragmentActivity);
        

        @NonNull
        @Override
        public Fragment createFragment(int position) 
            switch (position) 
                case PAGE_HOME:
                    return HomeFragment.getInstance();
                case PAGE_FIND:
                    return PageFragment.getInstance();
                case PAGE_INDICATOR:
                    return IndicatorFragment.getInstance();
                case PAGE_OTHERS:
                    return OthersFragment.getInstance();
                default:
                    return EmptyFragment.getInstance();
            
        

        @Override
        public int getItemCount() 
            return 4;
        
    

上述代码覆写了createFragment和getItemCount方法,可以看到其实和FragmentStatePagerAdapter做的事情差不多;

4.3 ViewPager2设置FragmentStateAdapter

初始化完ViewPager2和FragmentStateAdapter之后,最后一步就是给ViewPager设置上adapter,代码如下:

viewPager2.setAdapter(new CustomAdapterFragmentPager(this));
   viewPager2.setOffscreenPageLimit(3);
   viewPager2.setUserInputEnabled(false);

五、TabLayout和ViewPager2

TabLayout在平时开发工作中出现的频率也不算低了,经常会用来和ViewPager一起搭配使用,那么在TabLayout中如何搭配使用ViewPager2呢?

和原有的ViewPager不同,不能直接通过setViewPager来进行vp的设置;在ViewPager2中需要用到一个控件 TabLayoutMediator,这个类是在 material-1.2.0 中新增的一个类,目前 material 包的最新版本是 1.2.0-alpha03,添加依赖如下:

implementation 'com.google.android.material:material:1.2.0-alpha03'

需要传入三个参数去构造 TabLayoutMediator,第一个参数为 TabLayout;第二个参数为 ViewPager2;第三个参数是 TabConfigurationStrategy,这是一个接口,里面需要实现一个方法, onConfigureTab(@NonNull TabLayout.Tab tab, int position),第一个参数是当前的tablayout,第二个是当前的位置:

public interface TabConfigurationStrategy 
      /**
       * Called to configure the tab for the page at the specified position. Typically calls @link
       * TabLayout.Tab#setText(CharSequence), but any form of styling can be applied.
       *
       * @param tab The Tab which should be configured to represent the title of the item at the given
       *     position in the data set.
       * @param position The position of the item within the adapter's data set.
       */
      void onConfigureTab(@NonNull TabLayout.Tab tab, int position);
    

通过构造TabLayoutMediator,我们就可以将tablayout和viewpager2搭配起来使用了:

new TabLayoutMediator(tabLayout, viewPager2, new TabLayoutMediator.TabConfigurationStrategy() 
         @Override
         public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) 
            tab.setText(titles[position]);
        
    );

最后来看下效果图:

总结

在上述文章带领大家了解了一下ViewPager2的新特性,以及和ViewPager在开发使用中的区别;可以看到不管是从API、性能还是动效上,ViewPager2都是远远领先于ViewPager的,所以我认为未来有很大概率将会取代原来的ViewPager的;

看完这些,是时候将ViewPager2接入到你的项目里了!

参考

  • https://www.jianshu.com/p/bd70970600aa

  • https://my.oschina.net/zhongsm/blog/4653730?utm_source=tuicool

  • https://blog.csdn.net/weixin_42797048/article/details/88397381

往期推荐

云计算到底是谁发明的?

从Docker的信号机制看容器的优雅停止

Redis会遇到的坑,你踩过几个?

低代码平台会带动企业的组织变革吗?

点分享

点收藏

点点赞

点在看

以上是关于学不动也要学!ViewPager2 新特性的主要内容,如果未能解决你的问题,请参考以下文章

学不动也要学,用 Jetpack Compose 写一个 IM APP

谷歌开源史上最详《kotlin入门进阶实战》,学不动也要学

卷不动也得继续学!紧跟vue3的步伐,再来get一波进阶新特性!

什么?JDK11发布了?学不动了啊!

Java8刚刚普及,Java12即将发布,程序员吐槽学不动求慢一点

Redis 7.0 正式发布,新增近 50 个新命令,这次真的学不动了。。