嗨大家今天過得好嗎?週末難得有空閒可以走出舒適圈多認識新朋友,便下載了交友軟體開始左滑、右滑起來,突然想到這一頁接一頁的換頁之流暢,換頁之間又帶點動畫特效就像真的翻頁,難道是用 ViewPager 實現的?聽說還新出了繼承自 RecyclerView 的 ViewPager2 也來研究一下好了!( 憑實力單身 )
跟 RecyclerView 要搭配 RecyclerView.Adapter 一樣,ViewPager 也有搭配的 PagerAdapter,PagerAdapter 有兩個必要實作的 method 是override fun isViewFromObject(view: View, object: Any): Boolean
和 override fun getCount(): Int
,isViewFromObject
是要回傳當前畫面和綁定的物件是否一致,getCount
是返回當前畫面的個數,但實作這兩個物件後只是讓 PagerAdapter 可以處理換頁的邏輯,要把物件和畫面綁定的方法是透過 override fun instantiateItem(container: ViewGroup, position: Int): Any
的 method,通常會在 instantiateItem
的 method 中創建子畫面、綁定到 container 上、最後再回傳子畫面 ( isViewFromObject
就是比對當前的畫面和 instantiateItem
的子畫面是否相符 )。有綁定畫面和物件的方法當然還有移除的方法是 override fun destroyItem(container: ViewGroup, position: Int, view: Any)
,把 container
階層中的 view
移除。
透過 PagerAdapter 的實作我們已經可以實現一個換頁的機制了,也有基本的換頁動畫,但如果想來點不一樣的動畫,譬如縮放顯示或淡入淡出的話要怎麼實現呢?可以透過 ViewPaager.PageTransformer 處理!在 PageTransformer 的 transformPage()
method 會提供 ViewPager 當前頁面的 position 還有當前的 View,position 的數值介於 -1 到 1 之間,代表向後或是向前滑動,停在當前的位置就代表 position 為 0,利用 position 可以對 View 的縮放、透明度做比例特效。交友軟體的左滑右滑應該也是透過 position 這個參數實現動畫的。
自動換頁的效果可以透過 Handler 的方式指定 ViewPager 的當前頁面, setCurrentItem(position, true)
帶 true
的話就會有換頁動畫,看起來就像是自動滑動 ViewPager 一樣,如果不想要動畫效果就改成 false
就好。使用 Handler 記得要在 View 銷毀時一併移除 callback,不然就可能會有 Leak 的問題。
那如果滑動到最後一頁時想要接續滑動到第一頁,達到像無限換頁的效果呢?首先需要讓 PagerAdapter 的數量前後加一頁,另外再新增 method 維護正確的 position,再來使用 addOnPageChangeListener
監聽滑動,在 listener 的 onPageSelected
紀錄當前的頁面和設定下一頁要換到哪一頁,當從最後兩頁滑到最後一頁時,在滑動動畫結束時載入第一頁作為下一頁,看起來就像無限滑動的列表,同理從第一頁往前滑時也是會滑到最後一頁,在滑動結束時載入倒數第二頁。
話不多說直接上 code:
class BannerPagerAdapter(val listener: BannerPagerAdapterListener) : PagerAdapter() {
val list = mutableListOf<Banner>()
interface BannerPagerAdapterListener {
fun openBanner(banner: Banner)
}
override fun isViewFromObject(view: View, any: Any): Boolean = view == any
override fun getCount(): Int = if (list.isEmpty()) 0 else list.size + 2
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val bannerView = LayoutInflater.from(container.context).inflate(R.layout.adapter_banner_pager, container, false)
val bannerCover = bannerView.venue_activities_banner_pager_item_cover
val virtualPosition = getRealPosition(position)
bannerView.setOnClickListener {
if (list.isNotEmpty()) {
listener.openVenueActivity(list[virtualPosition])
}
}
container.addView(bannerView)
return bannerView
}
override fun destroyItem(container: ViewGroup, position: Int, view: Any) {
container.removeView(view as View)
}
private fun getRealPosition(position: Int): Int {
return if (position == 0) {
list.size - 1
} else if (position == list.size + 1) {
0
} else {
position - 1
}
}
fun addAll(banners: List<Banner>) {
if (banners.isNotEmpty()) {
this.list.clear()
this.list.addAll(venueActivity)
notifyDataSetChanged()
}
}
}
class MainFragment() {
private var handler: Handler? = null
private var bannerViewPager: ViewPager? = null
override fun onViewCreated() {
bannerViewPager = view.findViewById<View>(R.id.banner_view_pager) as ViewPager
handler = Handler() // on UI thread
val autoPager = Runnable {
bannerViewPager?.let {
if (it.currentItem + 1 < it.adapter?.count ?: 0) {
it.currentItem += 1
}
}
// 因為前後都有加一頁,因此第一頁的 index 是 1 不是 0
handler?.postDelayed({ bannerViewPager?.setCurrentItem(1, false) }, INITIAL_DELAY.toLong())
handler?.postDelayed(autoPager, BANNER_SWITCH_DELAY.toLong())
bannerViewPager?.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
var jumpPosition = -1
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) {
val realCount = bannerViewPager?.adapter?.count?.minus(2) ?: 0
if (position == 0) {
jumpPosition = realCount
} else if (position == realCount + 1) {
jumpPosition = 1
}
}
override fun onPageScrollStateChanged(state: Int) {
handler?.removeCallbacksAndMessages(null) // If token is null, all callbacks and messages will be removed.
if (state == ViewPager.SCROLL_STATE_IDLE) {
bannerIsDragging(false)
// 倒數最後一頁和第一頁在動畫結束時分別載入第一頁和最後一頁
if (jumpPosition >= 0) {
bannerViewPager?.setCurrentItem(jumpPosition, false)
jumpPosition = -1
}
handler?.postDelayed(autoPager, BANNER_SWITCH_DELAY.toLong())
} else if (state == ViewPager.SCROLL_STATE_DRAGGING) {
bannerIsDragging(true)
}
}
})
}
明天會繼續分享 Pager 的主題,討論 ViewPager2 有哪些改變還有怎麼轉移,喜歡今天分享內容的邦友請繼續關注「打造一個厲害的普通 Android App - 使用者體驗優化」的主題,我們明天見。