如何使用 TabLayout + ViewPager (使用Kotlin)

如何使用 TabLayout + ViewPager (使用Kotlin)

情境

如果要使用 TabLayout 在 Android 早期是很痛苦的,漸漸的有很多優秀的第三方出來支援,包含 Indicater、ViewPager 等等…。

由於太常使用這些元件,因此 Google 這次在 android.support.design 這包 library 內,直接就提供 TabLayout 讓你快速建立好一個 Tab 元件。

程式碼下載

你可以到 GitHub 上面觀看或下載完整的程式碼。

程式碼說明

一開始要使用這個元件,在 Gradle 就要把它 import 進來,這邊的版本號是根據你的 Android SDK 來調整版號,因此你可以根據自己的版本來進行調整。

dependencies {
    implementation 'com.google.android.material:material:1.0.0'  
    implementation 'androidx.appcompat:appcompat:1.1.0'
}

接著在 activity_main.xml 內掛上你的 TabLayout,在 TabLayout 的下方放了一個 ViewPager,等下可以在 MainActvitity.java 程式內操作這兩個元件。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs"
            android:minHeight="?attr/actionBarSize"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>
    <androidx.viewpager.widget.ViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="@+id/tabs"
            app:layout_constraintTop_toBottomOf="@+id/tabs"
            app:layout_constraintEnd_toEndOf="@+id/tabs"/>
</androidx.constraintlayout.widget.ConstraintLayout>

ViewPager 會需要一個自定義的 Adapter,這個 Adapter 需要一個佈局,所以我們設計了一個 pager_item.xml 如下方佈局。

  • pager_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    <TextView
            android:layout_centerInParent="true"
            android:id="@+id/item_subtitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="" />
</RelativeLayout>

接著我們來看一下 MainActvity.java 這段程式碼,可以看到一開始我們從 xml 取出 TabLayout 以及 ViewPager 的物件。

class MainActivity : AppCompatActivity() {  
  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        setContentView(R.layout.activity_main)  
        tabs.addTab(tabs.newTab().setText("Tab 1"))  
        tabs.addTab(tabs.newTab().setText("Tab 2"))  
        tabs.addTab(tabs.newTab().setText("Tab 3"))  
        viewpager.adapter = SamplePagerAdapter()  
  viewpager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs))  
  tabs.addOnTabSelectedListener(TabLayout.ViewPagerOnTabSelectedListener(viewpager))
    }  
  
 class SamplePagerAdapter: PagerAdapter() {  
  override fun getCount(): Int {  
   return 3  
  }  
  
  override fun isViewFromObject(view: View, o: Any): Boolean {  
   return o === view  
  }  
  
  override fun getPageTitle(position: Int): CharSequence? {  
             return "Page: Item${position + 1}"
  }  
   
  override fun instantiateItem(container: ViewGroup, position: Int): Any {  
   val view = LayoutInflater.from(container.context).inflate(R.layout.pager_item, container, false)  
   container.addView(view)  
   view.item_title.text = getPageTitle(position)  
   return view  
  }  

  override fun destroyItem(container: ViewGroup, position: Int, any: Any) {  
   container.removeView(any as View)  
  }  
 }  
}

這邊你會發現一個重點,就是當 TabLayout 被點選的時候 下方的 ViewPager 就必須跟著滾動,同理當下方 ViewPager 滾動的時候 上方的 TabLayout 就必須跟著跳動,因此會加入這兩行。

viewpager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs))  
tabs.setOnTabSelectedListener(TabLayout.ViewPagerOnTabSelectedListener(viewpager))

但是你會發現,在 setOnTabSelectedListener API 24 的時候已經被棄用了,取而代之的是 addOnTabSelectedListener,因此你可以看到使用以下的方法,也可以跟 ViewPager 和 TabLayout 的連動綁在一起。

viewpager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabs))  
tabs.addOnTabSelectedListener(TabLayout.ViewPagerOnTabSelectedListener(viewpager))

另外還有一種方法就是當你在點選 Tab 頁面的時候,還會額外再進行一些事情,那麼你就可以透過以下的方式來進行切換。

tabs.setOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {  
 override fun onTabSelected(tab: TabLayout.Tab) {  
  viewpager.currentItem = tab.position  
 }  
    override fun onTabUnselected(tab: TabLayout.Tab) {}  
    override fun onTabReselected(tab: TabLayout.Tab) {}  
})

同樣的這個方法也在 API 24 被棄用了,當然還是有替代方案。

tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {  
 override fun onTabSelected(tab: TabLayout.Tab) {  
  viewpager.currentItem = tab.position  
 }  
    override fun onTabUnselected(tab: TabLayout.Tab) {}  
    override fun onTabReselected(tab: TabLayout.Tab) {}  
})

如此一來就可以很輕鬆的把上下兩個元件的連動綁在一起了,如下圖短短幾行就可以看到很棒的效果。


這邊可以看到滾動翻頁的效果。


這樣就是一個簡單的 TabLayout 操作了。