KotlinでFragmentを使う
KotlinでFragmentを使ってみます。失敗っていうのは結果だけで途中の段階は重要な部分もあります。
前準備
Gsonを使いますのでbuild.gradle(app)に
dependencies {
省略
implementation 'com.google.code.gson:gson:2.8.6'
}
gsonを追加します。 2.8.6の部分はバージョン番号です。
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<string name="app_name">FragmentSample</string>
<string name="btSave">保存</string>
<string name="btLoad">読込</string>
<string name="tvStr">String型</string>
<string name="tvInt">Int型</string>
<string name="tvDouble">Double型</string>
</resources>
空のFragmmentを作ります。
プロジェクトツリーから右クリック→新規→フラグメント→フラグメント(空白)と選んでいきます。
フラグメントの名称を決める画面ではデフォルトのBlankFragmentとしました。
作ったフラグメントのデザインは
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BlankFragment">
<!-- TODO: Update blank fragment layout -->
<LinearLayout
android:id="@+id/llv"
android:layout_width="204dp"
android:layout_height="301dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/llh"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:orientation="horizontal">
<TextView
android:id="@+id/tvStr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tvStr" />
<EditText
android:id="@+id/etStr"
android:layout_width="144dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:orientation="horizontal">
<TextView
android:id="@+id/tvInt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tvInt" />
<EditText
android:id="@+id/etInt"
android:layout_width="144dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="number" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:orientation="horizontal">
<TextView
android:id="@+id/tvDouble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tvDouble" />
<EditText
android:id="@+id/etDouble"
android:layout_width="144dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="numberDecimal" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
としました。
今度はMainActivityの方を作っていきます。
<?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:id="@+id/clMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/clTool"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="buttonClick"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/llView"
android:layout_width="wrap_content"
android:layout_height="378dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.231"
app:layout_constraintStart_toStartOf="@+id/clTool"
app:layout_constraintTop_toBottomOf="@+id/clTool">
<fragment
android:id="@+id/fragmentView"
android:name="com.example.qiitafragmentsample.BlankFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<LinearLayout
android:id="@+id/llEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
先ほど作ったFragmentをすでに設計段階で置くことが出来ます。
設計時に置いたFragmentに初期値を
そしてボタンを押したらFragmentを作るようにしてみます。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dStr = findViewById<EditText>(R.id.etStr)
val dInt = findViewById<EditText>(R.id.etInt)
val dDouble = findViewById<EditText>(R.id.etDouble)
dStr.setText("Test")
dInt.setText("1234")
dDouble.setText("98.76")
}
fun buttonClick(view : View){
val dStr = findViewById<EditText>(R.id.etStr)
val dInt = findViewById<EditText>(R.id.etInt)
val dDouble = findViewById<EditText>(R.id.etDouble)
val bundle = Bundle()
val data = gsonFile()
data.dStr = dStr.text.toString()
data.dInt = dInt.text.toString().toInt()
data.dDouble = dDouble.text.toString().toDouble()
data.backColor = Color.rgb(0xff, 0, 55)
bundle.putString("gson", data.gsonString())
val editFragment = BlankFragment()
editFragment.setArguments(bundle)
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.llEdit, editFragment)
fragmentTransaction.commit()
}
}
Fragmentに値を渡す
val bundle = Bundle()
val data = gsonFile()
data.dStr = dStr.text.toString()
data.dInt = dInt.text.toString().toInt()
data.dDouble = dDouble.text.toString().toDouble()
data.backColor = Color.rgb(0xff, 0, 55)
bundle.putString("gson", data.gsonString())
Fragmentには直接値を渡せないのでBundle()という仕組みを使います。
BundleにputString(キー,値) などして値を入れておくと受け取った側のFragmentでgetString(キー)とすれば値を受け取ることが出来ます。
色々な値があるFragmentにいちいち get putしてられないので独自にGsonを使って処理を簡単にしています。
今回作ったFragmentには 文字列型のdStr、数値型の dInt、浮動小数点型の dDouble あと確認のためbackColor という数値(色を管理させている)も用意しています。
fragmentTransaction.replace(R.id.llEdit, editFragment)
LinearLayoutに付けたID llEdit を今回作ったFragmentに置き換えています。他にaddとかありますが使わないと思います。
今度はFragment側で受け取ってみます。
class BlankFragment : Fragment() {
private val data = gsonFile()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val str : String = arguments?.getString("gson") ?:return
Log.d("Sample","Fragment str=$str")
data.fromData(str)
val dStr : EditText? = activity?.findViewById<EditText>(R.id.etStr)
val dInt : EditText? = activity?.findViewById<EditText>(R.id.etInt)
val dDouble : EditText? = activity?.findViewById<EditText>(R.id.etDouble)
Log.d("Sample","dStr = ${data.dStr} dInt = ${data.dInt} dDouble = ${data.dDouble}")
dStr?.setText(data.dStr)
dInt?.setText(data.dInt.toString())
dDouble?.setText(data.dDouble.toString())
dStr?.setTextColor(data.backColor)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_blank, container, false)
}
}
Fragmentを作ったときに出来た宣言にいくつか追加します。
Gsonを管理するgsonFileクラスは後で説明します。
Fragment側で値を受け取る
val str : String = arguments?.getString("gson") ?:return
Fragmentには直接値を渡すことが出来ず arguments経由で渡されるのでそこからgson形式の文字列を取り出します。設計時には取り出すことが出来ずstrがnullになるのでその場合は処理をしないようにしています。
null許容型にしていても if(str == null)とかで判断出来ないので注意です。
クラスにデータを管理させる
独自のgsonDataクラスを宣言してデータを管理しています。
管理したいデータが他にもあれば宣言を追加して、初期化して、代入命令 assignに代入文を追加します。
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)
}
}
先ほど宣言したデータを管理するクラスにデータ→Gson、Gson→データなど命令を追加します。
自分自身にデータを渡せないのでデータ部分の宣言と処理部分で分けています。
gsonFileクラス内にgsonDataを定義しても良いのですがそれだと gsonFild.data.dIntと長くなるので継承にしました。他に良い方法あればご教授願います。
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)
}
}
}
Contextを使い回す。
ファイルの読み書きにContextがいるのですが単なるデータクラスにあるわけもなく、このクラスから参照しています。
private class SingletonContext : Application() {
init { instance = this }
companion object {
private var instance: SingletonContext? = null
fun applicationContext() : Context {return instance!!.applicationContext}
}
}
結果
最初に表示されている設計時Fragmentに入力した値はボタンを押すと新しいFragmentが作られそちらに渡されます・・・が、そうはなりません。
値は渡るもののval dStr : EditText? = activity?.findViewById(R.id.etStr)
で得られる R.id.etStr は設計時のとボタンクリックで作ったものが同じなのでなんと設計時の方が参照されます。
Fragmentの目的は
1.表示させる項目を動的に変化させる
2.画面サイズにより表示、非表示を切り替える
であり、同じFragmentを同時に画面に表示させる物では無いと思われます。
最後に
練習でFragmentを使って見ましたが、こうなりました。
あと重要なことですが、作ったFragmentへ値は渡せましたが値を返すことは出来ません。