サイトアイコン 【TechGrowUp】

【Androidアプリ開発】タブで画面を切り替えるアプリの実装

タブアプリのアイキャッチ

はじめに

 本記事では、BottomNavigationViewを利用してタブを実装し、タブごとのページをFragmentを利用して実装します。
 ※説明をみながらソースコードを見たいという方がいれば、下記Githubからソースをダウンロードしてください

GitHub - daichi-mizuno-tech/BottomNavigationApp: BottomNavigationViewを利用したタブアプリケーション
BottomNavigationViewを利用したタブアプリケーション. Contribute to daichi-mizuno-tech/BottomNavigationApp development by creating an acc...

タブアプリについて

最終的なゴール

 今回作るものは下記のような動画のアプリケーションになります。シンプルですが、タブを配置し、タブの切替、Webページ、Webページの設定などをFragment間で切り替えられる仕様になっています。

アプリのUI構成

 アプリのUI構成については、画面下部にタブを2つ配置し、それぞれ「ホーム」「設定」というタブを作りたいと思います。
 また、それぞれのタブを押すとFragmentが切り替わり、「ホーム」タブでは本サイトの記事が表示されるWebViewを使い、「設定」画面ではWebviewの設定を変更できるようにします。ホーム画面と設定画面のデータのやりとりはSharedPreferencesというクラスを利用して受け渡しています。

利用するライブラリ(クラス)の説明

Webview

 WebViewとはViewというクラスの拡張で、ウェブページを表示するためのものです。通常みなさんがウェブページを開くときは、SafariやChromeなどのブラウザを利用すると思いますが、自分のアプリなのに毎回ユーザーにChromeなどを開かせると、利便性が悪くなってしまいます。
 そこでWebViewをアプリ内に組み込むことで、自分のアプリ内でウェブページを開くことができます。Chromeのようにブックマーク、ナビゲーション、アドレスバー等はないためシンプルなものにはなりますが、とても使いやすいです。

BottomNavigationView

 BottomNavigationViewとは画面下部にボタンを配置し、ナビゲーションを実装するためのクラスで、ユーザーが画面を切り替えるために利用されています。
 このBottomNavigationViewを利用すると、タブ切り替えの実装やボタンの配置などを自由に簡単にカスタマイズできたり、Fragmentとの相性がいいためコンポーネント単位で実装が用意になります。

SharedPreferences

 SharedPreferencesはアプリ内のデータを追加/保存/修正/削除などをxml形式で簡単に保存できるようにしたクラスです。通常データを保存する際にはAndroidのSQLiteというDBを使うこともありますが、データ量が少ないのであればDBを作るのは高機能すぎるため、大きなデータを扱わない限りは、SharedPreferencesで十分と言えます。
 余談ですが、Androidアプリではデータを保存するためにxml以外にもjsonファイルなどもよく利用されます。

アプリの実装

実装の流れを考える

 アプリを開発するときは、どのような流れで実装を行っていくか考えましょう。今回の場合だとタブを切り替えて各画面を表示し、Webview画面と設定画面の中身が表示されるという流れになりますので、流れとしては下記で行っていきます。

  1. BottomNavigationViewでタブの実装
  2. タブを押した時に表示するFragment(ホームと設定)の実装
  3. ホーム画面のWebviewの実装
  4. 設定画面の設定項目の実装
  5. 設定を保存するためのSharedPreferencesの実装
  6. 細かい部分の調整

 また、ここからは流れに沿ってソースコードを記載しますが、ソースコードを記述する場所や関数名、レイアウトの中身などは細かく説明しませんので、少しずつ自分で調整してみてください。

1.BottomNavigationViewでタブの実装

 まずBottomNavigationViewを利用するためにタブを作成するための/resフォルダの下に、/menu/my_nav_item.xmlを作成します。これをBottomNavigationViewにつけることで、タブが作成できます。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/action_home"
        android:title="ホーム"
        android:icon="@drawable/ic_baseline_home_24" />
    <item android:id="@+id/action_setting"
        android:title="設定"
        android:icon="@drawable/ic_baseline_settings_24" />
</menu>

 次にmain_activity.xmlのレイアウトにBottomNavigationViewを下位置に配置します。そして、MainActivity.ktにBottomNavigationView.OnNavigatiojnItemSelectedListenerを継承させ、タブが押されたときのコールバック関数を定義します。
 続いて、レイアウトに配置したBottomNavigationViewをfindViewByIdで参照し、onNavigationItemSelectedを加えます。最後にonNavigationItemSelectedのコールバック関数内で各タブ(アイテム)が選択された時にFragmentを参照するような処理にします。参照するときのレイアウトはFrameLayoutを使ってcontainerというidを使っています。

class MainActivity : AppCompatActivity() ,BottomNavigationView.OnNavigationItemSelectedListener{


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

		initializeResource()
	}

	fun initializeResource(){
//		BottomNavigationViewを設定
		val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation_view)
		bottomNavigationView.setOnNavigationItemSelectedListener(this)
		bottomNavigationView.itemIconSize = 70
		bottomNavigationView.scaleX = 1.2f
		bottomNavigationView.scaleY = 1.2f

	}

	override fun onNavigationItemSelected(item: MenuItem): Boolean {
		Log.d(TAG,"Selected item: " + item)

		when(item.itemId){
//			ホームボタンが押された時
			R.id.action_home -> {
				supportFragmentManager.beginTransaction()
						.replace(R.id.container,HomeFragment())
						.setReorderingAllowed(true)
						.commit()
			}
//			設定ボタンが押された時
			R.id.action_setting -> {
				supportFragmentManager.beginTransaction()
						.replace(R.id.container,SettingFragment())
						.setReorderingAllowed(true)
						.commit()
			}

		}

		return true
	}

	companion object{
		const val TAG : String = "MainActivity"
	}
}
main_activity.xml

2.タブを押した時に表示するFragment(ホームと設定)の作成

 タブの設定が終わったので、次にタブを押した時にcontainerのidに表示するFragmentを作成します。作成方法はAndroid Studioのメニューから右クリックをし、[New]->[Fragment]->[Fragment(Blank)]で作成することができます。
 今回は「HomeFragment」と「SettingFragment」という名前のFragmentを作りました。

3.ホーム画面のWebviewの実装

 続いて、HomeFragmentにWebViewを実装します。Fragment作成時に、fragment_home.xmlというレイアウトファイルができていると思いますので、そちらにWebViewを全体に配置します。

fragment_home.xml

4.設定画面の設定項目の実装

 続いて、SettingFragment作成時にも、fragment_setting.xmlというレイアウトファイルができていると思いますので、こちらに「Webviewで読み込むURL」,「キャッシュ方法」、「ズーム倍率」をユーザーが入力するためのビューと、「保存」ボタンを配置します。
 ※配置方法がよくわからない場合は、とりあえずUIが崩れていても問題ないので配置してみてください。

fragment_setting.xml

5.設定を保存するためのSharedPreferencesの実装

 続いて、SettingFragmentで修正や保存したデータをHomeFragmentでも利用したいので、MySharedPrefsというクラスを作成し、SharedPreferencesを利用します。
 ※クラスの作成方法は、プロジェクト内のパッケージ名で右クリックし、[New]->[Kotlin Class/File]で作成できます。

 MySharedPrefesの中身としては、url、キャッシュ方法、ズーム倍率を保存/取得したいので、下記のように記述します。

class MySharedPrefs (context: Context){
	private val preferences = context.getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
	private val urlKey = "url"
	private val cacheKey = "cache"
	private val zoomKey = "zoom"

	fun getUrl() : String?{
		return preferences.getString(urlKey,"https://techgrowup.net/")
	}

	fun setUrl(value : String){
		val editor = preferences.edit()
		editor.putString(urlKey,value)
		editor.apply()
	}

	fun getCache() :  Int{
		return preferences.getInt(cacheKey, CACHE_DEFAULT)
	}

	fun setCache(value: Int){
		val editor = preferences.edit()
		editor.putInt(cacheKey,value)
		editor.apply()
	}

	fun getZoom() :  Int{
		return preferences.getInt(zoomKey, ZOOM_DEFAULT)
	}

	fun setZoom(value: Int){
		val editor = preferences.edit()
		editor.putInt(zoomKey,value)
		editor.apply()
	}

	companion object{
		const val CACHE_DEFAULT= 1
		const val NO_CACHE = 2
		const val CACHE_ONLY = 3

		const val ZOOM_DEFAULT = 100
	}
}

6.細かい部分の調整

 ここまでできたら、大枠がほとんど完成しましたので、ActivityとFragment同士を繋げます。初めに、SettingFragmentでMySharedPrefsにデータを保存する部分を作成します。下記がSettingFragment.ktの全体像です。

class SettingFragment : Fragment() {
    lateinit var  mySharedPrefs : MySharedPrefs

    lateinit var saveButton: Button
    lateinit var urlText : TextView
    lateinit var cacheRadioGroup : RadioGroup
    lateinit var zoomText: TextView


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_setting, container, false)
        initializeResource(view)

        return view
    }

    fun initializeResource(view : View){
//        SharedPreferenceのインスタンス生成
        mySharedPrefs = MySharedPrefs(context!!)

//        保存ボタンの初期設定
        saveButton = view.findViewById<Button>(R.id.save_button)
        saveButton.setOnClickListener {
            saveData(saveButton)
        }

//        埋め込みURLの初期設定
        urlText = view.findViewById<TextView>(R.id.url_plaintext)
        urlText.text = mySharedPrefs.getUrl()

//        キャッシュタイプラジオボタンの初期設定
        cacheRadioGroup = view.findViewById<RadioGroup>(R.id.cache_radiogroup)
        when (mySharedPrefs.getCache()){
            MySharedPrefs.CACHE_DEFAULT -> cacheRadioGroup.check(R.id.cache_default_radiobutton)
            MySharedPrefs.NO_CACHE -> cacheRadioGroup.check(R.id.nocache_radiobutton)
            MySharedPrefs.CACHE_ONLY -> cacheRadioGroup.check(R.id.cacheonly_radiobutton)
        }

//        ズーム倍率の初期設定
        zoomText = view.findViewById<TextView>(R.id.textzoom_plaintext)
        zoomText.text = mySharedPrefs.getZoom().toString()
    }


    fun saveData(view : View){
//        データの取得
        Log.d(TAG,"save datas")
        val url = urlText.text.toString()
        val checkedRadioButton = cacheRadioGroup.checkedRadioButtonId
        var cacheType = MySharedPrefs.CACHE_DEFAULT
        when(checkedRadioButton){
            R.id.cache_default_radiobutton -> cacheType = MySharedPrefs.CACHE_DEFAULT
            R.id.nocache_radiobutton -> cacheType = MySharedPrefs.NO_CACHE
            R.id.cacheonly_radiobutton -> cacheType = MySharedPrefs.CACHE_ONLY
        }
        val zoom = Integer.parseInt(zoomText.text.toString())

//        データの保存
        mySharedPrefs.setUrl(url)
        mySharedPrefs.setCache(cacheType)
        mySharedPrefs.setZoom(zoom)

        Toast.makeText(context,"データが更新されました。",Toast.LENGTH_LONG).show()
    }

    companion object {
       const val TAG : String = "SettingFragment"
    }
}

 これで設定画面でデータを保存することができるようになりましたので、続いてはHomeFragmentでMySharedPrefsから受け取る処理を作りたいと思います。以下がHomeFragmentの全コードです。

class HomeFragment : Fragment() {
    lateinit var  mySharedPrefs : MySharedPrefs

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_home, container, false)
        initializeResource(view)

        return view
    }

    fun initializeResource(view : View){
//        SharedPreferenceのインスタンス生成
        mySharedPrefs = MySharedPrefs(context!!)

//        データの取得
        val url = mySharedPrefs.getUrl()
        val cacheType = mySharedPrefs.getCache()
        val zoom = mySharedPrefs.getZoom()

//        WebViewの設定
        val webView = view.findViewById<WebView>(R.id.webview)
        webView.webViewClient = WebViewClient()
        webView.settings.javaScriptEnabled = true

        when(cacheType){
            MySharedPrefs.CACHE_DEFAULT -> webView.settings.cacheMode = WebSettings.LOAD_DEFAULT
            MySharedPrefs.NO_CACHE -> webView.settings.cacheMode = WebSettings.LOAD_NO_CACHE
            MySharedPrefs.CACHE_ONLY -> webView.settings.cacheMode = WebSettings.LOAD_CACHE_ONLY
        }

        webView.settings.textZoom = zoom
        webView.loadUrl(url!!)
    }

    companion object {
        const val TAG : String = "HomeFragment"
    }
}

 これで設定画面でデータを変更し、タブ切り替えでホーム画面でウェブページが変更されることが確認できると思います。実装の流れとしては以上になります。最後にできたアプリを確認してみましょう。

まとめ

 今回はタブを利用して画面を切り替えるアプリの実装をしてみました。BottomNavigationViewやWebView、SharedPreferencese、Fragmentなどは今後もよく利用するものですので、ぜひ覚えておきましょう。
 プロジェクト一式は下記に配置してありますので、参考にしてください。

GitHub - daichi-mizuno-tech/BottomNavigationApp: BottomNavigationViewを利用したタブアプリケーション
BottomNavigationViewを利用したタブアプリケーション. Contribute to daichi-mizuno-tech/BottomNavigationApp development by creating an acc...

モバイルバージョンを終了