ViewPager2的使用

简介

ViewPager2是Google在 androidx 组件包里增加的一个组件,目前已经到了1.0.0的稳定版本。
谷歌为什么要出这个组件呢?官方是这么说的:
在这里插入图片描述

源码简单了解

ViewPager2继承ViewGroup,所以跟ViewPager不兼容,内部核心是RecyclerView+LinearLayoutManager,其实就是对RecyclerView封装了一层,所有功能都是围绕着RecyclerView和LinearLayoutManager展开。

我们知道,SnapHelper用于辅助RecyclerView在滚动结束时将Item对齐到某个位置。PagerSnapHelper的作用让滑动结束时使当前Item居中显示,并且限制一次只能滑动一页,不能快速连续滑动,这样就和原来viewpager的交互很像,实现类似交互效果。所以需要设置下SnapHelper:

 new PagerSnapHelper().attachToRecyclerView(mRecyclerView);

ViewPager2需要一个adapter用来显示内容,adapter可以是RecyclerView.Adapter或者FragmentStateAdapter,还有ViewPager2是final类,所以无法被拓展。

改动点

新功能

  • 支持RTL布局
  • 支持竖向滚动
  • 支持关闭预加载offscreenPageLimit
  • 完整支持notifyDataSetChanged(还有局部刷新)
  • 方便启用和禁用用户的滑动 (setUserInputEnabled)
  • 引入了MarginPageTransformer,以提供在页面之间增加空隙
  • 引入CompositePageTransformer来组合多个PageTransformer
  • 因为ViewPager2由Recyclerview支持,所以也支持ItemDecorator、DiffUtil

API的变动

  • FragmentStateAdapter替换了原来的 FragmentStatePagerAdapter
  • RecyclerView.Adapter替换了原来的 PagerAdapter
  • registerOnPageChangeCallback替换了原来的 addPageChangeListener
    注意:不要忘记unregisterOnPageChangeCallback

常用Api

  • void setOrientation(int orientation)设置布局方向
  • void setUserInputEnabled(boolean enabled)设置是否允许用户输入/触摸
  • int getCurrentItem()获取当前Item下标
  • void setCurrentItem(int item)设置当前Item下标
  • setOffscreenPageLimit(int limit) 设置屏幕外加载页面数量
  • setPageTransformer(ViewPager2.PageTransformer transformer) 设置页面滑动时的变换效果
  • registerOnPageChangeCallback(OnPageChangeCallback) 注册页面改变回调
  • unregisterOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback) 解注册页面改变回调

更多的可以自行前往ViewPager2查看。

引入implementation

dependencies {
    implementation "androidx.viewpager2:viewpager2:1.0.0"
}

注意,viewpager2是在androidx里的,而androidx和android support库不能共存,所以项目中还是用support库的注意需要转移到androidx,这里不详述,自己google。

官方demo介绍

ViewPager2 with Views

这个示例用来展示添加views
在这里插入图片描述
代码:
XML布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:background="#FFFFFF">

    <include layout="@layout/controls" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

定义RecyclerView.Adapter

viewPager = findViewById(R.id.view_pager)
viewPager.adapter = CardViewAdapter()


class CardViewAdapter : RecyclerView.Adapter<CardViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
        return CardViewHolder(CardView(LayoutInflater.from(parent.context), parent))
    }

    override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
        holder.bind(Card.DECK[position])
    }

    override fun getItemCount(): Int {
        return Card.DECK.size
    }
}

class CardViewHolder internal constructor(private val cardView: CardView) :
    RecyclerView.ViewHolder(cardView.view) {
    internal fun bind(card: Card) {
        cardView.bind(card)
    }
}

从上面的代码可以看出adapter和使用RecyclerView是一样的。

ViewPager2 with Fragments

这个示例用来展示添加Fragments
在这里插入图片描述
代码:
XML布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:background="#FFFFFF">

    <include layout="@layout/controls" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

使用FragmentStateAdapter

viewPager.adapter = object : FragmentStateAdapter(this) {
            override fun createFragment(position: Int): Fragment {
                return CardFragment.create(Card.DECK[position])
            }

            override fun getItemCount(): Int {
                return Card.DECK.size
            }
        }

class CardFragment : Fragment() {
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            val cardView = CardView(layoutInflater, container)
            cardView.bind(Card.fromBundle(arguments!!))
            return cardView.view
        }

        companion object {

            /** Creates a Fragment for a given [Card]  */
            fun create(card: Card): CardFragment {
                val fragment = CardFragment()
                fragment.arguments = card.toBundle()
                return fragment
            }
        }
    }

ViewPager2和Fragment结合使用,需要使用FragmentStateAdapter。FragmentStateAdapter其实是继承RecyclerView.Adapter,有兴趣的可以去看看源码。

ViewPager2 with TabLayout

这个示例用来展示结合TabLayout使用
在这里插入图片描述
代码:
XML布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:background="#FFFFFF">

    <include layout="@layout/controls" />

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabMode="scrollable" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

注意:TabLayout与旧版ViewPager集成在一起很简单,只需将其添加为ViewPager的子项,并按设置layout_gravity属性就可以了。

<android.support.v4.view.ViewPager
     android:layout_width="match_parent"
     android:layout_height="match_parent">
     <android.support.design.widget.TabLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_gravity="top" />
 </android.support.v4.view.ViewPager>

然而,ViewPager2不接受TabLayout作为子View绑定。

定义RecyclerView.Adapter

class CardViewAdapter : RecyclerView.Adapter<CardViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CardViewHolder {
        return CardViewHolder(CardView(LayoutInflater.from(parent.context), parent))
    }

    override fun onBindViewHolder(holder: CardViewHolder, position: Int) {
        holder.bind(Card.DECK[position])
    }

    override fun getItemCount(): Int {
        return Card.DECK.size
    }
}

class CardViewHolder internal constructor(private val cardView: CardView) :
    RecyclerView.ViewHolder(cardView.view) {
    internal fun bind(card: Card) {
        cardView.bind(card)
    }
}

viewPager.adapter = CardViewAdapter()

TabLayout与 ViewPager2绑定

TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = Card.DECK[position].toString()
        }.attach()

注意:androidx中,TabLayout没有setupWithViewPager(ViewPager2 viewpager2)方法,而是用TabLayoutMediator将TabLayout和ViewPager2结合。

ViewPager2 with PageTransformer

这个示例用来展示给滑动增加自定义动画
在这里插入图片描述
代码:

    private val mAnimator = ViewPager2.PageTransformer { page, position ->
        val absPos = Math.abs(position)
        page.apply {
            rotation = if (rotateCheckBox.isChecked) position * 360 else 0f
            translationY = if (translateY) absPos * 500f else 0f
            translationX = if (translateX) absPos * 350f else 0f
            if (scaleCheckBox.isChecked) {
                val scale = if (absPos > 1) 0F else 1 - absPos
                scaleX = scale
                scaleY = scale
            } else {
                scaleX = 1f
                scaleY = 1f
            }
        }
    }
viewPager.setPageTransformer(mAnimator)

rotateCheckBox.setOnClickListener { viewPager.requestTransform() }

如果需要在页面直接增加空隙,可以使用MarginPageTransformer,这也是ViewPager2中的新功能。(注意:不能为负数)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BlKSdCth-1576063656447)(index_files/MarginPageTransformer.gif)]

val marginPageTransformer = MarginPageTransformer(50)

如果有多个page transformers,可以用CompositePageTransformer组合它们。

viewPager2.setPageTransformer(CompositePageTransformer().also {
    it.addTransformer(marginPageTransformer)
    it.addTransformer(translationPageTransformer())
})

ViewPager2 with MultiPages

这个示例用来展示一个页面显示两边item,两边能看到上一个和下一个item
在这里插入图片描述
要实现这个功能主要需要注意以下几点:
1.setOffscreenPageLimit(int limit)
设置为1,预加载前后item
2.android:clipToPadding
此属性表示: 用来定义ViewGroup是否允许在padding中绘制。默认情况下为true,为true的情况下, 那么绘制的区域会把padding部分剪裁。若为false,那么控件的绘制区域包含padding部分。所以这里需要设置为false。
3.给ViewPager2设置左右padding,给item设置左右margin

代码:

findViewById<ViewPager2>(R.id.view_pager).apply {
            offscreenPageLimit = 1
            val recyclerView = getChildAt(0) as RecyclerView
            recyclerView.apply {
                val padding = resources.getDimensionPixelOffset(R.dimen.halfPageMargin) +
                        resources.getDimensionPixelOffset(R.dimen.peekOffset)
                setPadding(padding, 0, padding, 0)
                clipToPadding = false
            }
            adapter = Adapter()
        }
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="@dimen/halfPageMargin"
    android:layout_marginRight="@dimen/halfPageMargin"
    android:background="#0C2962"
    android:padding="6dp"
    android:scaleType="fitCenter"
    android:src="@drawable/jetpack_logo" />

ViewPager2 with notifyDataSetChanged

用过ViewPager的notifyDataSetChanged的知道,在更新ViewPager中View的个数时会存在一些问题,要通过重写getItemPosition方法使其返回POSITION_NONE才会真正刷新到。
而ViewPager2的notifyDataSetChanged因实际用的是RecyclerView的notifyDataSetChanged,所以不会有这个问题,还支持局部刷新,而且可以利用DiffUtil类来更新(注意:如果用DiffUtil,需要覆盖getItemId()和containsItem()方法)。

示例:
在这里插入图片描述
代码:

fun changeDataSet(performChanges: () -> Unit) {
            if (checkboxDiffUtil.isChecked) {
                /** using [DiffUtil] */
                val idsOld = items.createIdSnapshot()
                performChanges()
                val idsNew = items.createIdSnapshot()
                DiffUtil.calculateDiff(object : DiffUtil.Callback() {
                    override fun getOldListSize(): Int = idsOld.size
                    override fun getNewListSize(): Int = idsNew.size

                    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
                        idsOld[oldItemPosition] == idsNew[newItemPosition]

                    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
                        areItemsTheSame(oldItemPosition, newItemPosition)
                }, true).dispatchUpdatesTo(viewPager.adapter!!)
            } else {
                /** without [DiffUtil] */
                val oldPosition = viewPager.currentItem
                val currentItemId = items.itemId(oldPosition)
                performChanges()
                viewPager.adapter!!.notifyDataSetChanged()
                if (items.contains(currentItemId)) {
                    val newPosition =
                        (0 until items.size).indexOfFirst { items.itemId(it) == currentItemId }
                    viewPager.setCurrentItem(newPosition, false)
                }
            }
        }

ViewPager2 with fake drag

这个示例是模拟拖曳动作,主要涉及beginFakeDrag()、fakeDragBy()、endFakeDrag()方法。
示例:
在这里插入图片描述
代码:

findViewById<View>(R.id.touchpad).setOnTouchListener { _, event ->
            handleOnTouchEvent(event)
        }

private fun handleOnTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                lastValue = getValue(event)
                viewPager.beginFakeDrag()
            }

            MotionEvent.ACTION_MOVE -> {
                val value = getValue(event)
                val delta = value - lastValue
                viewPager.fakeDragBy(if (viewPager.isHorizontal) mirrorInRtl(delta) else delta)
                lastValue = value
            }

            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
                viewPager.endFakeDrag()
            }
        }
        return true
    }

详细代码可以查看ViewPaer2 demo

关于offscreenPageLimit(预加载)

ViewPager的情况
ViewPager默认情况下的加载示意图如下:
在这里插入图片描述
当切换到当前页面时,会默认预加载左右两侧的布局到ViewPager中,尽管两侧的View并不可见的。由于ViewPageroffscreenPageLimit设置了限制,在ViewPager中offscreenPageLimit设置为0无效果其实还是默认值1(看下面源码),这导致页面的预加载是不可避免的,在Fragment配合ViewPager使用时也会存在这个问题。
在这里插入图片描述

ViewPager2的情况
前面改动点说到ViewPager2支持offscreenPageLimit,即在默认情况或设置默认值时只会加载当前页面。我们来看下ViewPager2的源码
在这里插入图片描述
可以看出只支持大于0或默认值(-1)。接着查看源码发现getOffscreenPageLimit()方法在ViewPager2.LinearLayoutManagerImpl里的calculateExtraLayoutSpace方法使用到了。
在这里插入图片描述
这个calculateExtraLayoutSpace方法据了解是为了定义布局额外的空间,默认空间等于RecyclerView的宽高空间,定义这个意在可以放大可布局的空间,该方法参数extraLayoutSpace是一个长度为2的int数组,第一条数据接受左边和上边的额外空间,第二条数据接受右边和下边的额外空间,故上述代码是表明等于默认值时不做处理,不然就是左右和上下各扩大offscreenSpace;所以OffscreenPageLimit其实就是放大了LinearLayoutManager的布局空间。

demo验证
为了验证加载效果,我用了LinearLayout同时展示ViewPager和ViewPager2,设置相同的item和数据,然后用Layout Inspector抓取两者的布局结构,结果如下:
1.默认offscreenPageLimit
在这里插入图片描述
2.设置offscreenPageLimit为1
在这里插入图片描述

官方网址

ViewPager2
ViewPager2 docs
ViewPaer2 demo

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值