1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Android + AnkoのAnkoComponent周りで自分がよく使うパターンをまとめてみる。

Last updated at Posted at 2018-06-18

自分が実際にアプリ作成時、Ankoを使う際に使うパターンをまとめてみます。

コンポーネント

まずは、画面のコンポーネントを表すインターフェースを定義します

Component.kt
import android.view.View

interface Component<R : View> {
    val root: R
}

fun <R : View, T : View> Component<R>.bindView(id: Int) = lazy {
    root.findViewById<T>(id)
}

val Component<*>.rootId: Int
    get() {
        if (root.id == View.NO_ID) {
            root.id = View.generateViewId()
        }
        return root.id
    }

コンポーネントは必ずrootとなるViewを持つようにして、拡張関数としてbindViewを、拡張プロパティとしてrootIdを提供します。

ViewComponent

次に、複数のActivityで用いるような共通のComponentを表すViewComponent(名前が微妙)を定義します。

ViewComponent.kt
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.view.ViewManager
import org.jetbrains.anko.AnkoContext
import org.jetbrains.anko.AnkoException
import kotlin.properties.Delegates


interface ViewComponent<R : View> : Component<R> {
    //Contextを元にViewを生成
    fun createView(ctx: Context): R
}

private val DEFAULT_INIT: View.() -> Unit = {}

//ViewComponentからViewを生成してViewManagerに追加
//主にAnkoのDSL上で使用する
fun <R : View> ViewManager.component(component: ViewComponent<R>,
                                     init: R.() -> Unit = DEFAULT_INIT): R = when (this) {
    is ViewGroup -> component.createView(context).apply(init).also(::addView)
    is AnkoContext<*> -> component.createView(ctx).apply(init).also { addView(it, null) }
    else -> throw AnkoException("$this is the wrong parent")
}

//ViewComponentからViewを生成してViewManagerに追加
//主にAnkoのDSL外で使用する
fun <R : View> ViewManager.addComponent(component: ViewComponent<R>,
                                        init: R.() -> Unit = DEFAULT_INIT): R = component(component, init)

//主に長過ぎるDSLを切り分けるために使う
fun <R : View> component(create: Context.() -> R): ViewComponent<R> = object : ViewComponent<R> {
    override var root: R by Delegates.notNull()
        private set

    override fun createView(ctx: Context) = ctx.create().also { root = it }
}

UI

最後にActivityのUIを表すUIを定義します。

UI.kt
import android.app.Activity
import android.view.View
import org.jetbrains.anko.AnkoComponent

interface UI<A : Activity, R : View> : Component<R>, AnkoComponent<A>

実際に使う

使う際はこんな感じ

AppbarComponent.kt
interface IAppbarComponent : ViewComponent<AppBarLayout> {

    val toolbar: Toolbar
}

class AppbarComponent : IAppbarComponent {
    override var root: AppBarLayout by Delegates.notNull()
        private set

    private val toolbarId = View.generateViewId()
    override val toolbar: Toolbar by bindView(toolbarId)

    override fun createView(ctx: Context) = with(ctx) {
        appbarLayout {
            root = this
            toolbar {
                id = toolbarId
            }
        }
    }
}
MainUI.kt
interface IMainUI : UI<MainActivity, RelativeLayout> {
    val textView: TextView
    val appbarComponent: IAppbarComponent
}

class MainUI : IMainUI {
    override lateinit var root: RelativeLayout
        private set

    private val textViewId = View.generateViewId()
    override val textView: TextView by bindView(textViewId)

    override val appbarComponent: IAppbarComponent = AppbarComponent()

    @SuppressLint("SetTextI18n")
    override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
        relativeLayout {
            root = this

            component(appbarComponent) {
                //Appbarをなんか適当に初期化してみたり
            }.lparams(matchParent, wrapContent)

            this.textView {
                id = textViewId
                text = "Hello World"
            }.lparams(wrapContent, wrapContent) {
                centerInParent()
            }
        }
    }
}
MainActivity.kt
class MainActivity : AppCompatActivity(), IMainUI by MainUI() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(this)
        setSupportActionBar(appbarComponent.toolbar)
    }
}

こんな感じでボイラープレート的に頻繁にでてくるAppbarLayoutToolbarの入れ子等をViewComponentとして切り分けて見たり出来ます。

あとはRecyclerView.ViewHolderに使うViewViewComponentとして定義してみたりとか。

ちなみにAndroidXを使っているのでappbarLayouttoolbarはこんな感じで定義してます。

import android.content.Context
import android.view.View
import android.view.ViewManager
import androidx.appcompat.widget.Toolbar
import com.google.android.material.appbar.AppBarLayout
import org.jetbrains.anko.custom.customView

private val DEFAULT_INIT: View.() -> Unit = {}

fun ViewManager.toolbar(theme: Int = 0, init: Toolbar.() -> Unit = DEFAULT_INIT): Toolbar {
    return customView(theme, init)
}

fun Context.toolbar(theme: Int = 0, init: Toolbar.() -> Unit = DEFAULT_INIT): Toolbar {
    return customView(theme, init)
}

fun ViewManager.appbarLayout(theme: Int = 0, init: AppBarLayout.() -> Unit = DEFAULT_INIT): AppBarLayout {
    return customView(theme, init)
}

fun Context.appbarLayout(theme: Int = 0, init: AppBarLayout.() -> Unit = DEFAULT_INIT): AppBarLayout {
    return customView(theme, init)
}

まとめ

ComponentにはViewとしての振る舞いも持たせていいのかもなーとか思います。
例えば、TwitterCardComponentとかあったりしたらそれに、setTextとかsetUserNameを定義するみたいな。

1
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?