自分が実際にアプリ作成時、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)
}
}
こんな感じでボイラープレート的に頻繁にでてくるAppbarLayout
とToolbar
の入れ子等をViewComponent
として切り分けて見たり出来ます。
あとはRecyclerView.ViewHolder
に使うView
をViewComponent
として定義してみたりとか。
ちなみにAndroidXを使っているのでappbarLayout
とtoolbar
はこんな感じで定義してます。
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
を定義するみたいな。