Anko for Android

weixin_34120274發表於2015-10-21

Anko 是一個使開發Android應用更簡單更快捷的庫,Anko使你的程式碼簡潔易懂, 使開發者不用再在意Android SDK對Java版本的限制(目前還不支援Java8 =。= ).

Anko版本的 hello world :

verticalLayout {
    val name = editText()
    button("Say Hello") {
        onClick { toast("Hello, ${name.text}!") }
    }
}

上面的程式碼建立了一個Button,放在 LinearLayout 內, 併為其設定了一個點選監聽器OnClickListener .

上面是一個DSL(Domain Specific Language),使用的是 Kotlin語言.

DSL,即 Domain Specific Language,領域相關語言。什麼是 DSL,說白了它就是某個行業中的行話。

[TOC]

Why Anko?

為啥 DSL?

平時開發android, UI寫在xml中,這就導致了下面的幾個問題 :

  • It is not typesafe
  • It is not null-safe
  • It forces you to write almost the same code for every layout you make
  • XML is parsed on the device wasting CPU time and battery 渲染xml為物件過程耗時耗電
  • Most of all, it allows no code reuse. 大部分不能重用

但是全部只在程式碼中寫UI,這很難,不僅程式碼醜,而且冗餘難維護,下面是 Ktolin版本的(Java甚至更長):

val act = this
val layout = LinearLayout(act)
layout.setOrientation(LinearLayout.VERTICAL)
val name = EditText(act)
val button = Button(act)
button.setText("Say Hello")
button.setOnClickListener {
    Toast.makeText(act, "Hello, ${name.getText()}!", Toast.LENGTH_SHORT).show()  
}
layout.addView(name)
layout.addView(button)

DSL 就不一樣類,相同的邏輯,但簡潔易懂, 易於編寫而且沒有執行開銷(runtime overhead)看下面的程式碼:

verticalLayout {
    val name = editText()
    button("Say Hello") {
        onClick { toast("Hello, ${name.text}!") }
    }
}

為啥不用 Scaloid?

Scaloid 是一個類似與 Scala 的庫, 有很多非常酷的特性可供 Scala 開發者使用. Anko主要是針對 Java 和 Kotlin developers.

相容已有的程式碼

不需要用Anko重寫所有的UI, 你可以保留原有的Java程式碼. 此外, 如果你想寫一個 Kotlin的activity類並且由於某些需求需要使用 inflate來渲染xml, 你完全可以按照原來的寫法:

// Same as findViewById(), simpler to use
val name = find<TextView>(R.id.name)
name.hint = "Enter your name"
name.onClick { /*do something*/ }

工作原理

There is no :tophat:. Anko 由一些 Kotlin擴充套件函式和屬性,被設定成型別安全(type-safe builders)的, under Type Safe Builders.

他們繁瑣的手工編寫所有這些擴充套件, 使用Android SDK的原始碼中的 android.jar 檔案自動生成

可擴充套件嗎?

答案是: yes.
例如. 你可能想使用 MapView 在DSL中.你可以編寫下面的程式碼(kotlin檔案中),然後就可已到處使用了

public inline fun ViewManager.mapView() = mapView {}
public inline fun ViewManager.mapView(init: MapView.() -> Unit): MapView {
    return ankoView({ MapView(it) }, init)
}

{ MapView(it) } 是你自定義View的一個工廠方法View. 接受一個 Context .

frameLayout {
    val mapView = mapView().lparams(width = matchParent)
}

如果你想建立一個 頂級的 DSL,看這裡Extending Anko.

使用 Gradle

這裡有個例子 template project 展示類如果在Android中Gradle配置.

基本上,你只需要配置 repository 和一個 compile dependency:

dependencies {
    compile 'org.jetbrains.anko:anko-sdk15:0.7.1' // sdk19, sdk21, sdk23 are also available
    compile 'org.jetbrains.anko:anko-support-v4:0.7.1' // In case you need support.v4 bindings
}

當作 Jar library使用

加入你的專案不是基於Gradl, 不需要配置 Maven. 只要新增這裡 的jar包即可.

編譯 Anko

如何編譯看under Building.

理解 Anko

Anko 是使用 Kotlin語言編寫的.
如果不熟悉Kotlinkotlinlang.org.
Kotlin與Java很類似,所以很容易學.

基礎

Anko中, 你不需要繼承其他奇怪的類,只要標準的Activity, Fragment, FragmentActivity 或者其他任意的類

首先, 在使用Anko的DSL的類中匯入 org.jetbrains.anko.* .

DSL 可以在 onCreate()中使用:

override fun onCreate(savedInstanceState: Bundle?) {
    super<Activity>.onCreate(savedInstanceState)
    
    verticalLayout {
        padding = dip(30)
        editText {
            hint = "Name"
            textSize = 24f
        }
        editText {
            hint = "Password"
            textSize = 24f
        }
        button("Login") {
            textSize = 26f
        }
    }
}

不需要顯示的呼叫 setContentView(R.layout.something), Anko 自動為Activity(只會對Activity)進行 set content view

padding, hinttextSize擴充套件屬性. 大多數 View 具有這些屬性,允許使用text = "Some text" 代替 setText("Some text").

verticalLayout (一個豎直方向的 LinearLayout), editTextbutton are
擴充套件函式. 這些函式存在與ANdroid 框架中的大部View中, Activities, Fragments ( android.support 包中的) 甚至 Context同樣適用.

如果有一個 Context 例項, 可以寫出下面的DSL結構:

val name = with(myContext) {
    editText {
        hint = "Name"
    }
}

變數 name 成為了 EditText型別.

Helper 方法

你可能注意到了,前面章節中 button 方法接了一個字串引數,這樣的Helper方法同樣使用與 TextView, EditText, ButtonImageView.

如果你不需要 View 其他的屬性,你可以省略 {} 直接寫 button("Ok") 或只有 button():

verticalLayout {
    button("Ok")
    button("Cancel")
}

Layouts 和 LayoutParams

在父佈局中佈局控制元件可能需要使用 LayoutParams. 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來實現類似與xml的 LayoutParams

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

如果指定了 lparams,但是沒有指定 width 或者 height, 預設是 WRAP_CONTENT.但是你可以自己通過使用named arguments指定.

注意下面一些方便的屬性:

  • horizontalMargin 同時設定 left 和 right margins,
  • verticalMargin 同時設定 top 和 bottom
  • margin 同時設定4個方向的 margins.

注意 lparams 的使用在不同的佈局中是不同的, 例如在 RelativeLayout中:

val ID_OK = 1

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

Listeners

設定listeners:

button("Login") {
    onClick {
        login(name, password)
    }
}

下面的效果一樣:

button.setOnClickListener(object : OnClickListener {
    override fun onClick(v: View) {
        login(name, password)
    }
})

當一個Listener有多個方法時,Anko就顯得很方便類. 看下面的程式碼(沒有使用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
        }
    }
}

如果你同時設定了onProgressChangedonStartTrackingTouch , 兩個方法將被合併. 對於多個相同的方法,最後的一個有效.

Resources, Colors 和 Dimensions

Using resource identifiers

前面的所有例子直接使用的 Java的字串,但是大多數時候字串都是放在 res/values/ 目錄下,並且是執行時呼叫的,例如 getString(R.string.login).

幸運的是,Anko中可以使用以下兩個 helper方法 (button(R.string.login)) 和 (button { textResource = R.string.login }).

注意,這些屬性不是 text, hint, image, 而是 textResource, hintResource and imageResource.

Resource properties always throw AnkoException when read.

Colors

兩個簡單的擴充套件函式使程式碼更加易懂。

Function Result
0xff0000.opaque <span style="color:#ff0000">non-transparent red</span>
0x99.gray.opaque <span style="color:#999">non-transparent #999999 gray</span>

Dimensions

你可以指定 dimension 的 dip (density-independent pixels) 或 sp (scale-independent pixels)值: dip(dipValue)sp(spValue). 注意 textSize屬性預設接受sp (textSize = 16f). 使用 px2dippx2sp 相互轉換.

Instance shorthands

在Activity中,有時你需要傳一個 Context例項給一個 Android SDK中的方法,通常你會寫 this, 如果在內部類呢?你可能寫SomeActivity.this ,如果你使用 Kotlin,你只需寫 this@SomeActivity
使用 Anko,你可以只寫 ctx. ctxActivityService 或者 Fragment (使用的 getActivity() )內部的一個屬性. 你也可以使用act擴充套件屬性獲取 Activity例項.

UI wrapper

開始使用Anko 之前,將 UI tag 作為 DSL 頂級元素:

UI {
    editText {
        hint = "Name"
    }
}

這將更易於擴充套件 DSL ,因為你必須宣告一個函式 ViewManager.customView.
看這裡 Extending Anko 獲取更多資訊.

Include tag

使用 include tag 很容易向 DSL 插入 一個 XML layout :

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

通常可以使用 lparams , 如果型別不是 View,仍然可以用 {}:

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

Styles

Anko 支援 styling: style 是一個簡單的函式,接受一個View, 效果作用於這個 View , 並且當這個View 是一個ViewGroup 時,可以可以遞迴的作用與 這個View的 child View:

verticalLayout {
    editText {
        hint = "Name"
    }
    editText {
        hint = "Password"
    }
}.style { view -> when(view) {
    is EditText -> view.textSize = 20f
}}


相關文章