anko
Anko 是一个用 Kotlin 写的Android DSL (Domain-Specific Language)。 内部提供很多工具,详情可点https://github.com/Kotlin/anko
在这里我们主要涉及的是anko的layout部分,长久以来,Android视图都是用XML来完成布局的。用xml写布局有下面这些缺点:
- 不是类型安全的;
- 不是null-safe;
- 强迫你对每个layout编写几乎相同的代码;
- 在设备上解析XML费电费CPU;
- 最惨的是代码不可复用
所以,如果可以,我们可以尝试用anko来绘制布局。
环境配置
因为anko版本发布的并不是很多,用户量也不是很多,所有网上能找到的资源是非常少的,所以配置的时候总会出现许许多多的问题。现在android studio默认已经集成了kotlin了,我们需要配置的就是anko support,也就是anko preview,用来预览anko写的ui布局的工具。
插件安装
as联网下载
我们可以从android studio内置的下载器中下载,从Preference->Plugins中搜索Anko Support,并进行下载。
[图片上传失败...(image-8ed987-1542512015800)]
官网下载导入
我们也能够从官网中下载指定的anko support版本,点击https://plugins.jetbrains.com/plugin/7734-anko-support,选择指定版本进行下载,然后在as导入。
[图片上传失败...(image-770d0a-1542512015800)]
在android选择项目的页面,选择configuration->plugins->install plugin from disk,选择相应的plugin包导入。
可能出现的问题
目前官网最新的anko support插件版本是0.10.5,如果我们的as版本是3.0,那么还是将会出现anko preview不能预览的情况,报错情况为
Anko Support threw an uncaught NoSuchMethodError
这个是因为as3.0的问题不支持anko support,这个时候我们只需要更新as的版本到3.1之后,anko support就能够使用了。
开始使用anko
首先需要先引入anko依赖库
implementation "org.jetbrains.anko:anko:0.10.5"
这个是anko给我们集成的一个包含anko所有功能的库,包含了
- anko commons: 一些常用的扩展函数,包括intent、dialog、log、resources等
- ankoLayout: anko布局
- ankoSqlite: anko优化的数据库操作
- anko Coroutines: anko对于kotlin协程的扩展
如果我们需要大部分的anko的功能,我们可以直接这么引入,否则我们需要单独引入每一个功能的依赖。
dependencies {
// Anko Commons
implementation "org.jetbrains.anko:anko-commons:$anko_version"
// Anko Layouts
implementation "org.jetbrains.anko:anko-sdk25:$anko_version" // sdk15, sdk19, sdk21, sdk23 are also available
implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version"
// Coroutine listeners for Anko Layouts
implementation "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
implementation "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version"
// Anko SQLite
implementation "org.jetbrains.anko:anko-sqlite:$anko_version"
}
我们只需要从中引入我们所必须的库就ok了。在这里我们需要注意的是anko-sdk的引入需要和我们build.gradle中的minSdkVersion做下适配。
org.jetbrains.anko:anko-sdk15 : 15 <= minSdkVersion < 19
org.jetbrains.anko:anko-sdk19 : 19 <= minSdkVersion < 21
org.jetbrains.anko:anko-sdk21 : 21 <= minSdkVersion < 23
org.jetbrains.anko:anko-sdk23 : 23 <= minSdkVersion < 25
org.jetbrains.anko:anko-sdk25 : 25 <= minSdkVersion
如果没有按照要求做sdk适配,那么一旦运行在不符合的版本,将会直接崩溃~切记
AnkoComponent
我们可以在activity的onCreate、fragemnt的onCreateView中直接绘制ui,但是这样就让ui绘制代码�与activity、fragment代码有了耦合了。所有我们可以将Anko代码定义到另外一个class中,作为对应的UiClass,这就需要AnkoComponent。
定义UIClass
class MemberCenterActivityUI : AnkoComponent<MemberCenterActivity> {
override fun createView(ui: AnkoContext<MemberCenterActivity>) : View = with(ui){
}
在我们定义了相应的uiclass之后,我们需要实现其中的oncreateView方法,这个类的返回就是一个View,就是我们anko绘制的布局。
添加上with(ui),是为了让dsl代码块能够以this的形式持有AnkoContext,这个AnkoContext是一个非常有用的类
val ctx: Context
val owner: T
val view: View
ctx就是上下文信息,owner就是uiclass依附的类的实例,在这个例子里面指的是MemberCenterActivity,我们可以通过owner调用MemberCenterActivity的任意public方法。
绘制布局
所有布局
relativeLayout {
imageView {
adjustViewBounds = true
scaleType = ImageView.ScaleType.CENTER_CROP
imageResource = R.drawable.bg_members
}.lparams(width = matchParent, height = matchParent)
statusBar = view {
id = statusBarHolder
}.lparams {
height = statusBarHeight(ctx)
width = matchParent
}
toolbar {
id = toolbarId
backgroundColor = R.color.color_00000000
imageView {
imageResource = R.drawable.ic_arrow_back_white
onClick {
owner.finish()
}
}.lparams(height = wrapContent, width = wrapContent) {
padding = dip(5)
}
textView("会员中心") {
textColor = R.color.color_ffffff
textSize = R.dimen.dimens_18sp.toFloat()
}.lparams(width = wrapContent, height = wrapContent) {
gravity = Gravity.CENTER
}
}.lparams(height = dip(50), width = matchParent) {
below(statusBar)
}
memberRv = recyclerView {
itemAnimator.changeDuration = 0
layoutManager = LinearLayoutManager(context)
adapter = MemberCenterAdapter(context, null)
memberAdapter = adapter as MemberCenterAdapter
}.lparams(width = matchParent, height = matchParent) {
below(toolbarId)
}
}
每一个anko布局,最多只能有一个根节点,在这个例子中最外层的根节点就是relativelayout,这个组件的名称都是以小写字母开头的,就是anko给java的View多包装了一层,以便在dsl使用。
imageView {
....
}.lparams(width = matchParent, height = matchParent){
.....
}
控件直接包含的区域是添加控件的直接参数的,比如说imageView的src,scaleType,TextView的text,textSize,textColor等。而lparams是用来定义组件的大小,布局、位置属性的,包括height、width、gravity等等。
当然,我们也可以不指定lparams,这样的话,它会有默认的宽高,即wrap_content。
inline fun <T: View> T.lparams(
width: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
height: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT
): T {
val layoutParams = FrameLayout.LayoutParams(width, height)
this@lparams.layoutParams = layoutParams
return this
}
整个imageView域是有返回值的,返回的就是这个imageView实体,我们也可以理解成这个域创建了imageView实体并返回。
anko扩展View
当我们想要用自定义View或者anko没有支持的View,我们可以扩展ankoView
inline fun ViewManager.mapView() = mapView(theme = 0) {}
inline fun ViewManager.mapView(init: MapView.() -> Unit): MapView {
return ankoView({ MapView(it) }, theme = 0, init = init)
}
这个mapView就是我们想要扩展的View,这个是官网给的例子,下面我们来扩展fresco的simpledraweeView
inline fun ViewManager.simpleDraweeView(theme: Int = 0) = simpleDraweeView(theme) {}
inline fun ViewManager.simpleDraweeView(theme: Int = 0, init: SimpleDraweeView.() -> Unit) : SimpleDraweeView {
return ankoView({ SimpleDraweeView(it) }, theme, init)
}
这样我们就能在anko dsl中直接使用simpleDraweeView了
onclick
anko中已经给我们添加了onclick方法,我们可以在每个View中添加onClick,并且指定其实现,anko将会给这个View设置listener
fun android.view.View.onClick(
context: CoroutineContext = UI,
handler: suspend CoroutineScope.(v: android.view.View?) -> Unit
) {
setOnClickListener { v ->
launch(context) {
handler(v)
}
}
}
一些小点
- 用了anko之后,我们如果需要对于id的诉求只有一个,那就是在relativelayout中指定位置时,需要通过id,才指定below、above、left、right的位置,对于id的设置,我们需要自己定义Id的int值,我们得保证它不会重复。
- 对于imageView,不再有src属性,因为anko内部其实是动态代码创建view,所有对于这个src,替代的就是在代码中指定的imageResouce
- 对于color,需要的就是color的值,而非项目中的colorId
- 对于textView,它的textSize指定为f,而非sp
- 如果我们想要用recyclerView,那么我们需要引入anko的依赖库,或者直接扩展ankoView
- 对于dp计算,anko内部给我们实现了dip方法计算。
anko preivew
对于anko support,虽然可能它的代码效率比较高,但是它在预览的时候并没有办法做到像在xml里面实时预览,只有在代码build之后才能够看到预览页面,而且每次修改都必须要进行rebuild,这个是一个非常让人无法接受的点,刚开始效率肯定会低,但是随着代码的熟练,效率还是会有所提升的,毕竟anko的ui绘制效率要笔xml高。可以通过command+f9进行build。
ankoComponent元素的调用
我们免不了需要在activity,framgent等类中更新uiClass的元素的状态。所以了解uiClass与activity,fragment及viewHolder的绑定及调用还是有必要的。
绑定
activity
activity绑定ankoComponent可以通过下面这种方式来绑定
MemberCenterActivityUI().setContentView();
fragment
val view = MemberCenterFragmentUI().createView(AnkoContext.create(ctx, MemberCenterFragment()))
在onCreateView中返回这个View,就和fragment进行绑定了。
viewHolder
MemberCenterItemUI().createView(AnkoContext.create(context, parent)
自定义View
在自定义View中,对于anko来说是一个比较不方便的使用,因为在anko support的支持预览的前提是使用AnkoComponent,而自定义View的View绑定却不通过AnkoComponent,也有可能是我现在没发现,目前发现的支持绑定的方式为:
constructor(context: Context) : super(context) {
initView()
}
private fun initView() = AnkoContext.createDelegate(this).apply {
...init anko view
}
在java的自定义View中,是通过构造函数调用inflate方法,来将xml设置到该自定义View中。
在anko中,利用AnkoContext.createDelegate(this).apply来替代inflate。
我们可以先通过AnkoComponent来预览界面,然后预览完成之后再将anko代码放在AnkoContext.createDelegate(this).apply中。
调用
对于三者的调用其实都是一致的,我们只需要缓存AnkoComponent类的实例就能够通过这个实例引用其内部的ui组件。
//in activity
mainUI = MemberCenterActivityUI()
mainUI.setContentView(this)
TextUtils.isEmpty(chargeTip?.chargeButtonDesc).yes {
mainUI.bottomMemberOpenDesc.visibility = View.GONE
}.no {
mainUI.bottomMemberOpenDesc.visibility = View.VISIBLE
mainUI.bottomMemberOpenDesc.text = chargeTip?.chargeButtonDesc
}
TextUtils.isEmpty(chargeTip?.chargeButtonUrl).yes {
mainUI.btnOpenViewIcon.visibility = View.GONE
}.no {
mainUI.btnOpenViewIcon.visibility = View.VISIBLE
KKGifPlayer
.with(this)
.load(chargeTip?.chargeButtonUrl)
.playPolicy(KKGifPlayer.PlayPolicy.Auto_Always)
.into(openViewIcon)
}