はじめに
空のActivityに値を渡すことが出来るまでを書いていきます。
ActivityへはGson形式で受渡を行い。
さらにメインのアクティビティとサブのアクティビティにはFragmentを使って同じ画面を表示します。
準備
strings.xmlを編集
strings.xmlを下記の様に編集します。
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<string name="app_name">QiitaActivityFragmentSample</string>
<string name="btEdit">編集</string>
<string name="tvStr">String型</string>
<string name="tvInt">Int型</string>
<string name="tvDouble">Double型</string>
</resources>
Gsonを扱えるようにする
左のプロジェクトのGradleツリーからbuild.gradle(app)を開きます。
dependencies {
省略
implementation 'com.google.code.gson:gson:2.8.6'
}
上記の一文を追加してGsonを使えるようにします。
空のFragmentを作成する
左のプロジェクトで右クリック→新規→フラグメント→空のフラグメントを指定します。
Fragmentの名称を指定するダイアログが表示されますが初期値のBlankFragmentで進めます。
Fragmentをデザインする。
FragmentにString型、Int型、Double型を編集出来るレイアウトを作成しました。
<?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=".BlankFragment">
<LinearLayout
android:id="@+id/llv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
android:text="@string/tvStr" />
<EditText
android:id="@+id/etString"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView2"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="right"
android:text="@string/tvInt" />
<EditText
android:id="@+id/etInt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="number" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/textView3"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:gravity="right"
android:layout_weight="1"
android:text="@string/tvDouble" />
<EditText
android:id="@+id/etDouble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="numberDecimal" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
MainActivityにFragmentを配置する
レイアウトデザインを行います。
MainActivityにはボタン1つとFragmentを1つ配置する予定なので**LinearLayout(vertical)**を配置してボタン1つとFragmentを1つ配置します。
MainActivityにてパレットのcontainersのFragmentを配置するとどのFragmentを配置するのか聞いてきますのでBlankFragmentを指定します。
<?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">
<LinearLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onButtonClick"
android:text="@string/btEdit" />
<fragment
android:id="@+id/fragment"
android:name="com.example.qiitaactivityfragmentsample.BlankFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Fragmentの配置が終わったら問題無いか表示を確認しましょう。
空のActivityを作成する
左のプロジェクトで右クリック→新規→アクティビティ→空のアクティビティを指定します。
Activityの名称を指定するダイアログが表示されますので名称をSubActivityと設定して完了をクリックします。
空のActivityにFragmentを配置する
SubActivityにてパレットのcontainersのFragmentを配置するとどのFragmentを配置するのか聞いてきますのでBlankFragmentを指定します。
SubActivityにはFragmentを1つ配置して終了です。
<?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=".SubActivity">
<fragment
android:id="@+id/fragment2"
android:name="com.example.qiitaactivityfragmentsample.BlankFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
追加したActivityに戻るボタンを追加
class SubActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sub)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
SubActivity.klには戻るボタンを表示させておきます。
supportActionBar?.setDisplayHomeAsUpEnabled(true)
こう書いておくとSubActibity表示の時に戻るボタンが表示されます。
追加したActivityを表示する
MainActivity.ktにボタンクリックイベントを作ります。
fun onButtonClick(view : View){
val intent = Intent(applicationContext,SubActivity::class.java)
startActivityForResult(intent,1)
}
このようにすると追加したイベント内でActivityを表示することができます。
実行してボタンを押すとMainActivityからSubActivityへ表示が変わり、SubActivity内の「←」戻るボタンでMainActivityに戻れば成功です。
Fragmentから値を取得する
FragmentはMainActivityとSubActivityから呼び出されています。以前の失敗編ではこの2つが区別出来ずに自滅しました。
今回はMainActivityとSubActivityに配置したFragmentのidがそれぞれMainがFragment SubがFragment2になっていることをデザインしたXMLファイルで確認しましょう。この差で区別出来るようです。
デバッグ用に値を入れておく
いちいち値を入力するのはデバッグしづらいので生成時に値を入れてしまいます。
class MainActivity : AppCompatActivity() {
private val data = gsonFile()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val etString = findViewById<EditText>(R.id.etString)
val etInt = findViewById<EditText>(R.id.etInt)
val etDouble = findViewById<EditText>(R.id.etDouble)
etString.setText("test")
etInt.setText("1234")
etDouble.setText("987.65")
}
Fragmentを参照する
デザイン時に作られたFragmentは
fun onButtonClick(view : View){
val flg = supportFragmentManager.findFragmentById(R.id.fragment) as BlankFragment
Log.d("Sample","flg = $flg")
}
このようにして実行するとfragmentが取得できていることがわかります。
※このやり方が正しいのかどうかわかりません。
Fragmentと値をやりとりする
Fragmentのプログラムに色々追加して簡単にやりとりできるようにします。
メソッド名 | 内容 |
---|---|
toGsonString | Fragmentの編集内容をGson形式の文字列で返します |
fromGsonString | 渡されたGson形式の文字列に従いFragmentの表示に反映します |
準備
BlankFragment内で使用するクラスを宣言します。
class BlankFragment : Fragment() {
private val data = gsonFile()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
Fragmentの内容からGson文字列の変換
fun toGsonString() : String{
val dStr : EditText? = activity?.findViewById<EditText>(R.id.etString)
val dInt : EditText? = activity?.findViewById<EditText>(R.id.etInt)
val dDouble : EditText? = activity?.findViewById<EditText>(R.id.etDouble)
data.dStr = dStr?.text.toString()
data.dInt = dInt?.text.toString().toInt()
data.dDouble = dDouble?.text.toString().toDouble()
return data.gsonString()
}
Fragment内でレイアウト上のidを取得するためにはこれまでのfindViewByIdの前にactivity?としてactivity経由の参照に変わります。
EditTextのidを参照してtext部分を読み取り、それをGson形式の文字列にして返します。※gsonString()は後で完成したものを参考にして下さい。
Gson文字列からFragmentの内容に変換
fun fromGsonString(str : String){
data.fromData(str)
val dStr : EditText? = activity?.findViewById<EditText>(R.id.etString)
val dInt : EditText? = activity?.findViewById<EditText>(R.id.etInt)
val dDouble : EditText? = activity?.findViewById<EditText>(R.id.etDouble)
dStr?.setText(data.dStr.toString())
dInt?.setText(data.dInt.toString())
dDouble?.setText(data.dDouble.toString())
}
完成したBlankFragment.kt
class BlankFragment : Fragment() {
private val data = gsonFile()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false)
}
fun toGsonString() : String{
Log.d("Sample","toGsonString = $activity")
val dStr : EditText? = activity?.findViewById<EditText>(R.id.etString)
val dInt : EditText? = activity?.findViewById<EditText>(R.id.etInt)
val dDouble : EditText? = activity?.findViewById<EditText>(R.id.etDouble)
data.dStr = dStr?.text.toString()
data.dInt = dInt?.text.toString().toInt()
data.dDouble = dDouble?.text.toString().toDouble()
return data.gsonString()
}
fun fromGsonString(str : String){
data.fromData(str)
val dStr : EditText? = activity?.findViewById<EditText>(R.id.etString)
val dInt : EditText? = activity?.findViewById<EditText>(R.id.etInt)
val dDouble : EditText? = activity?.findViewById<EditText>(R.id.etDouble)
dStr?.setText(data.dStr.toString())
dInt?.setText(data.dInt.toString())
dDouble?.setText(data.dDouble.toString())
}
}
open class gsonData(){
var dStr : String = ""
var dInt : Int = 0
var dDouble : Double = 0.0
var backColor : Int = android.R.color.background_light
fun assign(a : gsonData){
dStr = a.dStr
dInt = a.dInt
dDouble = a.dDouble
backColor = a.backColor
}
fun gsonString() : String{
return Gson().toJson(this)
}
}
class gsonFile() : gsonData(){
fun fromData(str : String){
if (str == "") return
val data = Gson().fromJson<gsonData>(str, gsonData::class.java) as gsonData
super.assign(data)
}
fun fileSave(filename : String,str : String){
File(SingletonContext.applicationContext().filesDir, filename).writer().use {
it.write(str)
}
}
fun fileLoad(filename : String) : String?{
val readFile = File(SingletonContext.applicationContext().filesDir, filename)
if(!readFile.exists()){
Log.d("debug","No file exists")
return null
}
else{
return readFile.bufferedReader().use(BufferedReader::readText)
}
}
}
private class SingletonContext : Application() {
init { instance = this }
companion object {
private var instance: SingletonContext? = null
fun applicationContext() : Context {return instance!!.applicationContext}
}
}
FragmentをGson文字列にしてActivityに渡す
MainActivity.ktのボタンクリックにSubActivityへ渡す処理を追加します。
fun onButtonClick(view : View){
val flg = supportFragmentManager.findFragmentById(R.id.fragment) as BlankFragment
val str = flg.toGsonString()
val intent = Intent(applicationContext,SubActivity::class.java)
intent.putExtra("data",str)
startActivityForResult(intent,1)
}
Gson文字列にしたものを**intent.putExtra("data",str)**としてキー「data」の文字列として渡します。
SubActivityから値を返す
SubActivityから値を取得するためにstartActivityではなくstartActivityResultを使っています。これを使って呼び出さないとSubActivity側でいくら頑張ってもMainActivityには値が返ってきません。
SubActivityで値を受け取る
MainActivityから値を渡されたのでSubActivityで値を受け取ります。
SubActivityは自動的に色々作られますが、この例で使用するのはほんのわずかです。
class SubActivity : AppCompatActivity() {
private val data = gsonFile()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sub)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
var str = intent.getStringExtra("data")
val flg = supportFragmentManager.findFragmentById(R.id.fragment2) as BlankFragment
flg.fromGsonString(str)
}
}
たった三行追加しただけで済みました。
var str = intent.getStringExtra("data")
MainActivityから渡されたデータを受け取ります
val flg = supportFragmentManager.findFragmentById(R.id.fragment2) as BlankFragment
SubActivityが表示しているFragmentを参照します
flg.fromGsonString(str)
Fragmentに値を反映させます
ここまで書けたら実行してMainActivityからSubActivityに値が渡されているか確認してみましょう
SubActivityからMainActivityへ値を渡す
SubActivityの「戻る」ボタンをクリックしたときに発生するイベントを受け取ります。
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
val flg = supportFragmentManager.findFragmentById(R.id.fragment2) as BlankFragment
val result: Intent = Intent(applicationContext, SubActivity::class.java)
val str = flg.toGsonString()
result.putExtra("data", str)
setResult(Activity.RESULT_OK, result)
finish()
}
return super.onOptionsItemSelected(item)
}
順番に説明していきます。
if (item.itemId == android.R.id.home) {
押されたのが「戻る」ボタンかを判断しています。
val flg = supportFragmentManager.findFragmentById(R.id.fragment2) as BlankFragment
SubActivityのFragment2を参照します。
val result: Intent = Intent(applicationContext, SubActivity::class.java)
SubActivityから値を返すIntent型を定義します。
result.putExtra("data", str)
Intent型に返すべき値を渡します。
setResult(Activity.RESULT_OK, result)
MainActivityに対してSubActivityはどのような状態で終了したのかを設定します。
finish()
SubActivityを終了します。
MainActivityにてSubActivityの値を受け取る
Activity.ktにてSubActivityの値を受け取ります。
SubActivityから戻るとonActivityResultイベントが発生しますのでこれを継承します。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode === Activity.RESULT_OK && requestCode === 1) {
val flg = supportFragmentManager.findFragmentById(R.id.fragment) as BlankFragment
val str = data?.extras?.getString("data").toString()
flg.fromGsonString(str)
}
}
それぞれ説明します。
if (resultCode === Activity.RESULT_OK && requestCode === 1) {
resultCodeはSubActivityにて**setResult(Activity.RESULT_OK, result)**と返り値を指定された値が返りますのでこれと比較しています。
さらにMainActivityからSubActivityを呼び出す時に** startActivityForResult(intent,1)**とrequestCodeを指定していてこれが返ってきますので比較しています。
val flg = supportFragmentManager.findFragmentById(R.id.fragment) as BlankFragment
Fragmentを参照します。
val str = data?.extras?.getString("data").toString()
onActivityRsultの引数としてIntent型の dataが渡されますのでここからSubActivityで設定した文字列を取得します。
flg.fromGsonString(str)
FragmentにSubActivityから返ってきたGson形式の文字列を渡して反映させます。
最後に
Fragmentにて編集すべきデータクラスと、さらにFragmentのレイアウトに設定しているidに従って値の取得と反映処理を作っておくと、使う側が非常に楽になりました。
またActivity間を移動する場合でもGson型の文字列として渡すことが出来て便利です。
これで1つのレイアウトを複数から参照も出来そうです。