Kotlin

Kotlin向けAndroid UI構築用ライブラリAnko Wiki要約

Understanding Ankoをほぼ日本語訳しただけのものですが、ご了承ください。
※また、私自身、英語力には自信がありませんので、誤りがありましたらご指摘ください。

基本

Ankoライブラリを利用するために、org.jetbrains.anko.*パッケージをインポートしましょう。

それでは、onCreate()の例を見てみましょう。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    verticalLayout {
        padding = dip(30)
        editText {
            hint = "Name"
            textSize = 24f
        }
        editText {
            hint = "Password"
            textSize = 24f
        }
        button("Login") {
            textSize = 26f
        }
    }
}

Ankoでは、このようなDSLで記述できます。従来のように、setContentView(R.layout.something)を呼び出す必要はありません。
これを詳しく見れば、

  • hint, textSize: JavaBeansのgetter/setter仕様に乗っ取った拡張プロパティ
  • padding: Ankoの拡張プロパティ
  • verticalLayout, editText, button : Viewインスタンスを生成し、親Viewに追加する拡張関数

であることがわかります。

このような記法は、Androidフレームワーク下のほぼ全てのViewに採用され、Activity, Fragment, Context上で動作します。

AnkoComponent

前項では、onCreate()内にDSLを記述しましたが、通常は別のクラスを作ったほうが良いでしょう。
以下のように、onCreate()内で、AnkoComponentインターフェースを実装したクラスのインスタンスのsetContentView()を呼び出しましょう。

class MyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
        super.onCreate(savedInstanceState, persistentState)
        MyActivityUI().setContentView(this)
    }
}

class MyActivityUI : AnkoComponent<MyActivity> {
    override fun createView(ui: AnkoContext<MyActivity>) = with(ui) {
        verticalLayout {
            val name = editText()
            button("Say Hello") {
                onClick { ctx.toast("Hello, ${name.text}!") }
            }
        }
    }
}

ブロックの省略

もしViewにプロパティが不要であれば、{}を省略して、button("Ok")button()とできます。

例:

verticalLayout {
    button("Ok")
    button(R.string.cancel)
}

また、以下のように、テンプレートを付加することもできます。

verticalLayout {
    themedButton("Ok", theme = R.style.myTheme)
}

※Themed blocksを利用し次第、書き改めます。

レイアウトの記述

レイアウトを記述する際に、従来では、XML内に、

<ImageView 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="5dip"
    android:layout_marginTop="10dip"
    android:src="@drawable/something" />

のように記述していましたが、Ankoでは、Viewの後ろにlparams()を続けて、

linearLayout {
    button("Login") {
        textSize = 26f
    }.lparams(width = wrapContent) {
        horizontalMargin = dip(5)
        topMargin = dip(10)
    }
}

のように記述できます。widthheightは、lparams()の名前付き引数として定義されており、省略した場合は、いずれもwrapContextのデフォルト値が使用されます。
また、

  • horizontalMargin : 左右
  • verticalMargin : 上下
  • margin : 上下左右

のプロパティを用いることで、対応する余白が同時に指定できます。

lparams()の記述はレイアウト(ViewGroup)によって異なります。例えば、RelativeLayoutであれば、

val ID_OK = 1

relativeLayout {
    button("Ok") {
        id = ID_OK
    }.lparams { alignParentTop() }

    button("Cancel").lparams { below(ID_OK) }
}

となります。

Listeners

ボタンを作成する際、

button("Login") {
    onClick {
        // do something
    }
}

と記述していました。実はこれ、

button.setOnClickListener(object : OnClickListener {
    override fun onClick(v: View) {
        launch(UI) {
            // do something
        }
    }
})

とほぼ同じ記述なのです。つまり、拡張関数内でコルーチンが作成されるため、非同期処理をシームレスに記述することができるのです。

Ankoには、他にも可読性を向上させる記述方法があります。例えば、

seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
    override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
        // Something
    }
    override fun onStartTrackingTouch(seekBar: SeekBar?) {
        // Just an empty method
    }
    override fun onStopTrackingTouch(seekBar: SeekBar) {
        // Another empty method
    }
})

を現在のAnkoでは、

seekBar {
    onSeekBarChangeListener {
        onProgressChanged { seekBar, progress, fromUser ->
            // Something
        }
    }
}

と記述できます。onProgressChanged()onStartTrackingTouch()を同じView内に記述することも可能です。同じ関数を呼び出した場合は、最後に呼び出したものが適用されます。

また、以下のように、自前のコルーチンコンテキストを利用することもできます。

button("Login") {
    onClick(yourContext) {
        val user = myRetrofitService.getUser().await()
        showUser(user)
    }
}

リソースの扱い

前項までの例では、文字列をそのまま用いていましたが、本来ならば、文字列データはres/values/ディレクトリ以下にあり、実行時に呼ばれるべきです。
もちろんAnkoでも、今まで、getString(R.string.login)と記述していたように、
button(R.string.login)

button { textResource = R.string.login }
と記述することが可能です。ここで注意しなければならないのが、text, hint, imageではなく、textResource, hintResource, imageResourceを用いる必要があるということです。

インスタンスの取得

Ankoでは、ctxと記述するだけで、Contextインスタンスを取得できます。インナークラスであっても、this@SomeActivityと記述する必要はありません。また、Activityインスタンスはactで取得できます。

DSL内でXMLを利用する

include()を用いて、

include<View>(R.layout.something) {
    backgroundColor = Color.RED
}.lparams(width = matchParent) { margin = dip(12) }

と記述することで、簡単にXMLをDSLに挿入できます。
lparams()はいつでも利用でき、View以外の特定の型を用いる場合も同じ記述ができます。

例:

include<TextView>(R.layout.textfield) {
    text = "Hello, world!"
}

参考

https://github.com/Kotlin/anko/wiki/Anko-Layouts#understanding-anko