【Androidアプリ開発】タブ表示

2023年2月7日

前回はメロディを流すことをやりました。

【Androidアプリ開発】音を鳴らす。メディア再生
【Androidアプリ開発】音を鳴らす。メディア再生
前回はサービスと通知を行いました。 今回自己学習で作成しているのは、Todoアプリで期限に近づいたらアラームを…
http://nomux2.net/post-2367/

今回は、レイアウトを少し変更したいと思いました。

今、私が作ろうとしているのはTodoアプリです。「未完了」「完了」「全て」で表示を切り替えたいと思いました。そこでタブ表示で分けようとなりました。

それではやっていきます。

タブ表示を実装する

基本的には、レイアウトにタブを追加してアダプタを渡してあげるだけです。

まずは全体像を表します。

タブ表示に使う文字列です。

<resources>
    (省略)

    <string name="main_tab_incomplete">未完了</string>
    <string name="main_tab_complete">完了</string>
    <string name="main_tab_all">全て</string>

    (省略)
</resources>

タブを表示するレイアウトファイルです。

<?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">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabMainLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <com.google.android.material.tabs.TabItem
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/main_tab_incomplete" />

            <com.google.android.material.tabs.TabItem
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/main_tab_complete" />

            <com.google.android.material.tabs.TabItem
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/main_tab_all" />

        </com.google.android.material.tabs.TabLayout>
    </androidx.viewpager.widget.ViewPager>

</androidx.constraintlayout.widget.ConstraintLayout>

タブの子ページのレイアウトです。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lvTodoCompleteList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lvTodoIncompleteList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lvTodoAllList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

タブに表示する子ページのフラグメントを作成します。

package com.example.todomanager

import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ListView
import androidx.fragment.app.Fragment

class TabPageInCompleteFragment(context: Context): Fragment(){

    private var _viewTabItem : View? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        _viewTabItem = inflater.inflate(R.layout.main_tab_item_incomplete, container,false)

        //Viewに対する処理
        return _viewTabItem
    }
}

class TabPageCompleteFragment(context: Context): Fragment(){
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.main_tab_item_complete,container,false)
    }
}

class TabPageAllFragment(context: Context): Fragment(){
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.menu_tab_item_all,container,false)
    }
}

子ページと親ページをつなぐアダプタを作成します。

package com.example.todomanager

import android.content.Context
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter

class TabAdapter(fm: FragmentManager, private val context: Context): FragmentPagerAdapter(fm){

    val _flagmentTodoInComplete = TabPageInCompleteFragment(context)
    val _flagmentTodoComplete = TabPageCompleteFragment(context)
    val _flagmentTodoAll = TabPageAllFragment(context)

    override fun getItem(position: Int): Fragment {
        when(position){
            0 -> { return _flagmentTodoInComplete }
            1 -> {return _flagmentTodoComplete}
            else ->  { return _flagmentTodoAll }
        }
    }

    override fun getPageTitle(position: Int): CharSequence? {
        when(position){
            0 -> { return context.getString(R.string.main_tab_incomplete); }
            1 -> { return context.getString(R.string.main_tab_complete); }
            else ->  { return context.getString(R.string.main_tab_all); }
        }
    }

    override fun getCount(): Int {
        return 3
    }
}

呼び出すActivity(親)で子ページを取得/設定します。

(省略)
class MainActivity : AppCompatActivity(), DialogFragmentResultListener {

    private var _adapterTab : TabAdapter? = null

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

        _adapterTab = TabAdapter(supportFragmentManager,this)
        pager.adapter = _adapterTab
        tabMainLayout.setupWithViewPager(pager)
    }

    (省略)
}

それでは細かく見ていきます。

タブレイアウト

最初にタブレイアウトを利用できるようにします。
メニューから「Project Structure」を選択します。

「Dependencies」を選択し、「+」ボタンで追加します。

「Library Dependency」を選択してください。

Step1.のテキストボックスに「material」と入力しSearchボタンをクリックします。
出てきたリストの中から「com.google.android.material」を選択しバージョンを選択します。

バージョンに特にこだわりはありませんがalpha版はまだ正式版ではないので「1.1.0」にしました。

追加されていることを確認します。

build.gradleに追加されていると思います。

dependencies {
    (省略)
    implementation 'com.google.android.material:material:1.1.0'
}

レイアウトの作成

表示するタブのレイアウトを設定します。

<?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">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabMainLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <com.google.android.material.tabs.TabItem
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/main_tab_incomplete" />

            <com.google.android.material.tabs.TabItem
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/main_tab_complete" />

            <com.google.android.material.tabs.TabItem
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/main_tab_all" />

        </com.google.android.material.tabs.TabLayout>
    </androidx.viewpager.widget.ViewPager>

</androidx.constraintlayout.widget.ConstraintLayout>

ViewPagerはアダプタで使用するViewの管理を行います。
この状態で実行すると以下のようになります。

各タブページのレイアウトを作成

タブページのレイアウトを作成します。ここでは例ですので単純にリストビューを表示するようにします。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lvTodoCompleteList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lvTodoIncompleteList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lvTodoAllList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

タブページのフラグメントを作成。

アダプタよりフラグメントが呼び出されるとonCreateViewが呼び出されるのでこの時にViewをインフレイトします。取得したビューに対して初期表示の処理を行います。

class TabPageInCompleteFragment(context: Context): Fragment(){

    private var _viewTabItem : View? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        _viewTabItem = inflater.inflate(R.layout.main_tab_item_incomplete, container,false)

        //Viewに対する処理

        return _viewTabItem
    }
}

アダプタを作成

作成したフラグメントをアダプタで取得できるようにします。

class TabAdapter(fm: FragmentManager, private val context: Context): FragmentPagerAdapter(fm){

    val _flagmentTodoInComplete = TabPageInCompleteFragment(context)
    val _flagmentTodoComplete = TabPageCompleteFragment(context)
    val _flagmentTodoAll = TabPageAllFragment(context)

    override fun getItem(position: Int): Fragment {
        when(position){
            0 -> { return _flagmentTodoInComplete }
            1 -> {return _flagmentTodoComplete}
            else ->  { return _flagmentTodoAll }
        }
    }

    override fun getPageTitle(position: Int): CharSequence? {
        when(position){
            0 -> { return context.getString(R.string.main_tab_incomplete); }
            1 -> { return context.getString(R.string.main_tab_complete); }
            else ->  { return context.getString(R.string.main_tab_all); }
        }
    }

    override fun getCount(): Int {
        return 3
    }
}

上記の例では、宣言で作成したフラグメントをgetItemで取得します。

またgetPageTitleでタブの名称を取得します。strings.xmlから文字列を取得するにはcontextが必要ですので引数に追加しています。

タブを設定

Activityで呼び出します。pagerにアダプタを渡して、pagerをタブに渡します。

    private var _adapterTab : TabAdapter? = null

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

        _adapterTab = TabAdapter(supportFragmentManager,this)
        pager.adapter = _adapterTab
        tabMainLayout.setupWithViewPager(pager)
    }

まとめ

タブを扱うにはまず使用できるように「com.google.android.material」を登録します。そのあとタブページをレイアウトに記述し、タブで使用するレイアウト、フラグメント、アダプタを作成します。

Visual Studioだとツールボックスで選んでフォームに置くだけで終わりなのですが、結構面倒に感じました。

ここまで読んで頂いてありがとうございます。