前言

我的第一份IT工作是Web前端,转眼已过去8年,之前在学习Flutter的时候情不自禁想起了当年苦逼的div生活,之后还略微研究了一下JetPack Compose。这两个库都是代码实现GUI,关于JetPack Compose的用法一直颇有争议,有人说在Android上,使用代码布局是技术的倒退,这一点我有一些自己的看法:

  • Android把布局和代码分离,必须说非常有眼光,相比其他平台,例如ios,Android开发者非常幸福。
  • 由于开发者对于App的性能要求越来越苛刻,使用XML布局,每次都需要XML解析成对应的View,苛刻的开发者开始寻求更优的运行速度,于是诞生了更多的GUI相关的库。

目前在布局方面最流行的优化方法有两种:

  1. 以JetPack Compose, Flutter为代表,核心还是代码布局,但是志在简化代码;
  2. 以掌阅开源的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提上日程的朋友必须要抓紧时间了。

更多相关文章

  1. 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
  2. Android自定义控件之自定义属性
  3. Android(安卓)ActivityManagerService(AMS)的启动分析
  4. 支持Android(安卓)的几款开源3D引擎调研
  5. Java与C之间传递数据
  6. Android简易实战教程--第四十六话《RecyclerView竖向和横向滚动
  7. Android--关于Cursor空指针的问题
  8. android混淆代码bug跟踪
  9. Android教程-03 常见布局的总结

随机推荐

  1. 配置vscode的cmder的方法
  2. 前端杂记-环境搭建
  3. mysql 存在更新 不存在插入
  4. mysql创建存储过程失败1307错误解决
  5. 0506作业
  6. h5红包活动特辑,红包H5营销活动如何制作
  7. 随时随地创建在线考试,“无纸化”考试时代
  8. 游戏Python开发从青铜到王者,你必须了解哪
  9. Unity3D实现模型体积拖拽变化
  10. MySQL5.7.30 Linux编译过程记录