深入瞭解ViewPager2的使用
一、ViewPager2的新特性
ViewPager2從名字就可以看出來它是ViewPager的升級版,既然是升級版那麼它相比ViewPager有哪些新功能和哪些API變化呢?我們接著往下看。
1.ViewPager2新特性
- 基於RecyclerView實現。這意味著RecyclerView的優點將會被ViewPager2所繼承。
- 支持豎直滑動。隻需要一個參數就可以改變滑動方向。
- 支持關閉用戶輸入。通過setUserInputEnabled來設置是否禁止用戶滑動頁面。
- 支持通過編程方式滾動。通過fakeDragBy(offsetPx)代碼模擬用戶滑動頁面。
- CompositePageTransformer 支持同時添加多個PageTransformer。
- 支持DiffUtil ,可以添加數據集合改變的item動畫。
- 支持RTL (right-to-left)佈局。我覺得這個功能對國內開發者來說可能用處不大..
2.相比ViewPager變化的API
ViewPager2相比ViewPager做瞭哪些改變呢?研究瞭一番之後我大概列出以下幾點:
- ViewPager2與ViewPager同是繼承自ViewGrop,但是ViewPager2被聲明成瞭final。意味著我們不可能再像ViewPager一樣通過繼承來修改ViewPager2的代碼。
- FragmentStatePagerAdapter被FragmentStateAdapter 替代
- PagerAdapter被RecyclerView.Adapter替代
- addPageChangeListener被registerOnPageChangeCallback。我們知道ViewPager的addPageChangeListener接收的是一個OnPageChangeListener的接口,而這個接口中有三個方法,當想要監聽頁面變化時需要重寫這三個方法。而ViewPager2的registerOnPageChangeCallback方法接收的是一個叫OnPageChangeCallback的抽象類,因此我們可以選擇性的重寫需要的方法即可。
- 移除瞭setPargeMargin方法。
以上所羅列的新特性和API可能並不完整,如有疏漏可以留言補充。
二、開啟ViewPager2之旅
ViewPager2位於androidx包下,也就是它不像ViewPager一樣被內置在系統源碼中。因此,使用ViewPager2需要額外的添加依賴庫。另外,android support中不包含ViewPager,也就是要使用ViewPager2必須遷移到androidx才可以。
1.添加依賴,目前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
因為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>
4.在Activity中為ViewPager設置Adapter
很簡單就完成瞭一個ViewPager的功能,來看下效果怎麼樣:
5.ViewPager2豎直滑動
接下來我們通過一行代碼為其設置豎直滑動
viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
豎直滑動用ViewPager是很難實現的,而通過ViewPager2隻需要設置一個參數即可。來看下效果:
6.頁面滑動事件監聽
上文已經提到過瞭,我們為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() } })
7.setUserInputEnabled與fakeDragBy
我們知道,在使用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的參數,當參數值為正數時表示向前一個頁面滑動,當值為負數時表示向下一個頁面滑動。
下面來看下效果圖:
演示圖中禁止瞭用戶輸入,通過按鈕點擊可以模擬用戶滑動。
三、ViewPager2的PageTransformer
相比ViewPager,ViewPager2的Transformer功能有瞭很大的擴展。ViewPager2不僅可以通過PageTransformer用來設置頁面動畫,還可以用PageTransformer設置頁面間距以及同時添加多個PageTransformer。接下來我們就來認識下ViewPager2的PageTransformer吧!
1.setPageMargin
在第一章中我們提到瞭ViewPager2移除瞭setPageMargin方法,那麼怎麼為ViewPager2設置頁面間距呢?其實在ViewPager2中為我們提供瞭MarginPageTransformer,我們可以通過ViewPager2的setPageTransformer方法來設置頁面間距。代碼如下:
viewPager2.setPageTransformer(MarginPageTransformer(resources.getDimension(R.dimen.dp_10).toInt()))
上述代碼我們為ViewPager2設置瞭10dp的頁面間距。效果如下:
2.認識CompositePageTransformer
這個時候我們應該有個疑問,為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。來看下效果:
3.ViewPager2中的PageTransformer
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 } }
4.ViewPager2的一屏多頁效果
在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與Fragment
我們前面也已經提到瞭ViewPager2中新增的FragmentStateAdapter 替代瞭ViewPager的FragmentStatePagerAdapter。那麼來我們就用ViewPager2來實現一個Activity中嵌套Fragment的實例。
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.實現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.在Activity中為ViewPager2設置FragmentStateAdapter
vp_fragment.adapter = AdapterFragmentPager(this) vp_fragment.offscreenPageLimit = 3 vp_fragment.isUserInputEnabled=false
五、ViewPager2與TabLayout
TabLayout也是項目中經常用到的一個控件,它通常會與ViewPager一起出現。那麼對於ViewPager2應該怎麼使用Tablayout呢?這需要我們認識一個新類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),第一個參數是當前Tab,第二個當前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); }
接下來我們便可以通過TabLayoutMediator將TabLayout與ViewPager2關聯起來瞭:
TabLayoutMediator(tab_layout, view_pager) { tab, position -> // 為Tab設置Text tab.text = Card.DECK[position].toString() }.attach()
使用起來非常簡單,實現效果如下圖所示:
六、小結
本篇文章我們認識瞭ViewPager2的新特性以及其用法。總得來說ViewPager2相比ViewPager不管在性能上還是在功能上都有瞭很大的提升。因此,我相信在不久的未來ViewPager2必定會取代ViewPager。那麼,你是否已經考慮將ViewPager2用到你的項目中瞭呢?
最後再來給大傢推薦一下BannerViewPager。這是一個基於ViewPager實現的具有強大功能的無限輪播庫。在未來,我會在BannerViewPager 3.0版本中用ViewPager2來重構代碼。歡迎大傢到GitHub關註BannerViewPager 。
本文涉及源碼下載
第四節中ViewPager2與Fragment的代碼見:
BannerViewPager
以上就是深入瞭解ViewPager2的使用的詳細內容,更多關於ViewPager2 使用教程的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Android如何使用ViewPager2實現頁面滑動切換效果
- Android BottomNavigationView結合ViewPager實現底部導航欄步驟詳解
- Android用viewPager2實現UI界面翻頁滾動的效果
- ViewPager+Fragment實現側滑導航欄
- 詳解Android ViewPager2中的緩存和復用機制