Android_组件_ViewPager2 使用

官方文档:https://developer.android.google.cn/jetpack/androidx/releases/viewpager2#1.0.0-alpha01
官方Demo: https://github.com/googlesamples/android-viewpager2

概述

2019年11月20号期待已久的 ViewPager2 正式版终于发布了。它不像 ViewPager 一样被内置在系统源码中。要使用ViewPager2必须迁移到androidx才可以

ViewPager2继承ViewGroup,内部核心是RecycleView加LinearLayoutManager,其实就是对RecycleView封装了一层,所有功能都是围绕着RecyclerView和LinearLayoutManager展开,本文章只是简单的介绍原理和使用,具体如何实现我们在这里不展开介绍了。

具体改动

新特性

  1. 基于 RecyclerView 实现。这意味着 RecyclerView 的优点将会被 ViewPager2 所继承。
  2. 支持竖直滑动。只需要一个参数就可以改变滑动方向。
  3. 支持关闭用户输入。通过setUserInputEnabled来设置是否禁止用户滑动页面。
  4. 支持通过编程方式滚动。通过fakeDragBy(offsetPx)代码模拟用户滑动页面。
  5. CompositePageTransformer 支持同时添加多个 PageTransformer。
  6. 支持 DiffUtil ,可以添加数据集合改变的 item 动画。
  7. 支持RTL (right-to-left) 布局。
  8. 完整支持notifyDataSetChanged

API 变化

  1. ViewPager2 被声明成了 final ,我们将无法继承 ViewPager2。
  2. FragmentStatePagerAdapter 被 FragmentStateAdapter 替代。
  3. PagerAdapter 被 RecyclerView.Adapter 替代。
  4. addPageChangeListener 被 registerOnPageChangeCallback。
  5. 移除了 setPargeMargin 方法。

具体使用

使用

添加依赖

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

布局文件

<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

class MyAdapter : RecyclerView.Adapter<MyAdapter.PagerViewHolder>() {
    private var mList: List<Int> = ArrayList()
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerViewHolder {
        val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_page, parent, false)
        return PagerViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: PagerViewHolder, position: Int) {
        holder.bindData(mList[position])
    }

    fun setList(list: List<Int>) {
        mList = list
    }

    override fun getItemCount(): Int {
        return mList.size
    }
    //  ViewHolder需要继承RecycleView.ViewHolder
    class PagerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val mTextView: TextView = itemView.findViewById(R.id.tv_text)
        private var colors = arrayOf("#CCFF99","#41F1E5","#8D41F1","#FF99CC")

        fun bindData(i: Int) {
            mTextView.text = i.toString()
            mTextView.setBackgroundColor(Color.parseColor(colors[i]))
        }
    }
}

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设置Adapter

val viewPager2 = findViewById<ViewPager2>(R.id.view_pager)
val myAdapter = MyAdapter()
myAdapter.setList(data)
viewPager2.adapter = myAdapter

滑动方向

使用接口public void setOrientation (int orientation)这里可以传递两个值 :

  • ViewPager2.ORIENTATION_VERTICAL //竖直滑动
  • ViewPager2.ORIENTATION_HORIZONTAL //横向滑动(默认)

页面滑动事件监听

使用public void registerOnPageChangeCallback (ViewPager2.OnPageChangeCallback callback)

viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
    override fun onPageSelected(position: Int) {
        super.onPageSelected(position)
        Toast.makeText(this@MainActivity, "page selected $position", Toast.LENGTH_SHORT).show()
    }
})

注意ViewPager2.OnPageChangeCallback()有三个可实现方法,因为是一个抽象类所以设置监听事件只需要重写需要的方法即可。

  • onPageScrollStateChanged(int state) //滚动状态更改时调用。
  • onPageScrolled(int position, float positionOffset, int positionOffsetPixels) //当滚动当前页面时(作为程序启动的平滑滚动的一部分或由用户启动的触摸滚动的一部分),将调用此方法。
  • onPageSelected(int position) //当选择新页面时,将调用此方法。

禁止用户滑动

API:setUserInputEnabled(boolean)

模拟用户滑动

注意:fakeDragBy前需要先beginFakeDrag方法来开启模拟拖拽

fakeDragBy会返回一个boolean值,true表示有fake drag正在执行,而返回false表示当前没有fake drag在执行。我们通过代码来尝试下:

fun fakeDragBy(view: View) {
    viewPager2.beginFakeDrag()
    if (viewPager2.fakeDragBy(-310f))
        viewPager2.endFakeDrag()
}

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

PageTransformer

PageTransformer 不光可以设置页面动画,还可以设置页面间距你可以同时添加多个PageTransformer

页面间距

在 ViewPager2 中使用 MarginPageTransformer() 来设置间距:viewPager2.setPageTransformer( MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))

但是 setPageTransformer 只可以输入一个对象且在次输入后会覆盖之前的设置,那么我们想输入多个 PageTransformer 改怎么办呢?

CompositePageTransformer

这个时候我们就要使用 CompositePageTransformer,来输入多个 PageTransformer

val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(AnimTransformer())
compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
viewPager2.setPageTransformer(compositePageTransformer)

如果我们要实现自己的动画效果该怎么办呢?而 ViewPager2 也为我们想到了 ,你只需继承 ViewPager2.PageTransformer 并在里面实现你的逻辑即可:

ViewPager2.PageTransformer

PageTransformer 是一个位于 ViewPager2 中的接口,因此 ViewPager2 的 PageTransformer 是独立于 ViewPager 的,它与 ViewPager 的 PageTransformer 没有任何关系。但是和 ViewPager 的 PageTransformer 中的实现方式是一摸一样的。

class ScaleInTransformer : ViewPager2.PageTransformer {
    private val mMinScale = DEFAULT_MIN_SCALE
    override fun transformPage(view: View, position: Float) {
        view.elevation = -abs(position)
        val pageWidth = view.width
        val pageHeight = view.height

        view.pivotY = (pageHeight / 2).toFloat()
        view.pivotX = (pageWidth / 2).toFloat()
        if (position < -1) {
            view.scaleX = mMinScale
            view.scaleY = mMinScale
            view.pivotX = pageWidth.toFloat()
        } else if (position <= 1) {
            if (position < 0) {
                val scaleFactor = (1 + position) * (1 - mMinScale) + mMinScale
                view.scaleX = scaleFactor
                view.scaleY = scaleFactor
                view.pivotX = pageWidth * (DEFAULT_CENTER + DEFAULT_CENTER * -position)
            } else {
                val scaleFactor = (1 - position) * (1 - mMinScale) + mMinScale
                view.scaleX = scaleFactor
                view.scaleY = scaleFactor
                view.pivotX = pageWidth * ((1 - position) * DEFAULT_CENTER)
            }
        } else {
            view.pivotX = 0f
            view.scaleX = mMinScale
            view.scaleY = mMinScale
        }
    }

    companion object {

        const val DEFAULT_MIN_SCALE = 0.85f
        const val DEFAULT_CENTER = 0.5f
    }
}

一页多屏

ViewPager2 的一屏多页可以通过 RecyclerView 设置 Padding 来实现:

viewPager2.apply { 
            offscreenPageLimit=1
           val recyclerView= getChildAt(0) as RecyclerView
            recyclerView.apply {
                val padding = resources.getDimensionPixelOffset(R.dimen.dp_10) +
                        resources.getDimensionPixelOffset(R.dimen.dp_10)
                // setting padding on inner RecyclerView puts overscroll effect in the right place
                setPadding(padding, 0, padding, 0)
                clipToPadding = false
            }
        }
val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(ScaleInTransformer())
compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
viewPager2.setPageTransformer(compositePageTransformer)

常用 API

添加修饰线

  • void addItemDecoration(RecyclerView.ItemDecoration decor)

  • 添加修饰线(index:如果此值为负,则将在末尾添加装饰。)

    • void addItemDecoration(RecyclerView.ItemDecoration decor, int index)
  • 开始模拟滑动

    • void beginFakeDrag()
  • 结束模拟滑动

    • endFakeDrag()
  • 设置滑动方向(垂直/水平)

    • void setOrientation(int orientation)
  • 禁止滑动

    • void setUserInputEnabled(boolean enabled)
  • 获取当前页面

    • int getCurrentItem()
  • 设置显示页面

    • void setCurremt(int item)
  • 获取适配器

    • getAdapter()
  • 设置缓存

    • setOffscreenPageLimit(int limit)
  • 设置 ViewPager 的切换动画

    • setPageTransformer(ViewPager2.PageTransformer transformer)
  • 页面回调(同addPageChangeListener

    • registerOnPageChangeCallback(OnPageChangeCallback).
  • 删除回调

    • unregisterOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback)

与Fragment配合

ViewPager2 中使用 FragmentStateAdapter 来代替 ViewPager 中的 FragmentStatePagerAdapter;我们来简单实现一个Demo;

<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" />

实现一个FragmentStateAdapter

class AdapterFragmentPager(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {

    private val fragments: SparseArray<BaseFragment> = SparseArray()

    init {
        fragments.put(PAGE_HOME, HomeFragment.getInstance())
        fragments.put(PAGE_FIND, PageFragment.getInstance())
        fragments.put(PAGE_INDICATOR, IndicatorFragment.getInstance())
        fragments.put(PAGE_OTHERS, OthersFragment.getInstance())
    }

    override fun createFragment(position: Int): Fragment {
        return fragments[position]
    }

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

    companion object {

        const val PAGE_HOME = 0

        const val PAGE_FIND = 1

        const val PAGE_INDICATOR = 2

        const val PAGE_OTHERS = 3

    }
}

在 Activty 中实现:

vp_fragment.adapter = AdapterFragmentPager(this)
vp_fragment.offscreenPageLimit = 3
vp_fragment.isUserInputEnabled = false

与 TadLayout 配合

在androidx中,TabLayout没有setupWithViewPager(ViewPager2 viewPager2)方法,而是用TabLayoutMediator将TabLayout和ViewPager2结合。

TabLayoutMediator(mTabLayout, viewPager2, (tab, position) -> tab.setText(titles.get(position))).attach()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容