- スマートフォンアプリを使っているとよく見かけるページコントロールを簡単に作ろうという話です。
- 簡単にという事で、できればPngやJpegといった、用意するのが手間になる画像リソースも省きたいところ。
ページコントロールってなに?
- よく見かけるこれです↓
このコンポーネントって・・・
- iOSのHuman Interface Guidelines に定義されているコンポーネントなので、Androidにはありません
- チュートリアルなんかでよく見かけ、iOSと同じデザインでヨロシクと言われた人もいるんじゃないかと
用意していくよ
構成
- MainFragment (全ページを管理する親Fragment)
- Page1Fragment (1ページ目のFragment)
- Page2Fragment (2ページ目のFragment)
- Page3Fragment (3ページ目のFragment)
- Page4Fragment (4ページ目のFragment)
全ページをまとめる親ページとしてMainFragment
と、子ページとしてPage1〜4Fragment
を用意
MainFragment
Layout
- 子ページを管理するための
ViewPager2
と、ページコントロール用のTabLayout
を用意。-
ViewPager2
→androidx.viewpager2.widget.ViewPager2
-
TabLayout
→com.google.android.material.tabs.TabLayout
- 必要に応じてAndroidXやMaterial 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>
ポイント
-
TabLayout
のapp: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()
}
}
ポイント
-
sealed class
を利用し、子ページの処理を統一 -
ViewPager
のadapter
はandroidx.viewpager2.adapter.FragmentStateAdapter
を利用 -
TabLayoutMediatorは
TabLayout
とViewPager2
の動きをリンクさせるためのクラス- タブのテキストを変更する為にも利用するが、今回は利用しないのでアタッチのみ実施
Page 1~N Fragment
- 好きなページ数でつくってください
- 内容もすきなままにどうぞ
おわり
- いかがでしたでしょうか。
ViewPager2
のおかげで少ないコード数で対応可能になりました。 - iOSのコンポーネントにあわせて、Androidをつくるのは感ありますががんばっていきましょう
これからも楽しいアプリケーション開発を