荐 DSL实战:仿Flutter代码布局实战
前言
我的第一份IT工作是Web前端,转眼已过去8年,之前在学习Flutter的时候情不自禁想起了当年苦逼的div生活,之后还略微研究了一下JetPack Compose。这两个库都是代码实现GUI,关于JetPack Compose的用法一直颇有争议,有人说在Android上,使用代码布局是技术的倒退,这一点我有一些自己的看法:
- Android把布局和代码分离,必须说非常有眼光,相比其他平台,例如ios,Android开发者非常幸福。
- 由于开发者对于App的性能要求越来越苛刻,使用XML布局,每次都需要XML解析成对应的View,苛刻的开发者开始寻求更优的运行速度,于是诞生了更多的GUI相关的库。
目前在布局方面最流行的优化方法有两种:
- 以JetPack Compose, Flutter为代表,核心还是代码布局,但是志在简化代码;
- 以掌阅开源的X2C框架为例,在编译期间,直接把XML转成源文件,编译时间变长,但是仍然可以使用XML布局,修改量小。
相信在众多开发者的努力下,将来还会有更多的优化方案。
如果你还没有了解Kotlin与DSL相关的知识,建议先阅读上一篇:
读书笔记:Kotlin自定义DSL语法
正文
既然已经了解了DSL语法。这一篇的我们就模仿JetPack Compose和Flutter的布局用法,实现一个自己的布局框架,就叫DSL-UI。
首先贴出一段JetPack Compose的代码:
setContent {
Column{
Text("A day in Shark Fin Cove")
Text("Davenport, California")
Text("December 2018")
}
}
代码非常的简单,我们的目标就是实现这样一个代码布局的方式。细节上可能略微有差别。
我们就实现上图的布局。
因为Android的布局种类比较多,特性也五花八门,为了实现简单,我们只是用LinearLayout,并改名为Column。
首先定义Column类,从图上我们知道,Column类中可以添加的组件为:ImageView,TextView以及Column自己。所以我们在Column类中,分别定义ImageView(), TextView(),Column()方法:
class Column(private val linearLayout: LinearLayout) {
fun ImageView(
id: Int,
width: Int,
height: Int,
backgroundColor: Int,
@DrawableRes src: Int = 0,
onClickListener: View.OnClickListener? = null,
block: (ImageView.() -> Unit)? = null
) {
val imageView = ImageView(linearLayout.context)
imageView.id = id
imageView.setImageResource(src)
imageView.setBackgroundColor(backgroundColor)
onClickListener?.let {
imageView.setOnClickListener(it)
}
block?.let { imageView.it() }
val layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
layoutParams.width = width
layoutParams.height = height
linearLayout.addView(imageView, layoutParams)
}
fun TextView(text: String, block: (TextView.() -> Unit)? = null) {
val textView = TextView(linearLayout.context)
textView.text = text
block?.let { textView.it() }
linearLayout.addView(textView)
}
fun Column(
orientation: Int,
leftMargin: Int = 0,
topMargin: Int = 0,
rightMargin: Int = 0,
bottomMargin: Int = 0,
layoutGravity: Int = Gravity.START,
block: Column.() -> Unit
) {
val linearLayout = LinearLayout(linearLayout.context)
initColumn(
linearLayout,
orientation,
leftMargin,
topMargin,
rightMargin,
bottomMargin,
layoutGravity
)
Column(linearLayout).block()
this.linearLayout.addView(linearLayout)
}
fun initColumn(
linearLayout: LinearLayout,
orientation: Int,
leftMargin: Int = 0,
topMargin: Int = 0,
rightMargin: Int = 0,
bottomMargin: Int = 0,
layoutGravity: Int = Gravity.START
) {
linearLayout.orientation = orientation
val layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
layoutParams.leftMargin = leftMargin
layoutParams.topMargin = topMargin
layoutParams.rightMargin = rightMargin
layoutParams.bottomMargin = bottomMargin
layoutParams.gravity = layoutGravity
linearLayout.layoutParams = layoutParams
}
}
在上面的三个方法中,根据要使用的场景添加了一些属性参数,剩下的都是基本的属性设置。
定义好了之后,我们需要定义Column的高级函数,因为创建View需要Context,如果Context作为参数传入显得不够优雅,所以就直接把高级函数扩展到Activity上:
fun Activity.Column(
orientation: Int,
leftMargin: Int = 0,
topMargin: Int = 0,
rightMargin: Int = 0,
bottomMargin: Int = 0,
block: Column.() -> Unit
): View {
val linearLayout = LinearLayout(this)
Column(linearLayout)
.apply {
initColumn(
linearLayout = linearLayout,
orientation = orientation,
leftMargin = leftMargin,
topMargin = topMargin,
rightMargin = rightMargin,
bottomMargin = bottomMargin
)
block()
}
return linearLayout
}
代码和Column中的几乎是一样。目前我们仅仅定义了一个类和一个高级函数,但是创建布局的代码已经发生了巨大的变化:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(
// 这里调用的是Activty.column
Column(orientation = android.widget.LinearLayout.HORIZONTAL) {
ImageView(
id = R.id.img,
width = 150,
height = 150,
src = R.drawable.ic_launcher_foreground,
backgroundColor = ContextCompat.getColor(
this@MainActivity,
R.color.colorAccent
),
onClickListener = View.OnClickListener { toast("img click") }
)
// 这里调用的是Column内部的Column()方法
Column(
orientation = android.widget.LinearLayout.VERTICAL,
leftMargin = 30,
layoutGravity = Gravity.CENTER_VERTICAL
) {
TextView(text = "这是一个标题")
TextView(text = "这是一个描述")
}
}
)
}
}
最后看一下实现效果:
源码下载地址:https://github.com/li504799868/DSL-UI
总结
DSL特性的实现,其实仅仅是Kotlin高级函数发光的一部分,只要我们设计合理,将会大大减少开发成本,不得不说Google大力支持Kotlin的确是眼光独到,Kotlin的发展未来可期。还没有把Kotlin提上日程的朋友必须要抓紧时间了。
本文地址:https://blog.csdn.net/u011315960/article/details/107536258