LoginSignup
2
2

More than 5 years have passed since last update.

複数項目を表示するAdapterをKoinでDIしながら実装する

Posted at

はじめに

この記事はロボP Advent Calendar 2018の15日目の記事です.

Koinとは

AndroidのKotlin向けDIライブラリらしいです。
すごい簡単に使えたので僕は好きです。
詳しくはこちら

やりたいこと

一般的な同じLayoutが並んでいるAdapterではなく、複数のLayoutが並んでるAdapterを作ります。
どうせならその要素一つ一つをDIで読み込んだらイケてそうだねって気がしたのでKoinの登場です。

実装する

土台づくり

準備

まず必要なライブラリをsyncします。
今回はKoinとList表示にRecyclerViewを使いました。

build.gradle
//RecyclerView
implementation 'com.android.support:recyclerview-v7:28.0.0'
//koin
implementation 'org.koin:koin-android:0.9.3'

Layout

ActivityのLayout

activity_main.xml
<?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

layout_container.xml
<?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

layout_normal.xml
<?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を定義します。

ItemContract.kt

interface ItemContract {
    val isVisible: Boolean
    fun getView(context: Context): View
}

それを使って1つ目の項目のクラスを作成します。
項目内で処理をしたい場合はここで設定します。

NormalItem.kt

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クラスを作ります。

App.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 }
    }
}

作成したApp.ktをManifestファイルに設定します。

AndroidManifest.xml
<?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>になっています。

MultiAdapter.kt

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にわたすことで反映しています。

MainActivity.kt

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を作成するだけです。

SpecialItem.kt
class SpecialItem(override val isVisible: Boolean) : ItemContract {
    override fun getView(context: Context): View {
        return LayoutInflater.from(context).inflate(R.layout.layout_special, null)
    }
}
layout_special.xml
<?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.ktMainActivity.ktに追加します。

App.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 }
    }
}
MainActivity.kt
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
    }
}

実行!

別のLayoutの項目が表示されたかと思います。

余談

例えば
12月のみSpecialLayoutを表示したい
などの場合はApp.ktで条件を書いて別インスタンスを作成すればいけます。

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 }
        }
    }
MainActivity.kt
...

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
    }
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2