Android TabLayoutとViewPagerでスワイプ連動タブ制御


よくあるこういったUIのやつ

f:id:letitride:20190621173600p:plain:h350

コンテンツ部分を横スワイプでタブが左右に切り替わる

使用するコンポーネント

ViewPager

スワイプに応じてページングされたFragmentを生成し、自動でアタッチする。

TabLayout

TabのUI表現及び、リスナーを管理する。また、生成したViewPagerをセットすることでViewPagerの挙動に応じてタブを切り替えることが可能。

導入

app/buile.gradle
implementation 'com.android.support:design:28.0.0'

implementation 'com.google.android.material:material:1.0.0'こちらを入れてしまうとAndroidXのコンポーネントと競合しビルドが通らなかった。

Manifest merger failed : Attribute application@appComponentFactory value=(android.support.v4.app.CoreComponentFactory) from [com.android.support:support-compat:28.0.0] AndroidManifest.xml:22:18-91
TabLayoutとViewPagerの配置

TabLayoutとViewPagerを配置するのみでよい。また、TabItemは後述するViewPagerで生成されるので、ここで記載の必要はない

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".Posts.MainActivity">
    
    <android.support.design.widget.TabLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" android:id="@+id/tabLayout">
    </android.support.design.widget.TabLayout>

    <android.support.v4.view.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@+id/tabLayout"
            app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</android.support.constraint.ConstraintLayout>
Fragmentの定義

Fragmentはタブ、スワイプで切り替わるコンテンツ部分の定義をすればよい。R.layout.fragment_personal_postsにレイアウトを記述しているものとする。

class Tab1PostsFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_personal_posts, container, false)
    }
    companion object {
        @JvmStatic
        fun newInstance() =
            PersonalPostsFragment().apply {
                arguments = Bundle().apply {
                }
            }
    }
}

Tab1PostsFragmentと同様にTab2PostsFragmentも定義しておく。

Tab生成、制御の実装

Activityまたは上記2つのFragmentの親となるFragmentに記述する。

Tabの生成、制御はViewPagerのAdapterが管理する

viewPager.adapter = object : FragmentPagerAdapter(supportFragmentManager){
    override fun getCount(): Int { ... }
    override fun getItem(position: Int): Fragment { ... }
    override fun getPageTitle(position: Int): CharSequence? { ... }
}

FragmentPagerAdapterは一度作成されたFragmentはViewPagerが保持し、FragmentStatePagerdAapter、切り替わりの度にSaveInstance、破棄、生成される。

実装必須となるメソッドは2つだが、タブのタイトルを返すメソッドも実装できる。

getCount()はViewPagerのページ数を返す

val fragmentList = listOf<Fragment>(
    Tab1PostsFragment.newInstance(),
    Tab2PostsFragment.newInstance()
)
override fun getCount(): Int {
    return fragmentList.size
}

getItem()はpositionに応じたFragmentインスタンスを返す

override fun getItem(position: Int): Fragment {
    val fragment = fragmentList.get(position)
    return fragment
}

getPageTitle()はpositionに応じたタイトルを返す

override fun getPageTitle(position: Int): CharSequence? {
    val tabTitles = listOf<String>(
        "まとめブログ", "個人ブログ"
    )
    return resources.getString( tabTitles[position] )
}

最後にAdapterをセットしたViewPagerをTabLayoutに渡せばViewPagerに連動したタブの制御が可能となる。

viewPager.adapter = object : FragmentPagerAdapter(supportFragmentManager){ ... }
tabLayout.setupWithViewPager(viewPager)

タブのレイアウトにデザインを入れる場合

setupWithViewPager()後に

tabLayout.setupWithViewPager(viewPager)
Tab tab1 = tabLayout.getTabAt(0);
tab1.setText("Home");
tab1.setIcon(R.drawable.tab1);

val view = layoutInflater.inflate(R.layout.activity_main, null)
tab1.setCustomView(view);

のようにすれば、各タブの属性にアクセスできる。