深入了解ViewPager2的使用
一、ViewPager2的新特性
ViewPager2從名字就可以看出來(lái)它是ViewPager的升級(jí)版,既然是升級(jí)版那么它相比ViewPager有哪些新功能和哪些API變化呢?我們接著往下看。
1.ViewPager2新特性
- 基于RecyclerView實(shí)現(xiàn)。這意味著RecyclerView的優(yōu)點(diǎn)將會(huì)被ViewPager2所繼承。
- 支持豎直滑動(dòng)。只需要一個(gè)參數(shù)就可以改變滑動(dòng)方向。
- 支持關(guān)閉用戶(hù)輸入。通過(guò)setUserInputEnabled來(lái)設(shè)置是否禁止用戶(hù)滑動(dòng)頁(yè)面。
- 支持通過(guò)編程方式滾動(dòng)。通過(guò)fakeDragBy(offsetPx)代碼模擬用戶(hù)滑動(dòng)頁(yè)面。
- CompositePageTransformer 支持同時(shí)添加多個(gè)PageTransformer。
- 支持DiffUtil ,可以添加數(shù)據(jù)集合改變的item動(dòng)畫(huà)。
- 支持RTL (right-to-left)布局。我覺(jué)得這個(gè)功能對(duì)國(guó)內(nèi)開(kāi)發(fā)者來(lái)說(shuō)可能用處不大..
2.相比ViewPager變化的API
ViewPager2相比ViewPager做了哪些改變呢?研究了一番之后我大概列出以下幾點(diǎn):
- ViewPager2與ViewPager同是繼承自ViewGrop,但是ViewPager2被聲明成了final。意味著我們不可能再像ViewPager一樣通過(guò)繼承來(lái)修改ViewPager2的代碼。
- FragmentStatePagerAdapter被FragmentStateAdapter 替代
- PagerAdapter被RecyclerView.Adapter替代
- addPageChangeListener被registerOnPageChangeCallback。我們知道ViewPager的addPageChangeListener接收的是一個(gè)OnPageChangeListener的接口,而這個(gè)接口中有三個(gè)方法,當(dāng)想要監(jiān)聽(tīng)頁(yè)面變化時(shí)需要重寫(xiě)這三個(gè)方法。而ViewPager2的registerOnPageChangeCallback方法接收的是一個(gè)叫OnPageChangeCallback的抽象類(lèi),因此我們可以選擇性的重寫(xiě)需要的方法即可。
- 移除了setPargeMargin方法。
以上所羅列的新特性和API可能并不完整,如有疏漏可以留言補(bǔ)充。
二、開(kāi)啟ViewPager2之旅
ViewPager2位于androidx包下,也就是它不像ViewPager一樣被內(nèi)置在系統(tǒng)源碼中。因此,使用ViewPager2需要額外的添加依賴(lài)庫(kù)。另外,android support中不包含ViewPager,也就是要使用ViewPager2必須遷移到androidx才可以。
1.添加依賴(lài),目前ViewPager2的最新版本是1.0.0:
dependencies {
implementation "androidx.viewpager2:viewpager2:1.0.0"
}
2.ViewPager2布局文件:
<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" />
3.ViewPager2的Adapter
因?yàn)閂iewPager2內(nèi)部封裝的是RecyclerView,因此它的Adapter也就是RecyclerView的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>
4.在A(yíng)ctivity中為ViewPager設(shè)置Adapter
很簡(jiǎn)單就完成了一個(gè)ViewPager的功能,來(lái)看下效果怎么樣:

5.ViewPager2豎直滑動(dòng)
接下來(lái)我們通過(guò)一行代碼為其設(shè)置豎直滑動(dòng)
viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
豎直滑動(dòng)用ViewPager是很難實(shí)現(xiàn)的,而通過(guò)ViewPager2只需要設(shè)置一個(gè)參數(shù)即可。來(lái)看下效果:

6.頁(yè)面滑動(dòng)事件監(jiān)聽(tīng)
上文已經(jīng)提到過(guò)了,我們?yōu)閂iewPager設(shè)置頁(yè)面滑動(dòng)的監(jiān)聽(tīng)事件需要重寫(xiě)三個(gè)方法,而為ViewPager2設(shè)置監(jiān)聽(tīng)事件只需要重寫(xiě)需要的方法即可,因?yàn)閂iewPager2中OnPageChangeCallback是一個(gè)抽象類(lèi)。
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
Toast.makeText(this@MainActivity, "page selected $position", Toast.LENGTH_SHORT).show()
}
})
7.setUserInputEnabled與fakeDragBy
我們知道,在使用ViewPager的時(shí)候想要禁止用戶(hù)滑動(dòng)需要重寫(xiě)ViewPager的onInterceptTouchEvent。而ViewPager2被聲明為了final,我們無(wú)法再去繼承ViewPager2。那么我們應(yīng)該怎么禁止ViewPager2的滑動(dòng)呢?其實(shí)在ViewPager2中已經(jīng)為我們提供了這個(gè)功能,只需要通過(guò)setUserInputEnabled即可實(shí)現(xiàn)。
viewPager2.isUserInputEnabled = false
同時(shí)ViewPager2新增了一個(gè)fakeDragBy的方法。通過(guò)這個(gè)方法可以來(lái)模擬拖拽。在使用fakeDragBy前需要先beginFakeDrag方法來(lái)開(kāi)啟模擬拖拽。fakeDragBy會(huì)返回一個(gè)boolean值,true表示有fake drag正在執(zhí)行,而返回false表示當(dāng)前沒(méi)有fake drag在執(zhí)行。我們通過(guò)代碼來(lái)嘗試下:
fun fakeDragBy(view: View) {
viewPager2.beginFakeDrag()
if (viewPager2.fakeDragBy(-310f))
viewPager2.endFakeDrag()
}
需要注意到是fakeDragBy接受一個(gè)float的參數(shù),當(dāng)參數(shù)值為正數(shù)時(shí)表示向前一個(gè)頁(yè)面滑動(dòng),當(dāng)值為負(fù)數(shù)時(shí)表示向下一個(gè)頁(yè)面滑動(dòng)。
下面來(lái)看下效果圖:

演示圖中禁止了用戶(hù)輸入,通過(guò)按鈕點(diǎn)擊可以模擬用戶(hù)滑動(dòng)。
三、ViewPager2的PageTransformer
相比ViewPager,ViewPager2的Transformer功能有了很大的擴(kuò)展。ViewPager2不僅可以通過(guò)PageTransformer用來(lái)設(shè)置頁(yè)面動(dòng)畫(huà),還可以用PageTransformer設(shè)置頁(yè)面間距以及同時(shí)添加多個(gè)PageTransformer。接下來(lái)我們就來(lái)認(rèn)識(shí)下ViewPager2的PageTransformer吧!
1.setPageMargin
在第一章中我們提到了ViewPager2移除了setPageMargin方法,那么怎么為ViewPager2設(shè)置頁(yè)面間距呢?其實(shí)在ViewPager2中為我們提供了MarginPageTransformer,我們可以通過(guò)ViewPager2的setPageTransformer方法來(lái)設(shè)置頁(yè)面間距。代碼如下:
viewPager2.setPageTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
上述代碼我們?yōu)閂iewPager2設(shè)置了10dp的頁(yè)面間距。效果如下:

2.認(rèn)識(shí)CompositePageTransformer
這個(gè)時(shí)候我們應(yīng)該有個(gè)疑問(wèn),為ViewPager2設(shè)置了頁(yè)面間距后如果還想設(shè)置頁(yè)面動(dòng)畫(huà)的Transformer怎么辦呢?這時(shí)候就該CompositePageTransformer出場(chǎng)了。從名字上也可以看出來(lái)它是一個(gè)組合的PageTransformer。沒(méi)錯(cuò),CompositePageTransformer實(shí)現(xiàn)了PageTransformer接口,同時(shí)在其內(nèi)部維護(hù)了一個(gè)List集合,我們可以將多個(gè)PageTransformer添加到CompositePageTransformer中。
val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(ScaleInTransformer())
compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
viewPager2.setPageTransformer(compositePageTransformer)
上述代碼中我們通過(guò)CompositePageTransformer為ViewPager設(shè)置了MarginPageTransformer和一個(gè)頁(yè)面縮放的ScaleInTransformer。來(lái)看下效果:

3.ViewPager2中的PageTransformer
PageTransformer是一個(gè)位于ViewPager2中的接口,因此ViewPager2的PageTransformer是獨(dú)立于ViewPager的,它與ViewPager的PageTransformer沒(méi)有任何關(guān)系。雖然如此,卻不必?fù)?dān)心。因?yàn)閂iewPager2的PageTransformer和ViewPager的PageTransformer實(shí)現(xiàn)方式一模一樣。我們看下上一小節(jié)中用到的ScaleInTransformer:
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
}
}
4.ViewPager2的一屏多頁(yè)效果
在ViewPager2的官方Sample上看到了ViewPager2的一屏多頁(yè)可以通過(guò)為RecyclerView設(shè)置Padding來(lái)實(shí)現(xiàn)。代碼如下:
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)
最后,我們來(lái)看下效果

四、ViewPager2與Fragment
我們前面也已經(jīng)提到了ViewPager2中新增的FragmentStateAdapter 替代了ViewPager的FragmentStatePagerAdapter。那么來(lái)我們就用ViewPager2來(lái)實(shí)現(xiàn)一個(gè)Activity中嵌套Fragment的實(shí)例。
1.Activity的layout中添加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" />
2.實(shí)現(xiàn)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 {
var fragment: Fragment
when (position) {
PAGE_HOME -> {
if (fragments.get(PAGE_HOME) == null) {
fragment = HomeFragment.getInstance();
fragments.put(PAGE_HOME, fragment)
} else {
fragment = fragments.get(PAGE_HOME)
}
}
PAGE_FIND -> {
if (fragments.get(PAGE_FIND) == null) {
fragment = PageFragment.getInstance();
fragments.put(PAGE_FIND, fragment)
} else {
fragment = fragments.get(PAGE_FIND)
}
}
PAGE_INDICATOR -> {
if (fragments.get(PAGE_INDICATOR) == null) {
fragment = IndicatorFragment.getInstance();
fragments.put(PAGE_INDICATOR, fragment)
} else {
fragment = fragments.get(PAGE_INDICATOR)
}
}
PAGE_OTHERS -> {
if (fragments.get(PAGE_OTHERS) == null) {
fragment = OthersFragment.getInstance();
fragments.put(PAGE_OTHERS, fragment)
} else {
fragment = fragments.get(PAGE_OTHERS)
}
}
else -> {
if (fragments.get(PAGE_HOME) == null) {
fragment = HomeFragment.getInstance();
fragments.put(PAGE_HOME, fragment)
} else {
fragment = fragments.get(PAGE_HOME)
}
}
}
return fragment
}
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
}
}
3.在A(yíng)ctivity中為ViewPager2設(shè)置FragmentStateAdapter
vp_fragment.adapter = AdapterFragmentPager(this)
vp_fragment.offscreenPageLimit = 3
vp_fragment.isUserInputEnabled=false
五、ViewPager2與TabLayout
TabLayout也是項(xiàng)目中經(jīng)常用到的一個(gè)控件,它通常會(huì)與ViewPager一起出現(xiàn)。那么對(duì)于ViewPager2應(yīng)該怎么使用Tablayout呢?這需要我們認(rèn)識(shí)一個(gè)新類(lèi)TabLayoutMediator,這個(gè)類(lèi)是在material-1.2.0中新增的一個(gè)類(lèi),目前material包的最新版本是1.2.0-alpha03,因此需要我們單獨(dú)引入這個(gè)包,依賴(lài)如下:
implementation 'com.google.android.material:material:1.2.0-alpha03'
TabLayoutMediator的構(gòu)造方法接收三個(gè)參數(shù),第一個(gè)參數(shù)為T(mén)abLayout;第二個(gè)參數(shù)為ViewPager2;第三個(gè)參數(shù)是TabConfigurationStrategy,這是一個(gè)接口,該接口中有一個(gè)方法onConfigureTab(@NonNull TabLayout.Tab tab, int position),第一個(gè)參數(shù)是當(dāng)前Tab,第二個(gè)當(dāng)前position,源碼如下:
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);
}
接下來(lái)我們便可以通過(guò)TabLayoutMediator將TabLayout與ViewPager2關(guān)聯(lián)起來(lái)了:
TabLayoutMediator(tab_layout, view_pager) { tab, position ->
// 為T(mén)ab設(shè)置Text
tab.text = Card.DECK[position].toString()
}.attach()
使用起來(lái)非常簡(jiǎn)單,實(shí)現(xiàn)效果如下圖所示:

六、小結(jié)
本篇文章我們認(rèn)識(shí)了ViewPager2的新特性以及其用法??偟脕?lái)說(shuō)ViewPager2相比ViewPager不管在性能上還是在功能上都有了很大的提升。因此,我相信在不久的未來(lái)ViewPager2必定會(huì)取代ViewPager。那么,你是否已經(jīng)考慮將ViewPager2用到你的項(xiàng)目中了呢?
最后再來(lái)給大家推薦一下BannerViewPager。這是一個(gè)基于ViewPager實(shí)現(xiàn)的具有強(qiáng)大功能的無(wú)限輪播庫(kù)。在未來(lái),我會(huì)在BannerViewPager 3.0版本中用ViewPager2來(lái)重構(gòu)代碼。歡迎大家到GitHub關(guān)注BannerViewPager 。
第四節(jié)中ViewPager2與Fragment的代碼見(jiàn):
以上就是深入了解ViewPager2的使用的詳細(xì)內(nèi)容,更多關(guān)于ViewPager2 使用教程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Android使用ViewPager完成app引導(dǎo)頁(yè)
- Android ViewPager實(shí)現(xiàn)滑動(dòng)指示條功能
- Android使用viewpager實(shí)現(xiàn)畫(huà)廊式效果
- android使用ViewPager實(shí)現(xiàn)圖片自動(dòng)切換
- Android自定義引導(dǎo)玩轉(zhuǎn)ViewPager的方法詳解
- android使用viewpager計(jì)算偏移量實(shí)現(xiàn)選項(xiàng)卡功能
- Android使用ViewPager快速切換Fragment時(shí)卡頓的優(yōu)化方案
- Android利用ViewPager實(shí)現(xiàn)帶小圓球的圖片滑動(dòng)
- Android Studio使用ViewPager+Fragment實(shí)現(xiàn)滑動(dòng)菜單Tab效果
- Android 兩個(gè)ViewPager的聯(lián)動(dòng)效果的實(shí)現(xiàn)
- Android自定義ViewPager實(shí)現(xiàn)縱向滑動(dòng)翻頁(yè)效果
相關(guān)文章
android內(nèi)存優(yōu)化之圖片優(yōu)化
對(duì)圖片本身進(jìn)行操作。盡量不要使用setImageBitmap、setImageResource、BitmapFactory.decodeResource來(lái)設(shè)置一張大圖,因?yàn)檫@些方法在完成decode后,最終都是通過(guò)java層的createBitmap來(lái)完成的,需要消耗更多內(nèi)存2012-12-12
簡(jiǎn)單了解Android性能優(yōu)化方向及相關(guān)工具
這篇文章主要介紹了簡(jiǎn)單了解Android性能優(yōu)化方向及相關(guān)工具,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
Android Kotlin 實(shí)現(xiàn)底部彈框日歷組件的案例
這篇文章主要介紹了Android Kotlin 實(shí)現(xiàn)底部彈框日歷組件的案例,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-08-08
Kotlin 高階函數(shù)與Lambda表達(dá)式示例詳解
這篇文章主要為大家介紹了Kotlin 高階函數(shù)與Lambda表達(dá)式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
Android Bitmap和Drawable相互轉(zhuǎn)換的簡(jiǎn)單代碼
Android Bitmap和Drawable相互轉(zhuǎn)換的簡(jiǎn)單代碼,需要的朋友可以參考一下2013-05-05
Android獲取手機(jī)配置信息具體實(shí)現(xiàn)代碼
下面為大家介紹下使用android獲取手機(jī)配置信息的具體過(guò)程,感興趣的朋友可以參考下哈,希望對(duì)你有所幫助2013-06-06
Android?NDK入門(mén)初識(shí)(組件結(jié)構(gòu)開(kāi)發(fā)流程)
這篇文章主要為大家介紹了Android?NDK入門(mén)之初識(shí)組件結(jié)構(gòu)開(kāi)發(fā)流程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
淺析Android手機(jī)衛(wèi)士接收短信指令執(zhí)行相應(yīng)操作
通過(guò)廣播接收者,接收到短信,對(duì)短信內(nèi)容進(jìn)行判斷,如果為我們指定的值就執(zhí)行相應(yīng)的操作。本文給大家介紹Android手機(jī)衛(wèi)士接收短信指令執(zhí)行相應(yīng)操作,感興趣的朋友參考下吧2016-04-04
Android開(kāi)發(fā)簡(jiǎn)單實(shí)現(xiàn)搖動(dòng)動(dòng)畫(huà)的方法
這篇文章主要介紹了Android開(kāi)發(fā)簡(jiǎn)單實(shí)現(xiàn)搖動(dòng)動(dòng)畫(huà)的方法,結(jié)合實(shí)例形式分析了Android搖動(dòng)動(dòng)畫(huà)的布局與功能簡(jiǎn)單實(shí)現(xiàn)方法,需要的朋友可以參考下2017-10-10

