とっとこメモ

困った時のエンジニアメモ。Unity, Kotlin, Flutter などなど

【Kotlin】Bottom Navigation + Tab Layout

元ネタ

medium.com

Gradle の修正

ナビゲーションを利用するためにbuild.gradle(:app)ファイルを修正する

dependencies {
   ・・・・
    implementation "com.google.android.material:material:1.1.0-alpha06"
    def nav_version = '2.0.0'
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

ボトムナビゲーションのメニュー用レイアウトファイルを作成

res/menu/navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/navigation_call"
        android:icon="@android:drawable/ic_menu_call"
        android:title="Call"/>

    <item android:id="@+id/navigation_search"
        android:icon="@android:drawable/ic_menu_search"
        android:title="Search"/>
</menu>

activity_main.xmlファイルの修正

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/container"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
    </FrameLayout>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="?android:attr/windowBackground"
        app:menu="@menu/navigation"/>

</LinearLayout>

4つのフラグメントファイルを作成する

CallFragment.kt

ボトムナビゲーションの「Call」に対応するフラグメント

SearchFragment.kt

ボトムナビゲーションの「Search」に対応するフラグメント

VoiceCallFragment.kt

「Call」画面内の「VOICE CALL」タブに対応するフラグメント

VideoCallFragment.kt

「Call」画面内の「VIDEO CALL」タブに対応するフラグメント

それぞれのfragmentに対応するレイアウトファイルも作成しておく

  • fragment_call.xml
  • fragment_search.xml
  • fragment_voice_call.xml
  • fragment_video_call.xml

ここでは fragment_voice_call.xml / fragment_video_call.xml はシンプルなレイアウトファイルとする

fragment_voide_call.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".VideoCallFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

</FrameLayout>

fragment_voice_call.xml も同様のレイアウトファイルを作成する

MainActivity.ktの修正

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.FrameLayout
import androidx.fragment.app.Fragment
import com.google.android.material.bottomnavigation.BottomNavigationItemView
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private var content : FrameLayout? = null

    private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        when(item.itemId){
            R.id.navigation_call -> {
                val fragment = CallFragment()
                addFragment(fragment)
                return@OnNavigationItemSelectedListener true
            }
            R.id.navigation_search -> {
                val fragment = SearchFragment()
                addFragment(fragment)
                return@OnNavigationItemSelectedListener true
            }
        }
        false
    }

    private fun addFragment(fragment: Fragment) {
        supportFragmentManager
            .beginTransaction()
            .setCustomAnimations(R.anim.nav_default_pop_enter_anim, R.anim.nav_default_exit_anim)
            .replace(R.id.content, fragment, fragment.javaClass.simpleName)
            .commit()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
        val fragment = CallFragment()
        addFragment(fragment)
    }
}

この状態でビルドすると以下のような表示になるはず

f:id:miscellaneous_engine:20200526133742j:plain:w200

タブレイアウト(View Pager)用のAdapterを作成する

MyPageAdapter.kt

class MyPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT){

    override fun getItem(position: Int): Fragment {
        when(position){
            0 -> {
                return VoiceCallFragment()
            }
            else -> {
                return VideoCallFragment()
            }
        }
    }

    override fun getCount(): Int {
        return 2
    }

    override fun getPageTitle(position: Int): CharSequence? {
        return when(position){
            0 -> "Voice Call2"
            else -> {
                "Video Call2"
            }
        }
    }
}

fragment_call.xmlファイルの修正

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

    <androidx.viewpager.widget.ViewPager
            android:id="@+id/viewpager_main"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
        tools:ignore="MissingConstraints">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs_main"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabMode="fixed"/>

    </androidx.viewpager.widget.ViewPager>

</androidx.constraintlayout.widget.ConstraintLayout>

CallFragment.ktの修正

package com.example.android.sample.bnavitab

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout


class CallFragment : Fragment() {
    private lateinit var viewPager: ViewPager
    private lateinit var tabs: TabLayout

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view: View = inflater.inflate(R.layout.fragment_call, container, false)
        viewPager = view.findViewById(R.id.viewpager_main)
        tabs = view.findViewById(R.id.tabs_main)

        val fragmentAdapter = MyPagerAdapter(childFragmentManager)
        viewPager.adapter = fragmentAdapter
        tabs.setupWithViewPager(viewPager)

        return view
    }
}

この状態でビルドすると以下のような表示になるはず

f:id:miscellaneous_engine:20200526133712j:plain:w200