アプリ導入画面のチュートリアル(ウォークスルー)をViewPagerで実装する【Android】

2020年5月4日

ユーザにアプリの機能を最初に案内してあげることで、利便性が向上しますし、アプリ自体のPRにも繋がりますよね。

「アプリの取扱説明書」としてよく使われるのは、ウォークスルーと呼ばれるデザインで、「1つの画像と、それに関する説明」の画面を横スライドで切り替えていくものです。

↑こんな感じのウォークスルーですが、Android アプリだと ViewPager を使うことで実現できます。

本記事は、ウォークスルーの実装を通して ViewPager の使い方をまとめた備忘録です。

(ソースコードは GitHub にあげています。)

前準備(スキップ可)

実装とは無関係ですが、ウォークスルーの作成には以下が必要です。

  • 機能説明に使う画像を用意する
  • ウォークスルーの完成イメージを作る

画像の用意

イラストを自分で作成できない人(私)は、フリー画像を使うことになるかと思うので、著作権フリーのサイトなどから、アプリ説明のイメージに合った画像を探しておきます。

こちらのサイトなどはオススメです。

完成イメージを作る

Adobe XDSketch など、プロトタイプ作成ツールでウォークスルーの完成イメージを作っておくと何かと便利です。

ソースコードを実装する際に、レイアウト調整に大きな時間を取られずに済みます( ・∇・)

個人的には無料で使える Adobe XD がオススメです(こちらで紹介しています)。

事前にプロトタイプを作成することで、以下のような利点があると考えています。

  • Adobe XD に画像を取り込むことで、「画像サイズの調整」「画像同士の結合」といった画像加工ができる。
  • Android Studio で必要となる画像サイズを自動で書き出してくれる
  • 色コードや文字サイズ、テキスト内容などを事前に決めておくことで、ソースコードの実装に集中できる

ウォークスルーの実装

レイアウトファイルを作成

以下を作成します。

  • ViewPager を配置したレイアウトファイル(activity_main.xml)
  • ViewPager に表示するフラグメントのレイアウトファイル(walkthrough_fragment.xml)

activity_main.xml





LinearLayout (id = indicator_area) の部分は、ウォークスルーのページ数を表すインディケータを表示します。

walkthrough_fragment.xml




ウォークスルーのページ数分だけフラグメントを用意します。

今回はサンプルということもあり、上記1つのレイアウトファイルを使い回しています。

ViewPager の実装

ViewPager を使用するにあたり、以下を実装します。

  • ViewPager に表示するフラグメント(WalkThroughFragment.kt)
  • フラグメントをまとめて ViewPager に渡すアダプター(CustomAdapter.kt)

実装の1例にすぎませんが、以下にコードを貼っています。

WalkThroughFragment.kt

class WalkThroughFragment : Fragment(){

    var walkThroughType : WalkThroughType? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.walkthrough_fragment, container, false)
    }

    /*
    * View の初期化。
    *
    * 表示するフラグメントの種類(WalkThroughType)に応じて View を変更している。
    * */
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        arguments?.takeIf { it.containsKey(WalkThroughTypeKey) }?.apply {
            walkThroughType = WalkThroughType.values().mapNotNull {
                if (getInt(WalkThroughTypeKey) == it.ordinal) it else null}.first()
            initWalkThroughPage(view)
        }

    }

    private fun initWalkThroughPage(argView : View) {

        val linearLayout : FrameLayout = argView.findViewById(R.id.frame_layout)
        val imageView : ImageView = argView.findViewById(R.id.imageView)
        val textView : TextView = argView.findViewById(R.id.title)

        when (walkThroughType){
            WalkThroughType.First -> {
                linearLayout.setBackgroundResource(R.color.walk_through_1)
                imageView.setImageResource(R.mipmap.fragment1)
                textView.text = getText(R.string.first_fragment_title)
            }
            WalkThroughType.Second -> {
                linearLayout.setBackgroundResource(R.color.walk_through_2)
                imageView.setImageResource(R.mipmap.fragment2)
                textView.text = getText(R.string.second_fragment_title)
            }
            WalkThroughType.Third -> {
                linearLayout.setBackgroundResource(R.color.walk_through_3)
                imageView.setImageResource(R.mipmap.fragment3)
                textView.text = getText(R.string.third_fragment_title)
            }
        }

    }

基本的なフラグメントの実装です。

  • onCreateView で、フラグメントのレイアウトを指定
  • onViewCreated で、レイアウト内の View を初期化。ここでは ImageView に各ページに対応した画像を、TextView に説明文を挿入しています。

CustomAdapter.kt

//ウォークスルーに表示するフラグメントの種類
enum class WalkThroughType{
    First,
    Second,
    Third
}

const  val WalkThroughTypeKey = "WalkThroughType"

class CustomAdapter (fm : FragmentManager) : FragmentPagerAdapter(fm){

    override fun getCount(): Int = WalkThroughType.values().size

    override fun getItem(position: Int): Fragment {
        val fragment = WalkThroughFragment()
        fragment.arguments = Bundle().apply {
            putInt(WalkThroughTypeKey,
                WalkThroughType.values().mapNotNull { if (position == it.ordinal) it.ordinal else null}.first())
        }
        return fragment
    }
}

ViewPager には、FragmentPagerAdapter を継承したクラスを渡す必要があります。

FragmentPagerAdapter を継承したクラスでは、以下をオーバーライドする必要があります。

  • getCount() ... ViewPager のページ数を返す
  • getItem() ... ViewPager に表示するフラグメントを返す

ViewPager に Adapter を渡す

class MainActivity : AppCompatActivity() {

    var scale : Float = 0.0f

    lateinit var viewPager: ViewPager
    lateinit var viewPagerAdapter : CustomAdapter
    lateinit var indicatorArea : LinearLayout
    private var indicatorViewList : ArrayList = ArrayList()


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        scale = resources.displayMetrics.density

        viewPager = findViewById(R.id.viewPager)
        viewPagerAdapter = CustomAdapter(supportFragmentManager)
        viewPager.adapter = viewPagerAdapter
        viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener(){
            override fun onPageSelected(position: Int) {
                for (i in 0 until indicatorViewList.size){
                    if (i == position)
                        indicatorViewList[i].background = ResourcesCompat.getDrawable(resources,R.drawable.indicator_view_active, null)
                    else
                        indicatorViewList[i].background = ResourcesCompat.getDrawable(resources,R.drawable.indicator_view_inactive, null)

                }
            }
        })

        //ウォークスルーのページ数だけインディケータを表示
        indicatorArea = findViewById(R.id.indicator_area)

        val indicatorWidth = resources.getDimension(R.dimen.walk_through_indicator_size).toInt()
        val indicatorHeight = resources.getDimension(R.dimen.walk_through_indicator_size).toInt()
        val indicatorMarginStart = resources.getDimension(R.dimen.walk_through_indicator_margin_start).toInt()
        for (i in 0 until WalkThroughType.values().size){
            var view = View(this)
            if (i == 0){
                view.background = getDrawable(R.drawable.indicator_view_active)
                val layoutParams = LinearLayout.LayoutParams(indicatorWidth, indicatorHeight)
                view.layoutParams = layoutParams
            }else {
                view.background = getDrawable(R.drawable.indicator_view_inactive)
                val layoutParams = LinearLayout.LayoutParams(indicatorWidth,indicatorHeight)
                layoutParams.marginStart = indicatorMarginStart
                view.layoutParams = layoutParams
            }
            indicatorArea.addView(view)
            indicatorViewList.add(view)
        }
    }

19 行目で ViewPager にアダプターを渡しています。

また、上記ソースコードではインディケータも作成しています。

ViewPager のページが切り替わったタイミングでインディケータの表示を変更したいので、SimpleOnPageChangeListener を設定しています。

まとめ

ViewPager もそうですが、ListView や RecyclerView などの使い方もよく忘れてしまいます(笑)

何も見ずに実装できるといいんですが、記憶力がアレなので、せめてすぐに調べられるようにこうして備忘録を残していくことも大切かなと感じました。

それに今回初めて GitHub を作成したので、今後もこうした備忘録を書いていこうと思います。