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=vertical
的LinearLayout
。 -
控件和布局的一些属性需要注意下,比如
verticalLayout
里面的gravity = Gravity.CENTER
对应的就是xml中的gravity
,如果出现在lparams
闭包中的gravity = Gravity.CENTER
指的就是layout_gravity
属性了。千万要分清。 -
AnkoComponent
的createView()
其实是有返回值的interface AnkoComponent<in T> { fun createView(ui: AnkoContext<T>): View }
返回的是一个
View
对象,这里我使用with(ui)
来实现自动返回。你也可以使用let()
或者apply()
等作用域函数。你可以从ui: AnkoContext<T>
对象中拿到Context
、Activity
和View
三个对象,都是很重要的属性。
效果如下:
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
所有的属性:manager
、adapter
都可以直接在闭包里面设置。
接下来看看适配器中的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
属性,将ItemView
的tag
和Holder
绑定在一起,这样我们AnkoComponent
的createView()
返回ItemView
的同时也把Holder
生成并返回了,就可以在Adapter
的onCreateViewHolder()
方法中拿到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和ViewManager
7个参数。方法的内部实现采用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
}
画Rect
和Arc
:简单的实现下
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)
}
第三步:调用,其实和button
、tv
没什么区别,看你自定义中的参数而已
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
如果你觉得这篇文章对你有所帮助,那么就动动小手指,长按下方的二维码,关注一波吧~~非常期待大家的加入
专注Kotlin和Android知识的公众号