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提上日程的朋友必须要抓紧时间了。
更多相关文章
- 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
- Android自定义控件之自定义属性
- Android(安卓)ActivityManagerService(AMS)的启动分析
- 支持Android(安卓)的几款开源3D引擎调研
- Java与C之间传递数据
- Android简易实战教程--第四十六话《RecyclerView竖向和横向滚动
- Android--关于Cursor空指针的问题
- android混淆代码bug跟踪
- Android教程-03 常见布局的总结