Android UI开发神兵利器—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三个对象,都是很重要的属性。

效果如下:


login

② 使用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对象。

效果如下:


rv

③ 复用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)
        }
    }
}

效果如下:


settin

④ 在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
                }
            }

        }
    }
}

效果:


custome

Anko中Layout部分使用就介绍到这,有感兴趣的还希望可以去wiki文档仔细阅读,谢谢

代码传送地:README文档中标明了每个知识点对应的代码所在地。

写在最后

每个人不是天生就强大,你若不努力,如何证明自己,加油!

Thank You!

--Taonce

如果你觉得这篇文章对你有所帮助,那么就动动小手指,长按下方的二维码,关注一波吧~~非常期待大家的加入

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容