Ankoは、XMLに慣れた人にも違和感なく使用できます。
今までXMLで書いていたものをAnkoではどう書くのかを見ていきましょう。
途中でAnkoの実装に触れた部分が少しありますが、今回は軽い説明で勘弁してください。
Viewの追加
verticalLayout {
textView {
text = "Hello, Anko!"
}
}
{ } の中に入れ子に書いていくことでViewを追加していくことができます。
XMLとほぼ変わらない感覚で書けることが分かると思います。
addViewを呼ぶ必要はありません。
ちなみに、verticalLayoutはorientationにverticalを指定したLinearLayoutです。
このverticalLayoutの正体はActivityの拡張関数です。
inline fun Activity.verticalLayout(): LinearLayout = verticalLayout({})
inline fun Activity.verticalLayout(init: _LinearLayout.() -> Unit): LinearLayout {
return ankoView(`$$Anko$Factories$CustomViews`.VERTICAL_LAYOUT_FACTORY, init)
}
inline fun <T : View> Activity.ankoView(factory: (ctx: Context) -> T, init: T.() -> Unit): T {
val view = factory(this)
view.init()
AnkoInternals.addView(this, view)
return view
}
少々分かりづらいかと思いますが、
- verticalなLinearLayoutのインスタンスを作成して
- 初期化を行い
- addView()する
という流れになっています。
UIの初期化に使われるのがカッコの中身の部分です。
verticalLayout {
// ここの中身が初期化時に呼ばれる
textView {
text = "Hello, Anko!"
}
}
初期化関数をカッコの中身にして渡す部分は、拡張関数を引数に取れるというKotlinの言語機能を利用して作られています。
詳しくは以下の記事が参考になりました。
jetBrains/anko触ってたらよくわからない実装を見たので調べたらKotlinで引数に拡張関数を取る関数を作れる事がわかった
属性の定義
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is XML layout."
android:background="@color/black"
android:textColor="@color/white"
/>
textView {
text = "Hello, Anko!"
backgroundColor = Color.BLACK
textColor = Color.WHITE
}
Anko DSLでは色の指定時にColorの定数を使用できます。
また、XMLのような名前空間の指定は不要です。
text = "Hello, Anko"
の部分はsetterのシンタックスシュガーとなっており、setText()しています。
textColor = Color.WHITE
の部分は拡張プロパティとなっています。
実際にはsetTextColor()を呼んでいますが、プロパティへの代入のように記述できます。
var android.widget.TextView.textColor: Int
get() = throw AnkoException("'android.widget.TextView.textColor' property does not have a getter")
set(v) = setTextColor(v)
イベントリスナーの追加
button("Show Toast") {
onClick { toast("Click!") }
}
イベントリスナーの設定もViewの追加と同じような表記で書くことができます。
上記のように書くとonClick時にToastを表示することができます。
こちらも拡張関数で実装されており、実際にはsetOnClickListener()を呼んでいます。
fun android.view.View.onClick(l: (v: android.view.View?) -> Unit) {
setOnClickListener(l)
}
LayoutParams
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is XML layout."
android:background="@color/black"
android:textColor="@color/white"
android:layout_marginTop="16dip"
/>
textView {
text = "Hello, Anko!"
backgroundColor = Color.BLACK
textColor = Color.WHITE
lparams {
topMargin = dip(16)
}
}
marginなどのLayoutParamsに属するものは lparams {} の中で指定します。
dipの指定はdip(16)のように書くことができます。
以下のようなContextの拡張関数が定義されており、これを使用することで簡潔な表現が可能になっています。
fun Context.dip(value: Int): Int = (value * resources.displayMetrics.density).toInt()
先ほどのレイアウトと同じものを以下のように表記することもできます。
textView {
text = "Hello, Anko!"
backgroundColor = Color.BLACK
textColor = Color.WHITE
}.lparams {
topMargin = dip(16)
}
公式のドキュメントではこちらの表記の方が普通のようです。
XMLのinclude
既存のXMLをAnkoの中にincludeするには下のように書きます。
include<RelativeLayout>(R.layout.xml_layout) {
}
これもやっぱり正体は拡張関数です。
inline fun <T: View> ViewManager.include(layoutId: Int): T = include(layoutId, {})
inline fun <T: View> ViewManager.include(layoutId: Int, init: T.() -> Unit): T {
@Suppress("UNCHECKED_CAST")
return ankoView({ ctx -> ctx.layoutInflater.inflate(layoutId, null) as T }) { init() }
}
ctxはContextです。普通にXMLをinflateしているだけですね。
相対レイアウトを指定する
RelativeLayoutの中で、他のViewとの相対位置を指定するときにはrightOf()などの ~~Of()関数を使用します。
class MainActivityUi() : AnkoComponent<MainActivity> {
override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
relativeLayout {
val xmlLayout = include<RelativeLayout>(R.layout.xml_layout) {
}
textView {
text = "Hello, Anko!"
backgroundColor = Color.BLACK
textColor = Color.WHITE
}.lparams {
topMargin = dip(16)
rightOf(xmlLayout)
alignParentRight()
}
}
}
}
idを指定することもできますが、この場合にはView.generateViewId()などを使用して動的に一意のidを作ります。
class MainActivityUi() : AnkoComponent<MainActivity> {
val xmlLayoutId = View.generateViewId()
var xmlLayout : RelativeLayout? = null
override fun createView(ui: AnkoContext<MainActivity>) = with(ui) {
relativeLayout {
xmlLayout = include<RelativeLayout>(R.layout.xml_layout) {
id = xmlLayoutId
}
textView {
text = "Hello, Anko!"
backgroundColor = Color.BLACK
textColor = Color.WHITE
}.lparams {
topMargin = dip(16)
rightOf(xmlLayoutId)
alignParentRight()
}
}
}
}
idを作成する方法は ids.xmlにidを指定する方法でも構いません。
カスタムビューを使用する
自作したViewも下の2行を追加すれば、Anko DLSの中に自然に混ぜることができます。
inline fun ViewManager.customView() = customView() {}
inline fun ViewManager.customView(init: CustomView.() -> Unit) = ankoView({ CustomView(it) }, init)
verticalLayout {
customView {
textColor = Color.RED
}
}
上記は0.8.3までの方法で、0.9からは以下のようになります。
inline fun ViewManager.customView(theme: Int = 0) = customView(theme) {}
inline fun ViewManager.customView(theme: Int = 0, init: CustomView.() -> Unit) = ankoView({ CustomView(it) }, theme, init)
Fragmentを追加する
XMLでの<fragment>タグのようにAnkoの中でFragmentを追加したいときは以下のように書けます。
参考: <fragment> tag equivalent in anko DSL
supportFragmentManager.beginTransaction().add(this.id,com.myapp.MyFragment()).commit()
しかし、この方法でやったところPreviewが表示されなくなりました。
Ankoの中に直接書かずに、idを指定したViewに対して外からaddしたときにはPreviewを表示することができました。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val ui = MainActivityUi()
ui.setContentView(this)
supportFragmentManager.beginTransaction().add(ui.framgentId, com.myapp.MyFragment()).commit()
}
}
XMLではできないこと
拡張関数を使用する
これまで見てきましたようにAnkoではKotlinの拡張関数の機能を生かして簡潔な表記を実現していました。
自分たちで追加した拡張関数も同じように利用することができます。
以下はPicassoで画像を読み込むようにする例です。
fun ImageView.loadImage(context: Context, imageUrl: String, error: Int) {
Picasso.with(context).load(imageUrl).error(error).into(this)
}
imageView = imageView {
loadImage(ctx, "画像のURL", R.drawable.error)
}
このように気軽に機能を追加できることが Anko DSL = Kotlinのコードで書くことのメリットの一つだと思います。
次の記事はListViewでの使用例について書こうと思います。