LoginSignup
22
15

More than 3 years have passed since last update.

ViewPager2とTabLayoutでページコントロール&ページ切り替えを簡単に用意

Posted at
  • スマートフォンアプリを使っているとよく見かけるページコントロールを簡単に作ろうという話です。
    • 簡単にという事で、できればPngやJpegといった、用意するのが手間になる画像リソースも省きたいところ。

ページコントロールってなに?

  • よく見かけるこれです↓

このコンポーネントって・・・

  • iOSのHuman Interface Guidelines に定義されているコンポーネントなので、Androidにはありません:expressionless:
  • チュートリアルなんかでよく見かけ、iOSと同じデザインでヨロシクと言われた人もいるんじゃないかと:frowning2:

用意していくよ

構成

  • MainFragment (全ページを管理する親Fragment)
  • Page1Fragment (1ページ目のFragment)
  • Page2Fragment (2ページ目のFragment)
  • Page3Fragment (3ページ目のFragment)
  • Page4Fragment (4ページ目のFragment)

全ページをまとめる親ページとしてMainFragmentと、子ページとしてPage1〜4Fragmentを用意

MainFragment

Layout

  • 子ページを管理するためのViewPager2と、ページコントロール用のTabLayoutを用意。
    • ViewPager2androidx.viewpager2.widget.ViewPager2
    • TabLayoutcom.google.android.material.tabs.TabLayout
    • 必要に応じてAndroidXMaterial Componentsを取り込みましょう
  • 最下部に次のページに遷移させる為のButtonを用意。
fragment_main.xml
<?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"
    android:background="@color/color_white">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/frameLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@+id/nextLayout"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/indicator"
            android:layout_height="40dp"
            android:layout_width="wrap_content"
            android:layout_gravity="center_horizontal"
            app:tabBackground="@drawable/indicator_selector"
            app:tabGravity="center"
            app:tabIndicatorHeight="0dp"
            app:tabIndicatorFullWidth="true"
            app:tabRippleColor="@null"/>

    </FrameLayout>

    <Button
        android:id="@+id/nextLayout"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@color/button_color"
        android:stateListAnimator="@null"
        android:text="@string/next_button_text"
        android:textColor="@color/color_white"
        android:textSize="15sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
ポイント:rolling_eyes:
  • TabLayoutapp:tabBackground="@drawable/indicator_selector"は、選択中、非選択中を意識しておく
indicator_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_selected="true">
        <shape android:innerRadius="0dp" android:shape="ring" android:thickness="4dp" android:useLevel="false">
            <solid android:color="@color/blue" />
        </shape>
    </item>
    <item>
        <shape android:innerRadius="0dp" android:shape="ring" android:thickness="4dp" android:useLevel="false">
            <solid android:color="@color/gray" />
        </shape>
    </item>
</selector>
  • TabLayoutのapp:tabRippleColor="@null"は、ページコントロールの選択時のRippleEffectの設定
    • 不要であれば@nullで表示しない設定にしておく

Code(必要そうな箇所を抜粋)

MainFragment.kt
class MainFragment : Fragment() {

    private sealed class IndexItem {
        abstract fun newInstance(): Fragment

        object FirstItem : IndexItem() {
            override fun newInstance() = Page1Fragment.newInstance()
        }
        object SecondItem : IndexItem() {
            override fun newInstance() = Page2Fragment.newInstance()
        }
        object ThirdItem : IndexItem() {
            override fun newInstance() = Page3Fragment.newInstance()
        }
        object FourthItem : IndexItem() {
            override fun newInstance() = Page4Fragment.newInstance()
        }
    }

    private val indexItems = listOf(
            IndexItem.FirstItem,
            IndexItem.SecondItem,
            IndexItem.ThirdItem,
            IndexItem.FourthItem)

    // ~~~ 省略

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupViewItem()
    }

    private fun setupViewItem() {

        viewPager.adapter = object : FragmentStateAdapter(this) {
            override fun getItemCount(): Int = indexItems.size
            override fun createFragment(position: Int): Fragment {
                return indexItems[position].newInstance()
            }
        }
        viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
        TabLayoutMediator(indicator, viewPager) { _, _ -> }.attach()
    }
}
ポイント:rolling_eyes:
  • sealed classを利用し、子ページの処理を統一
  • ViewPageradapterandroidx.viewpager2.adapter.FragmentStateAdapterを利用
  • TabLayoutMediatorTabLayoutViewPager2の動きをリンクさせるためのクラス
    • タブのテキストを変更する為にも利用するが、今回は利用しないのでアタッチのみ実施

Page 1~N Fragment

  • 好きなページ数でつくってください
  • 内容もすきなままにどうぞ:relaxed:

おわり

  • いかがでしたでしょうか。ViewPager2のおかげで少ないコード数で対応可能になりました。
  • iOSのコンポーネントにあわせて、Androidをつくるのは:disappointed_relieved:感ありますががんばっていきましょう:grinning:

これからも楽しいアプリケーション開発を:dancers:

22
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
15