就在上個月20號(2019年11月20號),期待已久的ViewPager2 正式版終於發佈了!不知道你是否已經蠢蠢欲動着手用ViewPager2去改造你項目的ViewPager了?什麼?你還不知道ViewPager2?那麼請你立刻繫好安全帶,本篇文章將帶你一覽ViewPager2的風采。java
ViewPager2從名字就能夠看出來它是ViewPager的升級版,既然是升級版那麼它相比ViewPager有哪些新功能和哪些API變化呢?咱們接着往下看。android
ViewPager2相比ViewPager作了哪些改變呢?研究了一番以後我大概列出如下幾點:git
以上所羅列的新特性和API可能並不完整,若有疏漏能夠留言補充。github
ViewPager2位於androidx包下,也就是它不像ViewPager同樣被內置在系統源碼中。所以,使用ViewPager2須要額外的添加依賴庫。另外,android support中不包含ViewPager,也就是要使用ViewPager2必須遷移到androidx才能夠。編程
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" />
複製代碼
由於ViewPager2內部封裝的是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>
複製代碼
val viewPager2 = findViewById<ViewPager2>(R.id.view_pager)
val myAdapter = MyAdapter()
myAdapter.setList(data)
viewPager2.adapter = myAdapter
複製代碼
很簡單就完成了一個ViewPager的功能,來看下效果怎麼樣: app
接下來咱們經過一行代碼爲其設置豎直滑動less
viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
複製代碼
豎直滑動用ViewPager是很難實現的,而經過ViewPager2只須要設置一個參數便可。來看下效果: ide
上文已經提到過了,咱們爲ViewPager設置頁面滑動的監聽事件須要重寫三個方法,而爲ViewPager2設置監聽事件只須要重寫須要的方法便可,由於ViewPager2中OnPageChangeCallback是一個抽象類。
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
Toast.makeText(this@MainActivity, "page selected $position", Toast.LENGTH_SHORT).show()
}
})
複製代碼
咱們知道,在使用ViewPager的時候想要禁止用戶滑動須要重寫ViewPager的onInterceptTouchEvent。而ViewPager2被聲明爲了final,咱們沒法再去繼承ViewPager2。那麼咱們應該怎麼禁止ViewPager2的滑動呢?其實在ViewPager2中已經爲咱們提供了這個功能,只須要經過setUserInputEnabled便可實現。
viewPager2.isUserInputEnabled = false
複製代碼
同時ViewPager2新增了一個fakeDragBy的方法。經過這個方法能夠來模擬拖拽。在使用fakeDragBy前須要先beginFakeDrag方法來開啓模擬拖拽。fakeDragBy會返回一個boolean值,true表示有fake drag正在執行,而返回false表示當前沒有fake drag在執行。咱們經過代碼來嘗試下:
fun fakeDragBy(view: View) {
viewPager2.beginFakeDrag()
if (viewPager2.fakeDragBy(-310f))
viewPager2.endFakeDrag()
}
複製代碼
須要注意到是fakeDragBy接受一個float的參數,當參數值爲正數時表示向前一個頁面滑動,當值爲負數時表示向下一個頁面滑動。 下面來看下效果圖:
offScreenPageLimit在ViewPager中就已經存在,這個參數用來控制ViewPager左右兩端預加載頁面的個數。爲了保證ViewPager的流暢性,offScreenPageLimit被強制規定爲大於0的數,即便咱們將其設置爲0,ViewPager內部也會將其改成1。所以ViewPager就被強制左右兩邊至少加載一個頁面。這也是一直被廣大開發者所詬病的一個問題。而在ViewPager2中針對這一問題作了優化。咱們點開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 {
}
複製代碼
能夠看到在ViewPager2中offScreenPageLimit的默認值被設置爲了-1,並且offScreenPageLimit這個成員變量被一個名爲@OffscreenPageLimit的註解所修飾,而在這個註解強制要求int的範圍是大於等於1的。什麼?ViewPager2的預加載頁面難道也必須大於等於1?那這相比ViewPager有什麼區別呢?先彆着急,其實最大的區別就在這個OFFSCREEN_PAGE_LIMIT_DEFAULT上,這個值被設置爲-1,那麼它表明什麼意思呢?咱們能夠從ViewPager2源碼的註釋中找出一些端倪
/** * <p>Set the number of pages that should be retained to either side of the currently visible * page(s). Pages beyond this limit will be recreated from the adapter when needed. Set this to * {@link #OFFSCREEN_PAGE_LIMIT_DEFAULT} to use RecyclerView's caching strategy. The given value * must either be larger than 0, or {@code #OFFSCREEN_PAGE_LIMIT_DEFAULT}.</p> * * <p>Pages within {@code limit} pages away from the current page are created and added to the * view hierarchy, even though they are not visible on the screen. Pages outside this limit will * be removed from the view hierarchy, but the {@code ViewHolder}s will be recycled as usual by * {@link RecyclerView}.</p> * * <p>This is offered as an optimization. If you know in advance the number of pages you will * need to support or have lazy-loading mechanisms in place on your pages, tweaking this setting * can have benefits in perceived smoothness of paging animations and interaction. If you have a * small number of pages (3-4) that you can keep active all at once, less time will be spent in * layout for newly created view subtrees as the user pages back and forth.</p> * * <p>You should keep this limit low, especially if your pages have complex layouts. By default * it is set to {@code OFFSCREEN_PAGE_LIMIT_DEFAULT}.</p> * * @param limit How many pages will be kept offscreen on either side. Valid values are all * values {@code >= 1} and {@link #OFFSCREEN_PAGE_LIMIT_DEFAULT} * @throws IllegalArgumentException If the given limit is invalid * @see #getOffscreenPageLimit() */
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();
}
複製代碼
從這段對setOffscreenPageLimit(int)方法的註釋中咱們能夠看到,當setOffscreenPageLimit被設置爲OFFSCREEN_PAGE_LIMIT_DEFAULT時候會使用RecyclerView的緩存機制。那麼咱們就來在ViewPager2中嘗試下加載Fragment是一種怎樣的效果吧。 首先咱們在ViewPager中添加多個Fragment,而且setOffscreenPageLimit使用默認值,而後再Fragment聲明週期中打印出日誌,代碼再也不貼出,直接看日誌打印的內容:
相比ViewPager,ViewPager2的Transformer功能有了很大的擴展。ViewPager2不只能夠經過PageTransformer用來設置頁面動畫,還能夠用PageTransformer設置頁面間距以及同時添加多個PageTransformer。接下來咱們就來認識下ViewPager2的PageTransformer吧!
在第一章中咱們提到了ViewPager2移除了setPageMargin方法,那麼怎麼爲ViewPager2設置頁面間距呢?其實在ViewPager2中爲咱們提供了MarginPageTransformer,咱們能夠經過ViewPager2的setPageTransformer方法來設置頁面間距。代碼以下:
viewPager2.setPageTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
複製代碼
上述代碼咱們爲ViewPager2設置了10dp的頁面間距。效果以下:
這個時候咱們應該有個疑問,爲ViewPager2設置了頁面間距後若是還想設置頁面動畫的Transformer怎麼辦呢?這時候就該CompositePageTransformer出場了。從名字上也能夠看出來它是一個組合的PageTransformer。沒錯,CompositePageTransformer實現了PageTransformer接口,同時在其內部維護了一個List集合,咱們能夠將多個PageTransformer添加到CompositePageTransformer中。
val compositePageTransformer = CompositePageTransformer()
compositePageTransformer.addTransformer(ScaleInTransformer())
compositePageTransformer.addTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
viewPager2.setPageTransformer(compositePageTransformer)
複製代碼
上述代碼中咱們經過CompositePageTransformer爲ViewPager設置了MarginPageTransformer和一個頁面縮放的ScaleInTransformer。來看下效果:
PageTransformer是一個位於ViewPager2中的接口,所以ViewPager2的PageTransformer是獨立於ViewPager的,它與ViewPager的PageTransformer沒有任何關係。雖然如此,卻沒必要擔憂。由於ViewPager2的PageTransformer和ViewPager的PageTransformer實現方式如出一轍。咱們看下上一小節中用到的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
}
}
複製代碼
在ViewPager2的官方Sample上看到了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)
複製代碼
最後,咱們來看下效果
咱們前面也已經提到了ViewPager2中新增的FragmentStateAdapter 替代了ViewPager的FragmentStatePagerAdapter。那麼來咱們就用ViewPager2來實現一個Activity中嵌套Fragment的實例。
<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" />
複製代碼
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
}
}
複製代碼
vp_fragment.adapter = AdapterFragmentPager(this)
vp_fragment.offscreenPageLimit = 3
vp_fragment.isUserInputEnabled=false
複製代碼
本篇文章咱們認識了ViewPager2的新特性以及其用法。總得來講ViewPager2相比ViewPager無論在性能上仍是在功能上都有了很大的提高。所以,我相信在不久的將來ViewPager2一定會取代ViewPager。那麼,你是否已經考慮將ViewPager2用到你的項目中了呢?
最後再來給你們推薦一下BannerViewPager。這是一個基於ViewPager實現的具備強大功能的無限輪播庫。在將來,我會在BannerViewPager 3.0版本中用ViewPager2來重構代碼。歡迎你們到GitHub關注BannerViewPager 。
第四節中ViewPager2與Fragment的代碼見: