Search code examples
androidandroid-viewpagerandroid-viewpager2

ViewPager2: getPageWidth alternative


I replaced ViewPager with ViewPager2, but I have problem with it. ViewPager has fixed 3 pages. And the second page larger than other. When page 1 or 3 is visible, a small part of page 2 is also visible, and when page 2 is visible, other pages are not visible. To do this I used method of adapter

override fun getPageWidth(position: Int): Float {
    return if (position != MAIN_PAGE) WIDTH_SMALL_PERCENT
    else super.getPageWidth(position)
}

But adapter of ViewPager2 don't has this method and I have no idea how do it.


Solution

  • Now it is impossible without hacks. I create feature request https://issuetracker.google.com/issues/150232913.
    And in anticipation of features I create extension method to set custom layout manager

    package androidx.viewpager2.widget
    
    import androidx.recyclerview.widget.RecyclerView
    import java.lang.reflect.Field
    
    
    fun ViewPager2.setLayoutManager(layoutManager: RecyclerView.LayoutManager) {
        mRecyclerView.layoutManager = layoutManager
        mRecyclerView.clearOnChildAttachStateChangeListeners() // to ability set exact view size
        setValueToField("mLayoutManager", layoutManager)
        mScrollEventAdapter.setValueToField("mLayoutManager", layoutManager) // to correct work ViewPager2.OnPageChangeCallback
    }
    
    private fun Any.setValueToField(field: String, value: Any) {
        val f1: Field = this.javaClass.getDeclaredField(field)
        f1.isAccessible = true
        f1.set(this, value)
        f1.isAccessible = false
    }
    

    and create custom LayoutManager

    class WidePageLayoutManager(
            private val viewPager: ViewPager2,
            private val viewSmallPercentage: Float,
            private val smallViewPosition: IntArray
    ) : LinearLayoutManager(viewPager.context, HORIZONTAL, false) {
    
    
        // copied from ViewPager2.LinearLayoutManagerImp
        override fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {
            val pageLimit: Int = viewPager.offscreenPageLimit
            val offscreenSpace: Int = viewPager.pageSize * pageLimit
            extraLayoutSpace[0] = offscreenSpace
            extraLayoutSpace[1] = offscreenSpace
        }
    
        // copied from ViewPager2.LinearLayoutManagerImp
        override fun requestChildRectangleOnScreen(
                parent: RecyclerView,
                child: View,
                rect: Rect,
                immediate: Boolean,
                focusedChildVisible: Boolean
        ): Boolean {
            return false // users should use setCurrentItem instead
        }
    
        // this need to show MainPage bounds on left/right side menu
        override fun checkLayoutParams(lp: RecyclerView.LayoutParams?): Boolean {
            if (lp != null && lp.viewAdapterPosition in smallViewPosition) {
                lp.width = (width * viewSmallPercentage).toInt()
            }
            return super.checkLayoutParams(lp)
        }
    
        override fun canScrollHorizontally(): Boolean {
            return super.canScrollHorizontally() && viewPager.isEnabled
        }
    }