ViewPager2 使用說明書


ViewPager2 使用說明書

零、Demo

項目源碼

演示 apk

如果對你有用,希望能給個 star,謝謝。

一、功能

官方關於使用 ViewPager2 創建滑動視圖的說明:

Swipe views allow you to navigate between sibling screens, such as tabs, with a horizontal finger gesture, or swipe. This navigation pattern is also referred to as horizontal paging. This topic teaches you how to create a tab layout with swipe views for switching between tabs, along with how to show a title strip instead of tabs.

大意是說,使用 ViewPager2 可以實現 Views 或頁面的水平方向(常用水平,垂直也支持)的滑動。也可以結合 Tab 組件使用。

二、基本使用

2.1 依賴引用

implementation "androidx.viewpager2:viewpager2:1.0.0"

2.2 版本說明

1.0.0 版本是 2019 年 11 月 20 日 更新的。

1.1.0-beta01 測試版本是 2021 年 8 月 4 日 ,最近才更新的。

具體的更新內容,和最新版本的信息,可以在這個鏈接查到。

2.3 基本使用

ViewPager2 使用方式簡單。學習需要掌握以下幾個要素:XML 聲明、定義 Adapter 、設置滑動監聽。

2.3.1 XML 布局中使用

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

2.3.2 常用 Adapter 類型

ViewPager2.java 部分源碼:

private void initialize(Context context, AttributeSet attrs) {
        mAccessibilityProvider = sFeatureEnhancedA11yEnabled
                ? new PageAwareAccessibilityProvider()
                : new BasicAccessibilityProvider();

        mRecyclerView = new RecyclerViewImpl(context);
        mRecyclerView.setId(ViewCompat.generateViewId());
        mRecyclerView.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);

        mLayoutManager = new LinearLayoutManagerImpl(context);
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
        setOrientation(context, attrs);

        mRecyclerView.setLayoutParams(
                new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());
 			  ...
}

通過 ViewPager2 的源碼,可以看到它內部維護了一個 RecyclerView,來實現列表視圖的顯示和滑動控制。

所以,RecyclerView.Adapter 可以直接用於 ViewPager2 ,這個應該是大家在使用 RecyclerView 組件時經常用到的。

另外,ViewPager2 的依賴包中,還提供了 FragmentStateAdapter ,繼承自RecyclerView.Adapter。主要用於 ViewPager2 和 Fragment 的結合使用。下文中會介紹如何使用。

image-20210818231024635

2.3.3 滑動事件監聽

void registerOnPageChangeCallback(@NonNull OnPageChangeCallback callback)

通過該函數,注冊 Page 變化的事件監聽。

OnPageChangeCallback 類的源碼如下:

 public abstract static class OnPageChangeCallback {
        /**
         * This method will be invoked when the current page is scrolled, either as part
         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
         *
         *
         * @param position Position index of the first page currently being displayed.
         *                 Page position+1 will be visible if positionOffset is nonzero.
         * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
         * @param positionOffsetPixels Value in pixels indicating the offset from position.
         */
        public void onPageScrolled(int position, float positionOffset,
                @Px int positionOffsetPixels) {
        }

        /**
         * This method will be invoked when a new page becomes selected. Animation is not
         * necessarily complete.
         *
         * @param position Position index of the new selected page.
         */
        public void onPageSelected(int position) {
        }

        /**
         * Called when the scroll state changes. Useful for discovering when the user begins
         * dragging, when a fake drag is started, when the pager is automatically settling to the
         * current page, or when it is fully stopped/idle. {@code state} can be one of {@link
         * #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
         */
        public void onPageScrollStateChanged(@ScrollState int state) {
        }
    }

有三個回調方法:

  1. onPageScrolled ,返回 Page 滑動位置偏移或像素的變化。
  2. onPageSelected ,滑動后的 page position。
  3. onPageScrollStateChanged,滑動狀態的變化。

從一個 Page 滑動到另一個 Page ,Callback 的回調情況:

2021-08-18 23:28:14.046  onPageSelected() called with: position = 0
2021-08-18 23:28:14.047  onPageScrolled() called with: position = 0, positionOffset = 0.0, positionOffsetPixels = 0
2021-08-18 23:28:37.333  onPageScrollStateChanged() called with: state = 1
2021-08-18 23:28:37.350  onPageScrolled() called with: position = 0, positionOffset = 0.016666668, positionOffsetPixels = 18
......
2021-08-18 23:28:37.427  onPageScrolled() called with: position = 0, positionOffset = 0.20277777, positionOffsetPixels = 219
2021-08-18 23:28:37.431  onPageScrollStateChanged() called with: state = 2
2021-08-18 23:28:37.450  onPageSelected() called with: position = 1
2021-08-18 23:28:37.451  onPageScrolled() called with: position = 0, positionOffset = 0.28611112, positionOffsetPixels = 309
......
2021-08-18 23:28:37.717  onPageScrolled() called with: position = 0, positionOffset = 0.99814814, positionOffsetPixels = 1078
2021-08-18 23:28:37.734  onPageScrolled() called with: position = 1, positionOffset = 0.0, positionOffsetPixels = 0
2021-08-18 23:28:37.734  onPageScrollStateChanged() called with: state = 0
onPageScrollStateChanged 滑動狀態 state:
SCROLL_STATE_IDLE = 0
SCROLL_STATE_DRAGGING = 1
SCROLL_STATE_SETTLING = 2

觀察日志,回調的特征:

  1. 初次加載,會回調 onPageSelected 和 onPageScrolled 方法,position 為 0。
  2. 向右滑動一頁時,首先觸發 onPageScrollStateChanged 回調,state 為 SCROLL_STATE_DRAGGING 。然后 onPageScrolled 多次回調,可以看到位置偏移量的變化。在 onPageScrolled 多次回調中間,會回調 onPageScrollStateChanged 方法,state 變為 SCROLL_STATE_SETTLING 位置固定。然后回調 onPageSelected ,position 為滑動后的位置。
  3. 最后一次 onPageScrolled 回調,position 變為 1。之后回調 onPageScrollStateChanged ,state 變為 SCROLL_STATE_IDLE 。

三、使用方式

3.1 + View

3.1.1 效果演示

views

3.1.2 Adapter 代碼實現

首先定義一個 item xml 布局。

<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/black"
        android:layout_width="match_parent"
        android:layout_height="280dp"
        android:gravity="center"
        android:textColor="#ffffff"
        android:textSize="22sp" />
</LinearLayout>

自定義 ViewAdapter。

import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class ViewAdapter : RecyclerView.Adapter<ViewAdapter.PagerViewHolder>() {
    var data: List<Int> = ArrayList()

    class PagerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val mTextView: TextView = itemView.findViewById(R.id.tv_text)
        private val colors = arrayOf("#CCFF99", "#41F1E5", "#8D41F1", "#FF99CC")

        fun bindData(i: Int) {
            mTextView.text = i.toString()
            mTextView.setBackgroundColor(Color.parseColor(colors[i]))
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagerViewHolder {
        return PagerViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_page, parent, false))
    }

    override fun onBindViewHolder(holder: PagerViewHolder, position: Int) {
        holder.bindData(position)
    }

    override fun getItemCount(): Int {
        return data.size
    }
}

3.1.3 ViewPager2 配置

在 Activity 或 Fragment 中的布局文件,直接使用 ViewPager2 標簽。

然后在代碼中,配置 Adapter 便完成了,實現 3.1.1 中的效果。

    val viewAdapter = ViewAdapter()
    viewAdapter.data = listOf(1, 2, 3, 4)
    viewPager2 = findViewById(R.id.view_pager)
    viewPager2.apply {
      adapter = viewAdapter
    }

3.1.4 使用場景

Banner ,輪播廣告圖。可以配合定時器,進行定時滾動展示。

3.2 + Fragment

3.2.1 效果展示

整體效果,看上去與使用 RecyclerView.Aadapter + Views 的形式差不多。

前者在於局部控件,和 Fragment 配合,更多是整頁的滑動。

這個例子中,增加了一些動畫效果,及一屏多頁的效果,在下文中會詳細說明。

fragment

3.2.2 Adapter 代碼實現

ViewPager2 和 Fragment 配合使用, Adapter 前文也提到過,ViewPager2 的依賴包中,特別提供了 FragmentStateAdapter

使用起來比較方便,只用重寫兩個方法:getItemCount 和 createFragment。示例如下:

TestFragment 是用 AndroidStudio 生成的模板 BlankFragment ,並將兩個入參,分別設置為頁面的文本內容及背景顏色。

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter

class FragmentPagerAdapter(fragmentActivity: FragmentActivity) :
    FragmentStateAdapter(fragmentActivity) {

    override fun getItemCount(): Int {
        return 4
    }

    private val colors = arrayOf("#CCFF99", "#41F1E5", "#8D41F1", "#FF99CC")

    override fun createFragment(position: Int): Fragment {
        return when (position) {
            PAGE_MESSAGE -> TestFragment.newInstance("消息$position", colors[0])
            PAGE_CONTACT -> TestFragment.newInstance("通訊錄$position", colors[1])
            PAGE_SETTING -> TestFragment.newInstance("設置$position", colors[2])
            PAGE_MINE -> TestFragment.newInstance("我$position", colors[3])
            else -> TestFragment.newInstance("$position", colors[0])
        }
    }

    companion object {
        const val PAGE_MESSAGE = 0
        const val PAGE_CONTACT = 1
        const val PAGE_SETTING = 2
        const val PAGE_MINE = 3
    }
}

3.2.3 ViewPager2 配置

使用方式簡單, 直接將 Adapter 實例化賦值給 ViewPager2 對象的 adapter 。

3.2.4 場景

頁面間的切換,支持手勢滑動。支持過渡的動畫。

還可以結合 Tab 組件,進行組件間的聯動。

3.3 + TabLayout

3.3.1 效果展示

tablayout

3.3.2 代碼實現

TabLayout 一般放在頁面的頂部位置。在頁面布局的 xml 中,用 com.google.android.material.tabs.TabLayout 標簽聲明。

TabLayout 組件是 material 包中的組件,Android Studio 新建項目,會自動引入這個依賴。

如果是老版本的項目,想要引入 TabLayout 組件,可以引入以下依賴,具體版本以官網為准。

'com.google.android.material:material:1.3.0'

XML 中聲明:

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

代碼中,通過 TabLayoutMediator 將 TabLayout 和 ViewPager2 進行綁定。

      val tabLayout = findViewById<TabLayout>(R.id.tab_layout)
      TabLayoutMediator(tabLayout, viewPager2) { tab, position ->
           //設置標簽名稱
           tab.text = "OBJECT ${(position + 1)}"
      }.attach()

這樣就實現了,標簽導航和頁面滑動的聯動。

3.3.3 使用場景

一般可以用於類似新聞或者電商 ,各種類目頁面間的切換。

3.4 + BottomNavigationView

3.4.1 效果展示

bottomNavigation

3.4.2 代碼實現

監聽 BottomNavigationView 的 setOnNavigationItemSelectedListener ,改變 ViewPager2 的 item。

監聽 ViewPager2 的 onPageSelected ,改變 BottomNavigationView 的 item。

注意兩組件的數量和位置要對應。

 				// 頁面適配器
        val fragmentPagerAdapter = FragmentPagerAdapter(this)
        val viewPager2 = findViewById<ViewPager2>(R.id.viewPager2)
        viewPager2.apply {
            adapter = fragmentPagerAdapter
            // 不提前加載
            offscreenPageLimit = fragmentPagerAdapter.itemCount
            // 單動畫效果
            setPageTransformer(ScaleInTransformer())
        }

        // 底部菜單
        val bottomNavigation = findViewById<BottomNavigationView>(R.id.bottomNavigation)
        // 設置監聽
        bottomNavigation.setOnNavigationItemSelectedListener { menuItem ->
            when (menuItem.itemId) {
              //設置 viewPager2 item 位置
                R.id.menu_messages -> {
                    viewPager2.setCurrentItem(0, true)
                 		// 如返回 false ,bottomNavigation 被點擊的 item ,不會被置為選中狀態
                    true
                }
                R.id.menu_contacts -> {
                    viewPager2.setCurrentItem(1, true)
                    true
                }
                R.id.menu_setting -> {
                    viewPager2.setCurrentItem(2, true)
                    true
                }
                R.id.menu_mine -> {
                    viewPager2.setCurrentItem(3, true)
                    true
                }
                else -> throw IllegalArgumentException("未設置的 menu position,請檢查參數")
            }
        }

        // 監聽頁面變化
        viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
              	// 設置 bottomNavigation item 狀態
                bottomNavigation.menu.getItem(position).isChecked = true
            }
        })

3.4.3 使用場景

帶底部導航的多頁面導航 APP 。

3.5 其它

3.5.1 預加載

ViewPager2 的 offscreenPageLimit 屬性。

設置為 ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT ,不進行預加載和緩存。

3.5.2 過渡效果

支持單效果和多效果設置,都是通過 setPageTransformer 函數。

  1. 單效果

    直接傳入 ViewPager2.PageTransformer 的子類,即可以設置對應的效果。

    ViewPager2 包中提供了 MarginPageTransformer 頁面邊距效果,可以直接在項目中使用。

    也可以自定義實現 PageTransformer 。

  2. 組合效果

    ViewPager2 包中的 CompositePageTransformer 類,該類中,維護了 List

    可以設置多種 PageTransformer 組合效果。

下面提供幾種效果的展示和代碼,可以學習下自定義實現自己的效果:

1. 深度變化效果
depth
import android.view.View
import androidx.viewpager2.widget.ViewPager2

private const val MIN_SCALE = 0.75f

class DepthPageTransformer : ViewPager2.PageTransformer {

    override fun transformPage(view: View, position: Float) {
        view.apply {
            val pageWidth = width
            when {
                position < -1 -> { // [-Infinity,-1)
                    // This page is way off-screen to the left.
                    alpha = 0f
                }
                position <= 0 -> { // [-1,0]
                    // Use the default slide transition when moving to the left page
                    alpha = 1f
                    translationX = 0f
                    translationZ = 0f
                    scaleX = 1f
                    scaleY = 1f
                }
                position <= 1 -> { // (0,1]
                    // Fade the page out.
                    alpha = 1 - position

                    // Counteract the default slide transition
                    translationX = pageWidth * -position
                    // Move it behind the left page
                    translationZ = -1f

                    // Scale the page down (between MIN_SCALE and 1)
                    val scaleFactor = (MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)))
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                else -> { // (1,+Infinity]
                    // This page is way off-screen to the right.
                    alpha = 0f
                }
            }
        }
    }
}
2. 比例放大進入效果
scarle
import android.view.View
import androidx.viewpager2.widget.ViewPager2
import java.lang.Math.abs

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
  }
}
3.縮放進入退出效果
zoom
private const val MIN_SCALE = 0.85f
private const val MIN_ALPHA = 0.5f

class ZoomOutPageTransformer : ViewPager2.PageTransformer {
    override fun transformPage(view: View, position: Float) {
        view.apply {
            val pageWidth = width
            val pageHeight = height
            when {
                position < -1 -> { // [-Infinity,-1)
                    // This page is way off-screen to the left.
                    alpha = 0f
                }
                position <= 1 -> { // [-1,1]
                    // Modify the default slide transition to shrink the page as well
                    val scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position))
                    val vertMargin = pageHeight * (1 - scaleFactor) / 2
                    val horzMargin = pageWidth * (1 - scaleFactor) / 2
                    translationX = if (position < 0) {
                        horzMargin - vertMargin / 2
                    } else {
                        horzMargin + vertMargin / 2
                    }

                    // Scale the page down (between MIN_SCALE and 1)
                    scaleX = scaleFactor
                    scaleY = scaleFactor

                    // Fade the page relative to its size.
                    alpha = (MIN_ALPHA +
                            (((scaleFactor - MIN_SCALE) / (1 - MIN_SCALE)) * (1 - MIN_ALPHA)))
                }
                else -> { // (1,+Infinity]
                    // This page is way off-screen to the right.
                    alpha = 0f
                }
            }
        }
    }
}
4. PageTransformer 頁面邊距效果
margin

3.5.3 禁止手動滑動

viewPager2.isUserInputEnabled = true or false

3.5.4 模擬滑動

 	viewPager2.beginFakeDrag()
	if (viewPager2.fakeDragBy(-300f)) {
  	viewPager2.endFakeDrag()
	}

3.5.5 滑動方向

viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL or ViewPager2.ORIENTATION_HORIZONTAL

四、延伸擴展

  1. 如何自定義 PageTransformer
  2. ViewPager2 中 Fragment 的生命周期變化
  3. 更多使用場景擴展

五、參考資料

《還在用 ViewPager?是時候替換成 ViewPager2 了!》

《Google 官方文檔》

《使用 ViewPager2 創建包含標簽的滑動視圖》

感謝!

關注 autismbug,不寫 bug!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM