目录
运行时app打开某个页面,必须做的事情有:
1.先把xml文件加载到内存
2.解析xml标签,读取布局
3.渲染绘制各层级View到屏幕
而使用代码直接动态绘制页面布局的话,就不需要这1、2两个耗时步骤。
实际测试对比,简单单层布局的页面就是20ms->2ms左右的巨大提升。如果是复杂或层级更深的页面,提升更大。
有利就有弊,不足的是:
1.代码动态布局,代码可读性较差
2.页面布局有变动,维护难度较大
3.代码量较大,同一类里编写,代码行数容易突破750行
4.界面无法预览,为了提升运行效率,而牺牲了开发效率
高阶函数是一种特殊的函数:它的参数或者返回值是另一个函数。比如平时开发用的传参回调,可以省去回调接口、实现的代码编写:
private fun exampleFun(emit: (bean: ImEventBean) -> Unit) {
...
...
val result = GsonUtils.fromJson(json, ImEventBean::class.java)
emit(result)
}
exampleFun {
EventBusUtils.post(it)
}
或者如:常用的let函数
public inline funT.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) }
简单理解,就是编译时,把函数体复制粘贴到函数调用处,减少一次方法栈的开辟(局部变量表、操作栈等附带开支)
主要运用场景,用于配合高阶函数/Lambda的使用。其中与Lambda的配合,主要是为了使其允许局部返回,如:
return@let
Kotlin 中的 Lambda函数,可以访问接收者的非私有成员。如:
info.apply{
updateName(info.nickName)//获取非私有成员
info.age = mInput.text.toString()//设置非私有成员
}
DSL(domain specific language),全称“特定领域语言”,DSL专注特定领域的操作/表达,具有结构性。例如HTML、SQL也是一种具有结构性DSL语言
基于Kotlin 自带接收者的lambda(&高阶函数)的特性,结合DSL可读化的结构性质,可以对动态布局代码进行可读性的优化,实践举例如下:
ConstraintLayout {
layout_width = match_parent
layout_height = match_parent
ImageView {
layout_id = "ivBack"
layout_width = 28
layout_height = 28
margin_start = 16
margin_top = 18
src = R.drawable.ic_back
start_toStartOf = parent_id
top_toTopOf = parent_id
onClick = { onBackClick() }
}
TextView {
layout_width = wrap_content
layout_height = wrap_content
text = "commit"
textSize = 16f
textStyle = bold
align_vertical_to = "ivBack"
center_horizontal = true
}
}
此处仅举例,具体以demo&lib_dsl为准,DemoGit
inline fun ViewGroup.TextView(
style: Int? = null,
autoAdd: Boolean = true,
init: AppCompatTextView.() -> Unit
): TextView {
val textView =
if (style != null) AppCompatTextView(ContextThemeWrapper(context, style))
else AppCompatTextView(context)
return textView.apply(init).also { if (autoAdd) addView(it) }
}
然后就可以这么用

inline var View.layout_width: Number
get() {
return 0
}
set(value) {
val w = if (value.dp > 0) value.dp else value.toInt()
val h = layoutParams?.height ?: 0
updateLayoutParams {
width = w
height = h
}
}
val wrap_content = ViewGroup.LayoutParams.WRAP_CONTENT val match_parent = ViewGroup.LayoutParams.MATCH_PARENT
然后就可以在kt代码中这么用

因为DSL的结构化构建布局,我们可以直接声明成员变量,构建view时直接赋值,也可以后续需要时,再通过父view.findViewById()获取
lateinit var btnMenu: View
...
mRootView = context.ConstraintLayout {
...
// 直接赋值
btnMenu = TextView {
//声明id 后续再findViewById()
layout_id = "tvMenu"
layout_width = wrap_content
layout_height = wrap_content
textSize = 16F
text = "菜单"
}
...
}
将动态布局代码独立为一个类,并在其中使用DSL编写布局。然后再在需要的地方进行初始化、使用
/** 布局dsl 等价于 viewBinding文件 */
private val contentViewDsl by lazy { SecondActivityDslLayout().inflate(this) }
val beforeMills = System.currentTimeMillis()
// 懒加载,setView
setContentView(contentViewDsl.getRoot())
val totalMills = System.currentTimeMillis() - beforeMills
Log.d(this.javaClass.simpleName, "================= 页面构建耗时:${totalMills}ms")
// 类似ViewBinding使用控件
contentViewDsl.tvTitle.text = "耗时:${totalMills}ms"
最终运行效果与xml一致,可以运行代码查看。
与xml相比首次渲染布局dsl耗时更久。
我们通过Android Studio工具,查看DSL布局.kt文件 编译后的情况,发现仅Demo简单的页面,编译后的Java代码已经是几千行。其中一部分原因是扩展函数使用了inline关键字,另一部分原因是kotlin自身的特性。
以上Demo是不涉及 点击或者变化监听 的纯UI布局代码,如果是正常业务的页面,那么生成的Java类文件将会是非常庞大的,后期依然是要被优化的。


1.使用DSL代码动态布局,解决了普通代码可读性差的问题,可读性与xml 基本持平
2.页面布局有变动,维护难度降低,利于扩展
3.编写代码量较小,界面绘制效率大大优于xml
4.界面无法预览是硬伤,为了提升运行效率,而牺牲了开发效率,建议后期不需兼顾低版本用户时,切换到Jetpack Compose
5.前期DSL扩展库编写需要耗费较多时间,实际使用的话推广难度较大
6.DSL布局.kt 代码转化为Java代码,代码量随着页面复杂度增加而增加
总而言之,等允许放弃5.0以下的用户的时候,直接上Jetpack Compose吧。