Anko的簡(jiǎn)介
引用Anko的GitHub主頁(yè)上面的解釋:
Anko is a Kotlin library which makes Android application development faster and easier. It makes your code clean and easy to read, and lets you forget about rough edges of the Android SDK for Java.
Anko是為了使Android開發(fā)程序更加簡(jiǎn)單和快速而生成的一個(gè)Kotlin庫(kù),它可以使您的代碼清晰、易讀,并且它可以讓您忘記粗糙的Java Android SDK。
Anko目前主要用于:Layout布局、SQLite數(shù)據(jù)庫(kù)和Coroutines協(xié)程三個(gè)方面。
接下來我們主要交流的是Layout方面的知識(shí)。
引入Anko和遇到的問題
添加Anko的依賴: implementation "org.jetbrains.anko:anko:$anko_version"
這時(shí)發(fā)現(xiàn)有爆紅的地方了:提示v7包和v4包版本不一致,這就很納悶了,我都沒用私自添加刪除v4包,怎么會(huì)出現(xiàn)問題呢,接著我就去libraries找原因了,原來是Anko也引入了v4的包,而且版本是27.1.1,我新建工程的編譯版本是28.0.0,小伙伴們要注意了。
解決:排除anko包中的v4包
implementation("org.jetbrains.anko:anko:$anko_version") {
exclude module: 'support-v4'
}
使用Anko,從四個(gè)點(diǎn)介紹下如何使用Anko以及遇到的問題
① 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的登錄界面
既然是使用Anko,那么當(dāng)然是要拋棄xml布局文件咯,也就不用寫setContentView()來綁定布局文件了,可以直接在onCreate()方法里面調(diào)用我們自己寫的AnkoComponent類的setContentView()綁定activity就行了,這種寫法是比較推薦的一種,還有一種就是直接把verticalLayout{}寫在onCreate()里面,但是不推薦,這樣會(huì)造成Activity類的代碼冗余。下面來看看如何實(shí)現(xiàn)這么一個(gè)簡(jiǎn)單的布局:
class AnkoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// AnkoComponent和Activity相互綁定
MainUI().setContentView(this@AnkoActivity)
}
}
class MainUI : AnkoComponent<AnkoActivity> {
override fun createView(ui: AnkoContext<AnkoActivity>) = with(ui) {
verticalLayout {
// 這個(gè)gravity對(duì)應(yīng)的就是gravity,而在lparams閉包中,gravity對(duì)應(yīng)的是layout_gravity
gravity = Gravity.CENTER
// 布局的屬性params在閉包里面的lparams中設(shè)置,但是button、TextView等控件的屬性params是在閉包外的lparams設(shè)置
lparams(matchParent, matchParent)
editText {
hint = "userName"
gravity = Gravity.CENTER
// 監(jiān)聽輸入框輸入情況
addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
})
}.lparams(width = dip(250), height = 200)
editText {
hint = "password"
top = 20
gravity = Gravity.CENTER
}.lparams(width = dip(250), height = 200)
button("list") {
backgroundColor = Color.parseColor("#FF9999")
alpha = 0.5f
// 點(diǎn)擊事件
onClick {
// anko封裝的intent攜帶值跳轉(zhuǎn)
startActivity<ListActivity>("aulton" to "aulton")
}
// 長(zhǎng)按事件
onLongClick {
toast("Long Click")
}
}.lparams(dip(250), dip(50))
button("setting") {
backgroundColor = Color.parseColor("#FF7777")
alpha = 0.5f
// 點(diǎn)擊事件
onClick {
// anko封裝的intent攜帶值跳轉(zhuǎn)
startActivity<SettingActivity>("aulton" to "aulton")
}
}.lparams(dip(250), dip(50)) {
topMargin = dip(16)
}
button("custom_view") {
backgroundColor = Color.parseColor("#FF7777")
alpha = 0.5f
// 點(diǎn)擊事件
onClick {
// anko封裝的intent攜帶值跳轉(zhuǎn)
startActivity<CustomCircleActivity>("aulton" to "aulton")
}
}.lparams(dip(250), dip(50)) {
topMargin = dip(16)
}
}
}
}
這里我們用的都是大家常見的一些布局和控件,
verticalLayout就是oritentation=vertical的LinearLayout。控件和布局的一些屬性需要注意下,比如
verticalLayout里面的gravity = Gravity.CENTER對(duì)應(yīng)的就是xml中的gravity,如果出現(xiàn)在lparams閉包中的gravity = Gravity.CENTER指的就是layout_gravity屬性了。千萬要分清。-
AnkoComponent的createView()其實(shí)是有返回值的interface AnkoComponent<in T> { fun createView(ui: AnkoContext<T>): View }返回的是一個(gè)
View對(duì)象,這里我使用with(ui)來實(shí)現(xiàn)自動(dòng)返回。你也可以使用let()或者apply()等作用域函數(shù)。你可以從ui: AnkoContext<T>對(duì)象中拿到Context、Activity和View三個(gè)對(duì)象,都是很重要的屬性。
效果如下:

② 使用Anko實(shí)現(xiàn)RV列表
要想使用RecyclerView你必須添加新的依賴:
// RecyclerView-v7
implementation "org.jetbrains.anko:anko-recyclerview-v7:$anko_version"
implementation "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version"
首先寫出rv+swipeRefreshLayout布局:
class ListUI : AnkoComponent<ListActivity> {
override fun createView(ui: AnkoContext<ListActivity>) = with(ui) {
// 下拉刷新控件
swipeRefreshLayout {
// 下拉監(jiān)聽事件
setOnRefreshListener {
toast("refresh")
isRefreshing = false
}
// rv
recyclerView {
layoutManager = LinearLayoutManager(ui.ctx)
lparams(width = matchParent, height = matchParent)
adapter = MyAdapter(ui.ctx, mutableListOf("1",
"2", "3", "4"))
}
}
}
}
rv所有的屬性:manager、adapter都可以直接在閉包里面設(shè)置。
接下來看看適配器中的ItemView如何用Anko實(shí)現(xiàn):
class MyAdapter(private val context: Context,
private val mData: MutableList<String>) : RecyclerView.Adapter<MyAdapter.MyHolder>() {
// 創(chuàng)建Holder對(duì)象
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): MyHolder {
// 根據(jù)anko生成itemView,并且給itemView的tag賦值,從而取得MyHolder
return AdapterUI().createView(AnkoContext.create(context, p0)).tag as MyHolder
}
override fun getItemCount(): Int {
return mData.size
}
// 綁定holder,呈現(xiàn)UI
override fun onBindViewHolder(holder: MyHolder, p1: Int) {
holder.tv_name.text = mData[p1]
}
class MyHolder(view: View, val tv_name: TextView) : RecyclerView.ViewHolder(view)
class AdapterUI : AnkoComponent<ViewGroup> {
override fun createView(ui: AnkoContext<ViewGroup>): View {
var tv_name: TextView? = null
val item_view = with(ui) {
relativeLayout {
lparams(width = matchParent, height = dip(50))
tv_name = textView {
textSize = 12f
textColor = Color.parseColor("#FF0000")
backgroundColor = Color.parseColor("#FFF0F5")
gravity = Gravity.CENTER
}.lparams(width = matchParent, height = dip(50)) {
// 設(shè)置外邊距
topMargin = dip(10)
}
}
}
// 返回itemView,并且通過tag生成MyHolder
item_view.tag = MyHolder(item_view, tv_name = tv_name!!)
return item_view
}
}
}
其實(shí)這里主要使用到的就是View對(duì)象的tag屬性,將ItemView的tag和Holder綁定在一起,這樣我們AnkoComponent的createView()返回ItemView的同時(shí)也把Holder生成并返回了,就可以在Adapter的onCreateViewHolder()方法中拿到Holder對(duì)象。
效果如下:

③ 復(fù)用AnkoView
在日常開發(fā)中我們會(huì)遇到這樣的情形,類似于通用的設(shè)置界面,所有的條目都是很類似的,只不過文字或者icon不一樣,如果我們用rv來實(shí)現(xiàn),難免覺得條目太少,不劃算,但是每個(gè)條目都是自己寫一遍,又會(huì)覺得太繁瑣,這時(shí)候Anko就會(huì)幫助我們簡(jiǎn)化很大的代碼,下面一起來看看:
fun myLinearLayout(viewManager: ViewManager,
itemHeight: Int = 40,
itemMarginTop: Int = 0,
itemMarginBottom: Int = 0,
headImageId: Int = 0,
headTextRes: String,
bottomImageId: Int = 0) = with(viewManager) {
linearLayout {
orientation = LinearLayout.HORIZONTAL
leftPadding = dip(16)
rightPadding = dip(16)
backgroundColor = Color.parseColor("#FFFFFF")
// 設(shè)置整體的寬高和外邊距
lparams(width = matchParent, height = dip(itemHeight)) {
setMargins(0, itemMarginTop, 0, itemMarginBottom)
}
// 左邊圖片
if (headImageId != 0) {
imageView(headImageId) {
scaleType = ImageView.ScaleType.FIT_XY
}.lparams(width = dip(30), height = dip(30)) {
gravity = Gravity.CENTER
}
}
// 左邊字體
textView(headTextRes) {
gravity = Gravity.CENTER_VERTICAL
}.lparams(width = matchParent, height = matchParent, weight = 1f) {
if (headImageId != 0) {
marginStart = dip(16)
}
}
// 右邊圖片
if (bottomImageId != 0) {
imageView(bottomImageId) {
scaleType = ImageView.ScaleType.FIT_XY
}.lparams(width = dip(30), height = dip(30)) {
gravity = Gravity.CENTER
}
}
}
}
首先定義一個(gè)方法,方法包含了item的高度、上下外邊距、頭部icon、頭部文字、尾部icon和ViewManager7個(gè)參數(shù)。方法的內(nèi)部實(shí)現(xiàn)采用Anko的DSL(領(lǐng)域特定語(yǔ)言)語(yǔ)言實(shí)現(xiàn)。其中參數(shù)ViewManager是我們前面提到的AnkoComponent的父類,它是這個(gè)方法的主要參數(shù),因?yàn)樵贏nko實(shí)現(xiàn)的一系列View都是ViewManager的擴(kuò)展函數(shù)。想復(fù)用的話,直接調(diào)用這個(gè)方法就行了,F(xiàn)orExample:
class SettingUI : AnkoComponent<SettingActivity> {
override fun createView(ui: AnkoContext<SettingActivity>) = with(ui) {
verticalLayout {
myLinearLayout(viewManager = this,
headImageId = R.mipmap.setting,
headTextRes = "Setting",
bottomImageId = R.mipmap.arrow,
itemMarginBottom = 8,
itemMarginTop = 8)
myLinearLayout(viewManager = this,
headTextRes = "MyInfo",
bottomImageId = R.mipmap.arrow,
itemMarginBottom = 8)
myLinearLayout(this,
headTextRes = "Exit",
headImageId = R.mipmap.exit,
bottomImageId = R.mipmap.arrow)
}
}
}
效果如下:

④ 在Anko中使用自定義View
有一天產(chǎn)品讓你畫一個(gè)比較奇特的圓弧,這個(gè)圓弧你必須用自定義View實(shí)現(xiàn),在你實(shí)現(xiàn)了之后,你發(fā)現(xiàn)Anko中卻不能使用,ViewManager并沒有生成自定義View的方法,這時(shí)你傻眼了,辛辛苦苦寫的View在Anko中用不了。別急,下面我們一起來學(xué)習(xí)下如何使用:
第一步:自定義一個(gè)圓弧,這里用很簡(jiǎn)單的一個(gè)例子
定義屬性:這些屬性都可以在Anko的閉包中直接賦值
// 圓弧開始的角度
var startAngle: Float = 0f
// 圓弧結(jié)束的角度
var endAngle: Float = 0f
// 圓弧的背景顏色
@ColorInt
var arcBg: Int = 0
set(value) {
field = value
circlePaint?.color = value
}
// 畫筆的寬度
var paintWidth: Float = 1f
set(value) {
field = value
circlePaint?.strokeWidth = value
rectPaint?.strokeWidth = value
}
畫Rect和Arc:簡(jiǎn)單的實(shí)現(xiàn)下
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawRect(circleRect!!, rectPaint!!)
canvas.drawArc(circleRect!!, startAngle, endAngle - startAngle, true, circlePaint!!)
}
第二步:實(shí)現(xiàn)擴(kuò)展函數(shù),擴(kuò)展函數(shù)主要的還是靠返回的ankoView()來實(shí)現(xiàn),我們看到的CustomCircle(it)中的it就是Context對(duì)象。這樣就直接調(diào)用了自定義View的構(gòu)造函數(shù)。
/**
* 以下都是為了在anko中實(shí)現(xiàn)自定義的CustomCircle,定義的一系列方法
*/
inline fun ViewManager.customCircle(): CustomCircle = customCircle {}
inline fun ViewManager.customCircle(theme: Int = 0, init: CustomCircle.() -> Unit): CustomCircle {
return ankoView({ CustomCircle(it) }, theme, init)
}
inline fun Context.customCircle(): CustomCircle = customCircle {}
inline fun Context.customCircle(theme: Int = 0, init: CustomCircle.() -> Unit): CustomCircle {
return ankoView({ CustomCircle(it) }, theme, init)
}
inline fun Activity.customCircle(): CustomCircle = customCircle {}
inline fun Activity.customCircle(theme: Int = 0, init: CustomCircle.() -> Unit): CustomCircle {
return ankoView({ CustomCircle(it) }, theme, init)
}
第三步:調(diào)用,其實(shí)和button、tv沒什么區(qū)別,看你自定義中的參數(shù)而已
class CustomCircleUI : AnkoComponent<CustomCircleActivity> {
override fun createView(ui: AnkoContext<CustomCircleActivity>) = with(ui) {
linearLayout {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER
lparams(matchParent, matchParent)
verticalLayout {
lparams(width = dip(200), height = dip(200))
backgroundColor = Color.parseColor("#ff9999")
customCircle {
startAngle = 0f
endAngle = 180f
arcBg = Color.WHITE
paintWidth = 2f
}
}
}
}
}
效果:

Anko中Layout部分使用就介紹到這,有感興趣的還希望可以去wiki文檔仔細(xì)閱讀,謝謝
代碼傳送地:README文檔中標(biāo)明了每個(gè)知識(shí)點(diǎn)對(duì)應(yīng)的代碼所在地。
寫在最后
每個(gè)人不是天生就強(qiáng)大,你若不努力,如何證明自己,加油!
Thank You!
--Taonce
如果你覺得這篇文章對(duì)你有所幫助,那么就動(dòng)動(dòng)小手指,長(zhǎng)按下方的二維碼,關(guān)注一波吧~~非常期待大家的加入
