Android UI开发神兵利器—Kotlin Anko

作者:Taonce

博客:https://www.jianshu.com/u/05f6d6641bcf

引用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包

1implementation("org.jetbrains.anko:anko:$anko_version") {
2        exclude module: 'support-v4'
3}
使用Anko,从四个点介绍下如何使用Anko以及遇到的问题

① 实现一个简单的登录界面
既然是使用Anko,那么当然是要抛弃xml布局文件咯,也就不用写setContentView()来绑定布局文件了,可以直接在onCreate()方法里面调用我们自己写的AnkoComponent类的setContentView()绑定activity就行了,这种写法是比较推荐的一种,还有一种就是直接把verticalLayout{}写在onCreate()里面,但是不推荐,这样会造成Activity类的代码冗余。下面来看看如何实现这么一个简单的布局:

 1class AnkoActivity : AppCompatActivity() {
 2
 3    override fun onCreate(savedInstanceState: Bundle?) {
 4        super.onCreate(savedInstanceState)
 5        // AnkoComponent和Activity相互绑定
 6        MainUI().setContentView(this@AnkoActivity)
 7    }
 8}
 9
10class MainUI : AnkoComponent<AnkoActivity> {
11    override fun createView(ui: AnkoContext<AnkoActivity>) = with(ui) {
12        verticalLayout {
13            // 这个gravity对应的就是gravity,而在lparams闭包中,gravity对应的是layout_gravity
14            gravity = Gravity.CENTER
15            // 布局的属性params在闭包里面的lparams中设置,但是button、TextView等控件的属性params是在闭包外的lparams设置
16            lparams(matchParent, matchParent)
17            editText {
18                hint = "userName"
19                gravity = Gravity.CENTER
20                // 监听输入框输入情况
21                addTextChangedListener(object : TextWatcher {
22                    override fun afterTextChanged(s: Editable?) {
23                    }
24
25                    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
26                    }
27
28                    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
29                    }
30                })
31            }.lparams(width = dip(250), height = 200)
32
33            editText {
34                hint = "password"
35                top = 20
36                gravity = Gravity.CENTER
37            }.lparams(width = dip(250), height = 200)
38
39            button("list") {
40                backgroundColor = Color.parseColor("#FF9999")
41                alpha = 0.5f
42                // 点击事件
43                onClick {
44                    // anko封装的intent携带值跳转
45                    startActivity<ListActivity>("aulton" to "aulton")
46                }
47                // 长按事件
48                onLongClick {
49                    toast("Long Click")
50                }
51            }.lparams(dip(250), dip(50))
52
53            button("setting") {
54                backgroundColor = Color.parseColor("#FF7777")
55                alpha = 0.5f
56                // 点击事件
57                onClick {
58                    // anko封装的intent携带值跳转
59                    startActivity<SettingActivity>("aulton" to "aulton")
60                }
61            }.lparams(dip(250), dip(50)) {
62                topMargin = dip(16)
63            }
64
65            button("custom_view") {
66                backgroundColor = Color.parseColor("#FF7777")
67                alpha = 0.5f
68                // 点击事件
69                onClick {
70                    // anko封装的intent携带值跳转
71                    startActivity<CustomCircleActivity>("aulton" to "aulton")
72                }
73            }.lparams(dip(250), dip(50)) {
74                topMargin = dip(16)
75            }
76        }
77    }
78}
  • 这里我们用的都是大家常见的一些布局和控件,verticalLayout就是oritentation=verticalLinearLayout

  • 控件和布局的一些属性需要注意下,比如verticalLayout里面的gravity = Gravity.CENTER对应的就是xml中的gravity,如果出现在lparams闭包中的gravity = Gravity.CENTER指的就是layout_gravity属性了。千万要分清。

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

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

    返回的是一个View对象,这里我使用with(ui)来实现自动返回。你也可以使用let()或者apply()等作用域函数。你可以从ui: AnkoContext<T>对象中拿到ContextActivityView三个对象,都是很重要的属性。

效果如下:

login

② 使用Anko实现RV列表

要想使用RecyclerView你必须添加新的依赖:

1//    RecyclerView-v7
2implementation "org.jetbrains.anko:anko-recyclerview-v7:$anko_version"
3implementation "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version"

首先写出rv+swipeRefreshLayout布局:

 1class ListUI : AnkoComponent<ListActivity> {
 2    override fun createView(ui: AnkoContext<ListActivity>) = with(ui) {
 3        // 下拉刷新控件
 4        swipeRefreshLayout {
 5            // 下拉监听事件
 6            setOnRefreshListener {
 7                toast("refresh")
 8                isRefreshing = false
 9            }
10            // rv
11            recyclerView {
12                layoutManager = LinearLayoutManager(ui.ctx)
13                lparams(width = matchParent, height = matchParent)
14                adapter = MyAdapter(ui.ctx, mutableListOf("1",
15                        "2", "3", "4"))
16            }
17        }
18    }
19}

rv所有的属性:manageradapter都可以直接在闭包里面设置。

接下来看看适配器中的ItemView如何用Anko实现:

 1class MyAdapter(private val context: Context,
 2                private val mData: MutableList<String>) : RecyclerView.Adapter<MyAdapter.MyHolder>() {
 3
 4    // 创建Holder对象
 5    override fun onCreateViewHolder(p0: ViewGroup, p1: Int): MyHolder {
 6        // 根据anko生成itemView,并且给itemView的tag赋值,从而取得MyHolder
 7        return AdapterUI().createView(AnkoContext.create(context, p0)).tag as MyHolder
 8    }
 9
10    override fun getItemCount(): Int {
11        return mData.size
12    }
13
14    // 绑定holder,呈现UI
15    override fun onBindViewHolder(holder: MyHolder, p1: Int) {
16        holder.tv_name.text = mData[p1]
17    }
18
19    class MyHolder(view: View, val tv_name: TextView) : RecyclerView.ViewHolder(view)
20
21    class AdapterUI : AnkoComponent<ViewGroup> {
22        override fun createView(ui: AnkoContext<ViewGroup>): View {
23            var tv_name: TextView? = null
24            val item_view = with(ui) {
25                relativeLayout {
26                    lparams(width = matchParent, height = dip(50))
27                    tv_name = textView {
28                        textSize = 12f
29                        textColor = Color.parseColor("#FF0000")
30                        backgroundColor = Color.parseColor("#FFF0F5")
31                        gravity = Gravity.CENTER
32                    }.lparams(width = matchParent, height = dip(50)) {
33                        // 设置外边距
34                        topMargin = dip(10)
35                    }
36                }
37            }
38            // 返回itemView,并且通过tag生成MyHolder
39            item_view.tag = MyHolder(item_view, tv_name = tv_name!!)
40            return item_view
41        }
42    }
43}

其实这里主要使用到的就是View对象的tag属性,将ItemViewtagHolder绑定在一起,这样我们AnkoComponentcreateView()返回ItemView的同时也把Holder生成并返回了,就可以在AdapteronCreateViewHolder()方法中拿到Holder对象。

效果如下:

rv

③ 复用AnkoView

在日常开发中我们会遇到这样的情形,类似于通用的设置界面,所有的条目都是很类似的,只不过文字或者icon不一样,如果我们用rv来实现,难免觉得条目太少,不划算,但是每个条目都是自己写一遍,又会觉得太繁琐,这时候Anko就会帮助我们简化很大的代码,下面一起来看看:

 1fun myLinearLayout(viewManager: ViewManager,
 2                   itemHeight: Int = 40,
 3                   itemMarginTop: Int = 0,
 4                   itemMarginBottom: Int = 0,
 5                   headImageId: Int = 0,
 6                   headTextRes: String,
 7                   bottomImageId: Int = 0) = with(viewManager) {
 8    linearLayout {
 9        orientation = LinearLayout.HORIZONTAL
10        leftPadding = dip(16)
11        rightPadding = dip(16)
12        backgroundColor = Color.parseColor("#FFFFFF")
13        // 设置整体的宽高和外边距
14        lparams(width = matchParent, height = dip(itemHeight)) {
15            setMargins(0, itemMarginTop, 0, itemMarginBottom)
16        }
17        // 左边图片
18        if (headImageId != 0) {
19            imageView(headImageId) {
20                scaleType = ImageView.ScaleType.FIT_XY
21            }.lparams(width = dip(30), height = dip(30)) {
22                gravity = Gravity.CENTER
23            }
24        }
25        // 左边字体
26        textView(headTextRes) {
27            gravity = Gravity.CENTER_VERTICAL
28        }.lparams(width = matchParent, height = matchParent, weight = 1f) {
29            if (headImageId != 0) {
30                marginStart = dip(16)
31            }
32        }
33        // 右边图片
34        if (bottomImageId != 0) {
35            imageView(bottomImageId) {
36                scaleType = ImageView.ScaleType.FIT_XY
37            }.lparams(width = dip(30), height = dip(30)) {
38                gravity = Gravity.CENTER
39            }
40        }
41    }
42}

首先定义一个方法,方法包含了item的高度、上下外边距、头部icon、头部文字、尾部icon和ViewManager7个参数。方法的内部实现采用Anko的DSL(领域特定语言)语言实现。其中参数ViewManager是我们前面提到的AnkoComponent的父类,它是这个方法的主要参数,因为在Anko实现的一系列View都是ViewManager的扩展函数。想复用的话,直接调用这个方法就行了,ForExample:

 1class SettingUI : AnkoComponent<SettingActivity> {
 2    override fun createView(ui: AnkoContext<SettingActivity>) = with(ui) {
 3        verticalLayout {
 4            myLinearLayout(viewManager = this,
 5                    headImageId = R.mipmap.setting,
 6                    headTextRes = "Setting",
 7                    bottomImageId = R.mipmap.arrow,
 8                    itemMarginBottom = 8,
 9                    itemMarginTop = 8)
10            myLinearLayout(viewManager = this,
11                    headTextRes = "MyInfo",
12                    bottomImageId = R.mipmap.arrow,
13                    itemMarginBottom = 8)
14            myLinearLayout(this,
15                    headTextRes = "Exit",
16                    headImageId = R.mipmap.exit,
17                    bottomImageId = R.mipmap.arrow)
18        }
19    }
20}

效果如下:

settin

④ 在Anko中使用自定义View

有一天产品让你画一个比较奇特的圆弧,这个圆弧你必须用自定义View实现,在你实现了之后,你发现Anko中却不能使用,ViewManager并没有生成自定义View的方法,这时你傻眼了,辛辛苦苦写的View在Anko中用不了。别急,下面我们一起来学习下如何使用:

第一步:自定义一个圆弧,这里用很简单的一个例子

定义属性:这些属性都可以在Anko的闭包中直接赋值

 1// 圆弧开始的角度
 2var startAngle: Float = 0f
 3// 圆弧结束的角度
 4var endAngle: Float = 0f
 5// 圆弧的背景颜色
 6@ColorInt
 7var arcBg: Int = 0
 8    set(value) {
 9        field = value
10        circlePaint?.color = value
11    }
12// 画笔的宽度
13var paintWidth: Float = 1f
14    set(value) {
15        field = value
16        circlePaint?.strokeWidth = value
17        rectPaint?.strokeWidth = value
18    }

RectArc:简单的实现下

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

第二步:实现扩展函数,扩展函数主要的还是靠返回的ankoView()来实现,我们看到的CustomCircle(it)中的it就是Context对象。这样就直接调用了自定义View的构造函数。

 1/**
 2 * 以下都是为了在anko中实现自定义的CustomCircle,定义的一系列方法
 3 */
 4inline fun ViewManager.customCircle(): CustomCircle = customCircle {}
 5inline fun ViewManager.customCircle(theme: Int = 0, init: CustomCircle.() -> Unit): CustomCircle {
 6    return ankoView({ CustomCircle(it) }, theme, init)
 7}
 8
 9inline fun Context.customCircle(): CustomCircle = customCircle {}
10inline fun Context.customCircle(theme: Int = 0, init: CustomCircle.() -> Unit): CustomCircle {
11    return ankoView({ CustomCircle(it) }, theme, init)
12}
13
14inline fun Activity.customCircle(): CustomCircle = customCircle {}
15inline fun Activity.customCircle(theme: Int = 0, init: CustomCircle.() -> Unit): CustomCircle {
16    return ankoView({ CustomCircle(it) }, theme, init)
17}

第三步:调用,其实和buttontv没什么区别,看你自定义中的参数而已

 1class CustomCircleUI : AnkoComponent<CustomCircleActivity> {
 2    override fun createView(ui: AnkoContext<CustomCircleActivity>) = with(ui) {
 3        linearLayout {
 4            orientation = LinearLayout.VERTICAL
 5            gravity = Gravity.CENTER
 6            lparams(matchParent, matchParent)
 7            verticalLayout {
 8                lparams(width = dip(200), height = dip(200))
 9                backgroundColor = Color.parseColor("#ff9999")
10                customCircle {
11                    startAngle = 0f
12                    endAngle = 180f
13                    arcBg = Color.WHITE
14                    paintWidth = 2f
15                }
16            }
17
18        }
19    }
20}

效果:

custome

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

近期文章:

今日问题:

大家项目都转战到Kotlin了木有?

打卡格式:

打卡 X 天,答:xxx 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值