Kotlin-Anko的使用

Anko的簡介

引用Anko的GitHub主頁上面的解釋:

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開發程序更加簡單和快速而生成的一個Kotlin庫,它可以使您的代碼清晰、易讀,並且它可以讓您忘記粗糙的Java Android SDK。

Anko目前主要用於:Layout佈局、SQLite數據庫和Coroutines協程三個方面。

接下來我們主要交流的是Layout方面的知識。

引入Anko和遇到的問題

添加Anko的依賴: implementation "org.jetbrains.anko:anko:$anko_version"

這時發現有爆紅的地方了:提示v7包和v4包版本不一致,這就很納悶了,我都沒用私自添加刪除v4包,怎麼會出現問題呢,接着我就去libraries找原因了,原來是Anko也引入了v4的包,而且版本是27.1.1,我新建工程的編譯版本是28.0.0,小夥伴們要注意了。

解決:排除anko包中的v4包

implementation("org.jetbrains.anko:anko:$anko_version") {
        exclude module: 'support-v4'
}

使用Anko,從四個點介紹下如何使用Anko以及遇到的問題

① 實現一個簡單的登錄界面
既然是使用Anko,那麼當然是要拋棄xml佈局文件咯,也就不用寫setContentView()來綁定佈局文件了,可以直接在onCreate()方法裏面調用我們自己寫的AnkoComponent類的setContentView()綁定activity就行了,這種寫法是比較推薦的一種,還有一種就是直接把verticalLayout{}寫在onCreate()裏面,但是不推薦,這樣會造成Activity類的代碼冗餘。下面來看看如何實現這麼一個簡單的佈局:

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 {
            // 這個gravity對應的就是gravity,而在lparams閉包中,gravity對應的是layout_gravity
            gravity = Gravity.CENTER
            // 佈局的屬性params在閉包裏面的lparams中設置,但是button、TextView等控件的屬性params是在閉包外的lparams設置
            lparams(matchParent, matchParent)
            editText {
                hint = "userName"
                gravity = Gravity.CENTER
                // 監聽輸入框輸入情況
                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
                // 點擊事件
                onClick {
                    // anko封裝的intent攜帶值跳轉
                    startActivity<ListActivity>("aulton" to "aulton")
                }
                // 長按事件
                onLongClick {
                    toast("Long Click")
                }
            }.lparams(dip(250), dip(50))

            button("setting") {
                backgroundColor = Color.parseColor("#FF7777")
                alpha = 0.5f
                // 點擊事件
                onClick {
                    // anko封裝的intent攜帶值跳轉
                    startActivity<SettingActivity>("aulton" to "aulton")
                }
            }.lparams(dip(250), dip(50)) {
                topMargin = dip(16)
            }

            button("custom_view") {
                backgroundColor = Color.parseColor("#FF7777")
                alpha = 0.5f
                // 點擊事件
                onClick {
                    // anko封裝的intent攜帶值跳轉
                    startActivity<CustomCircleActivity>("aulton" to "aulton")
                }
            }.lparams(dip(250), dip(50)) {
                topMargin = dip(16)
            }
        }
    }
}

  • 這裏我們用的都是大家常見的一些佈局和控件,verticalLayout就是oritentation=verticalLinearLayout

  • 控件和佈局的一些屬性需要注意下,比如verticalLayout裏面的gravity = Gravity.CENTER對應的就是xml中的gravity,如果出現在lparams閉包中的gravity = Gravity.CENTER指的就是layout_gravity屬性了。千萬要分清。

  • AnkoComponentcreateView()其實是有返回值的

    interface AnkoComponent<in T> {
        fun createView(ui: AnkoContext<T>): View
    }
    
    

    返回的是一個View對象,這裏我使用with(ui)來實現自動返回。你也可以使用let()或者apply()等作用域函數。你可以從ui: AnkoContext<T>對象中拿到ContextActivityView三個對象,都是很重要的屬性。

效果如下:


② 使用Anko實現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 {
            // 下拉監聽事件
            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所有的屬性:manageradapter都可以直接在閉包裏面設置。

接下來看看適配器中的ItemView如何用Anko實現:

class MyAdapter(private val context: Context,
                private val mData: MutableList<String>) : RecyclerView.Adapter<MyAdapter.MyHolder>() {

    // 創建Holder對象
    override fun onCreateViewHolder(p0: ViewGroup, p1: Int): MyHolder {
        // 根據anko生成itemView,並且給itemView的tag賦值,從而取得MyHolder
        return AdapterUI().createView(AnkoContext.create(context, p0)).tag as MyHolder
    }

    override fun getItemCount(): Int {
        return mData.size
    }

    // 綁定holder,呈現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)) {
                        // 設置外邊距
                        topMargin = dip(10)
                    }
                }
            }
            // 返回itemView,並且通過tag生成MyHolder
            item_view.tag = MyHolder(item_view, tv_name = tv_name!!)
            return item_view
        }
    }
}

其實這裏主要使用到的就是View對象的tag屬性,將ItemViewtagHolder綁定在一起,這樣我們AnkoComponentcreateView()返回ItemView的同時也把Holder生成並返回了,就可以在AdapteronCreateViewHolder()方法中拿到Holder對象。

效果如下:


③ 複用AnkoView

在日常開發中我們會遇到這樣的情形,類似於通用的設置界面,所有的條目都是很類似的,只不過文字或者icon不一樣,如果我們用rv來實現,難免覺得條目太少,不划算,但是每個條目都是自己寫一遍,又會覺得太繁瑣,這時候Anko就會幫助我們簡化很大的代碼,下面一起來看看:

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")
        // 設置整體的寬高和外邊距
        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
            }
        }
    }
}

首先定義一個方法,方法包含了item的高度、上下外邊距、頭部icon、頭部文字、尾部icon和ViewManager7個參數。方法的內部實現採用Anko的DSL(領域特定語言)語言實現。其中參數ViewManager是我們前面提到的AnkoComponent的父類,它是這個方法的主要參數,因爲在Anko實現的一系列View都是ViewManager的擴展函數。想複用的話,直接調用這個方法就行了,ForExample:

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

有一天產品讓你畫一個比較奇特的圓弧,這個圓弧你必須用自定義View實現,在你實現了之後,你發現Anko中卻不能使用,ViewManager並沒有生成自定義View的方法,這時你傻眼了,辛辛苦苦寫的View在Anko中用不了。別急,下面我們一起來學習下如何使用:

第一步:自定義一個圓弧,這裏用很簡單的一個例子

定義屬性:這些屬性都可以在Anko的閉包中直接賦值

// 圓弧開始的角度
var startAngle: Float = 0f
// 圓弧結束的角度
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
    }

RectArc:簡單的實現下

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.drawRect(circleRect!!, rectPaint!!)
    canvas.drawArc(circleRect!!, startAngle, endAngle - startAngle, true, circlePaint!!)
}

第二步:實現擴展函數,擴展函數主要的還是靠返回的ankoView()來實現,我們看到的CustomCircle(it)中的it就是Context對象。這樣就直接調用了自定義View的構造函數。

/**
 * 以下都是爲了在anko中實現自定義的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)
}

第三步:調用,其實和buttontv沒什麼區別,看你自定義中的參數而已

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文檔仔細閱讀,謝謝

代碼傳送地:README文檔中標明瞭每個知識點對應的代碼所在地。

寫在最後

每個人不是天生就強大,你若不努力,如何證明自己,加油!

Thank You!

--Taonce

如果你覺得這篇文章對你有所幫助,那麼就動動小手指,長按下方的二維碼,關注一波吧~~非常期待大家的加入

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章