はじめに
この記事はロボP Advent Calendar 2018の15日目の記事です.
Koinとは
AndroidのKotlin向けDIライブラリらしいです。
すごい簡単に使えたので僕は好きです。
詳しくはこちら
やりたいこと
一般的な同じLayoutが並んでいるAdapterではなく、複数のLayoutが並んでるAdapterを作ります。
どうせならその要素一つ一つをDIで読み込んだらイケてそうだねって気がしたのでKoinの登場です。
実装する
土台づくり
準備
まず必要なライブラリをsyncします。
今回はKoin
とList表示にRecyclerView
を使いました。
//RecyclerView
implementation 'com.android.support:recyclerview-v7:28.0.0'
//koin
implementation 'org.koin:koin-android:0.9.3'
Layout
ActivityのLayout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</android.support.constraint.ConstraintLayout>
</layout>
Listの要素を保持する空のLayout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/container"
android:orientation="vertical">
</LinearLayout>
1つ目の項目のlayout
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="normal view !"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
Koin
まずModule化するクラスのInterfaceを定義します。
interface ItemContract {
val isVisible: Boolean
fun getView(context: Context): View
}
それを使って1つ目の項目のクラスを作成します。
項目内で処理をしたい場合はここで設定します。
class NormalItem(override val isVisible: Boolean) : ItemContract {
override fun getView(context: Context): View {
return LayoutInflater.from(context).inflate(R.layout.layout_normal, null)
}
}
DIをアプリ起動時に行うので、Applicationを継承したカスタムApplicationクラスを作ります。
class App : Application() {
override fun onCreate() {
super.onCreate()
startKoin(
this, listOf(
this.itemModule
)
)
}
private val itemModule: Module = applicationContext {
factory("normal") { NormalItem(isVisible = true) as ItemContract }
}
}
作成したApp.kt
をManifestファイルに設定します。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.s04341.ditest">
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Adapter
複数項目表示するAdapterを作成します。
空のLinearLayoutのみをItemのLayoutとして持つAdapterに、
先程作成したInterfaceを実装するItemから読み込ませるViewを変えることで複数項目を出しています。
なので保持するItemsはList<ItemContract>
になっています。
class MultiAdapter(
val context: Context,
val items: ArrayList<ItemContract>
) : RecyclerView.Adapter<MultiAdapter.ViewHolder>() {
val layoutInflater = LayoutInflater.from(context)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(layoutInflater.inflate(R.layout.layout_container, viewGroup, false))
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(viewHolder: ViewHolder, pos: Int) {
viewHolder.container.removeAllViews()
viewHolder.container.addView(items[pos].getView(context).rootView)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val container: LinearLayout by lazy {
itemView.findViewById(R.id.container) as LinearLayout
}
}
}
Activity
ActivityでKoinでDIされた項目をInject()
して受け取り、それをList化してAdapterにわたすことで反映しています。
class MainActivity : AppCompatActivity() {
private val normalItem: ItemContract by inject("normal")
private val recyclerView: RecyclerView by lazy {
findViewById<RecyclerView>(R.id.recycler_view)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
val items = ArrayList<ItemContract>()
items.add(normalItem)
recyclerView.setHasFixedSize(true)
val multiAdapter = MultiAdapter(this, items)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = multiAdapter
}
}
実行!
NormalItem.kt
で設定したLayoutが表示されたかと思います。
二つ目の項目を追加する
項目の作成
違うViewを表示させてみたいですよね。
土台は出来てるので、あとは2つ目の項目のItemを作成するだけです。
class SpecialItem(override val isVisible: Boolean) : ItemContract {
override fun getView(context: Context): View {
return LayoutInflater.from(context).inflate(R.layout.layout_special, null)
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
android:textColor="#ff0000"
android:textStyle="bold"
android:text="special view !"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
実装
これらをApp.kt
とMainActivity.kt
に追加します。
class App : Application() {
override fun onCreate() {
super.onCreate()
startKoin(
this, listOf(
this.itemModule
)
)
}
private val itemModule: Module = applicationContext {
factory("normal") { NormalItem(isVisible = true) as ItemContract }
factory("sp") { SpecialItem(isVisible = true) as ItemContract }
}
}
class MainActivity : AppCompatActivity() {
private val normalItem: ItemContract by inject("normal")
private val spItem: ItemContract by inject("sp")
private val recyclerView: RecyclerView by lazy {
findViewById<RecyclerView>(R.id.recycler_view)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
val items = ArrayList<ItemContract>()
items.add(normalItem)
items.add(spItem)
recyclerView.setHasFixedSize(true)
val multiAdapter = MultiAdapter(this, items)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = multiAdapter
}
}
実行!
余談
例えば
12月のみSpecialLayoutを表示したい
などの場合はApp.kt
で条件を書いて別インスタンスを作成すればいけます。
...
private val itemModule: Module = applicationContext {
factory("normal") { NormalItem(isVisible = true) as ItemContract }
val df = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
val date = Date(System.currentTimeMillis())
if (date.month + 1 == 12) {
factory("sp") { SpecialItem(isVisible = true) as ItemContract }
} else {
factory("sp") { SpecialItem(isVisible = false) as ItemContract }
}
}
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
val items = ArrayList<ItemContract>()
items.add(normalItem)
if (spItem.isVisible) {
items.add(spItem)
}
recyclerView.setHasFixedSize(true)
val multiAdapter = MultiAdapter(this, items)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = multiAdapter
}