Andoroidでシステム設定のような設定画面を作ってみる
Androidのアプリで設定画面を持っているものが有ります。AndroidX Preference Libraryを使って、Andoroid OSのシステム設定のような設定画面をアプリ内に作ってみましょう。
参考URL
本家Android develpers 設定
どんな設定画面を作るか?
とりあえず、こんな設定画面を作ってみましょう。
設定値として色々な型が可能ですが、
- 文字列(何でも入力可)
- 文字列(数値のみ)
- リスト形式
- チェックボックス形式
- スィッチ形式(ON/OFF、YES/NO、True/False)
を作って見ましょう。
「文字列(何でも入力可)」と「文字列(数値のみ)」AndroidX Preference Libraryの型としてはEditTextPreferenceで一緒ですが、入力の時のキーパットが違います。
「文字列(何でも入力可)」は何でも入力できるキーパッドですが、
「文字列(数値のみ)」の場合は数値しか入力できないキーパッドになります。ここで数値しか入らないことを保証しています。
リスト形式の場合は、どれかひとつを選ぶダイアログが表示されます
チェックボックス形式は上の図の通りですね。複数選択可能です。
スィッチ形式はONかOFFかのどちらかが選択可能です。
build.gradleに依存関係の追加
build.gradleに依存性を追加します。
dependencies {
(中略)
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
}
ここで、注意しないといけないのは、androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1を追加しないと、ビルド時に意味不明のエラーになります。
Duplicate class androidx.lifecycle.ViewModelLazy found in modules lifecycle-viewmodel-2.5.1-runtime (androidx.lifecycle:lifecycle-viewmodel:2.5.1) and lifecycle-viewmodel-ktx-2.3.1-runtime (androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1)
Duplicate class androidx.lifecycle.ViewTreeViewModelKt found in modules lifecycle-viewmodel-2.5.1-runtime (androidx.lifecycle:lifecycle-viewmodel:2.5.1) and lifecycle-viewmodel-ktx-2.3.1-runtime (androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1)
Go to the documentation to learn how to Fix dependency resolution errors.
色々ネットを検索して、上記の解決方法を見つけたのですが、正直、なんでこのビルドエラーになって、なんでこれで解消するのか、自分の力量では説明できません。(lifecycle関係なんか使ってないのに・・・androidx.preference:preferenceから芋づる式で入ってしまうんだろうか?)
設定画面のFragmentウィザードで雛形を一気に作る
AndroidStudioに設定画面のウィザートが用意されていてある程度の雛形がすぐ作成できます。
javaのフォルダを右クリック → 新規 → Fragment → Setting Fragment
設定画面のFragment作成のダイアログが表示されます。ここはデフォルトのままFragment名はSettingFragmentのままでいきます。
完了をクリックすると、こんな感じで雛形ができあがっています。
- SettingFragment(フラグメント)
- arrays.xml(リスト形式で使う)
- root_preferences.xml(設定画面のデザインレイアウト)
がウィザードで作成されれているのがわかります。
root_preferences.xmlを開くとデザイナのviewが上がってきます。こんな感じで雛形ができています。
左上の「Palette」に設定画面に使用可能なパーツが出ています。ここから、その下の「Component Tree」にドラッグ&ドロップして設定画面にパーツを追加していきます。パーツとして何が使用可能か、一通り見てみましょう。
「Commons」の下
「Preferences」の下
「Groups」の下
設定画面のデザイン
「Component Tree」の構造を見ると
- PreferenceScreen
- PreferenceCategory
- 設定画面のパーツ
- ・・・
- PreferenceCategory
- 設定画面のパーツ
- ・・・
- PreferenceCategory
といった階層構造になっているのがわかると思います。今回、上の設定画面を作るのに必要な設定画面のパーツは
設定画面のパーツ | class |
---|---|
文字列(何でも入力可) | EditTextPreference |
文字列(数値のみ) | EditTextPreference |
リスト形式 | ListPreference |
チェックボックス形式 | CheckBoxPreference |
スィッチ形式 | SwitchPreference |
となります。では実際に雛形を参考にしながら設定画面のパーツを配置していきます。「ComponetTree」はこのようになります。
実は、チェックボックス形式(CheckBoxPreference)は設定値としては1個毎にバラバラになるので、上の例のように3つの中から任意の個数選択可能だと3つ配置することになります。
リスト形式のリストの中身ですが、雛形で作られたarrys.xmlを参照しています。
arrays.xmlはこうなっています。
<resources>
<!-- Reply Preference -->
<string-array name="list_entries">
<item>設定1</item>
<item>設定2</item>
<item>設定3</item>
</string-array>
<string-array name="list_values">
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
</resources>
ListPreferenceのプロパティの「entries」が実際に設定画面で表示されるリストの内容、「entryValues」が選択された時に採用される値です。それがarrays.xmlとリンクしているのがわかるでしょうか。HTMLのSELECTタグみたいな仕組みになっています。
できあがったroot_preferences.xmlはこのようになります。
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/textCatTitle">
<EditTextPreference
app:key="textPreference"
app:title="@string/textPrefTitle"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/numberCatTitle">
<EditTextPreference
android:defaultValue="0"
android:key="numberPreference"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/numberPrefTitle"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/listCatTitle">
<ListPreference
app:entries="@array/list_entries"
app:entryValues="@array/list_values"
app:key="listPreference"
app:title="@string/listPrefTitle"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/checkCatTitle">
<CheckBoxPreference
android:defaultValue="false"
android:key="checkboxPreference1"
android:title="@string/checkPrefTtile1" />
<CheckBoxPreference
android:defaultValue="false"
android:key="checkboxPreference2"
android:title="@string/checkPrefTtile2" />
<CheckBoxPreference
android:defaultValue="false"
android:key="checkboxPreference3"
android:title="@string/checkPrefTtile3" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/switchCatTitle">
<SwitchPreferenceCompat
app:defaultValue="false"
app:key="switchPreference"
app:summaryOff="@string/summaryOff"
app:summaryOn="@string/summaryOn"
app:title="@string/switchPrefTitle" />
</PreferenceCategory>
</PreferenceScreen>
SettingFragment.ktの修正
上でも説明しましたが、「文字列(何でも入力可)」、「文字列(数値のみ) 」は設定パーツ的にはEditTextPreferenceで同じです。ここで、「文字列(数値のみ) 」は数値のみ入力可能なキーパッドが表示されるように細工をします。
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
// 入力を数値のみに限定する
val numberPreference = findPreference<EditTextPreference>("numberPreference")
numberPreference?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER
}
}
onCreatePreferencesメソッドはウィザードの雛形ですでにできあがっているので、そこに追加します。
設定を表示する画面も作ってみる
設定を書き込んだだけじゃ、画面で確認する方法がないので、設定を表示する画面も作ってみます。少々、雑ですが、こんな画面を作ります。
アクションバーにアイコンを2つ配置して、スパナのアイコンをタップするとSettingFragmentの画面へ、Uターンのアイコンをタップするとこの表示画面へ、お互いに行き来できるようにしてみました。
class DisplayFragment : Fragment() {
private lateinit var binding: FragmentDisplayBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentDisplayBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val sharedPreference = activity?.let { PreferenceManager.getDefaultSharedPreferences(it) }
binding.textPref.text = sharedPreference?.getString("textPreference", "") ?: ""
binding.numberPref.text = sharedPreference?.getString("numberPreference", "") ?: ""
binding.listPref.text = sharedPreference?.getString("listPreference", "") ?: ""
binding.checkPref1.text = sharedPreference?.getBoolean("checkboxPreference1", false) .toString()
binding.checkPref2.text = sharedPreference?.getBoolean("checkboxPreference2", false) .toString()
binding.checkPref3.text = sharedPreference?.getBoolean("checkboxPreference3", false) .toString()
binding.switchPref.text = sharedPreference?.getBoolean("switchPreference", false) .toString()
}
}
onViewCreatedメソッドにSharedPreferenceから値を取得して表示する処理を入れています。
MainActivity
MainActivityは最初にDisplayFragmentを表示して、アクションバーのアイコンタップによってどっちのFragmentに遷移するかの処理をいれます。
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater).apply {
setContentView(this.root)
}
supportFragmentManager
.beginTransaction()
.replace(R.id.fragmentContainerView, DisplayFragment())
.commit()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.setting -> {
supportFragmentManager
.beginTransaction()
.replace(R.id.fragmentContainerView, SettingsFragment())
.commit()
}
R.id.disp -> {
supportFragmentManager
.beginTransaction()
.replace(R.id.fragmentContainerView, DisplayFragment())
.commit()
}
else -> {}
}
return true
}
}
入力した設定値はAndroid端末内のSharedPreferenceとしてxmlで保存されますので、アプリを終了しても、端末を再起動しても残っています。AndroidStudioのDeviceFileExploerで見ると
/data/data/{パッケージ名}/shared_prefs
の下にxmlでこんな風に保存されます。
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="listPreference">2</string>
<boolean name="checkboxPreference1" value="false" />
<boolean name="switchPreference" value="true" />
<string name="numberPreference">123</string>
<boolean name="checkboxPreference3" value="true" />
<string name="textPreference">ああああ</string>
<boolean name="checkboxPreference2" value="false" />
</map>
最後に
以上で、一通り説明しました。完成形はgitHubに置きました。