欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

荐 DSL实战:仿Flutter代码布局实战

程序员文章站 2022-03-13 16:35:23
前言我的第一份IT工作是Web前端,转眼已过去8年,之前在学习Flutter的时候情不自禁想起了当年苦逼的div生活,之后还略微研究了一下JetPack Compose。这两个库都是代码实现GUI,关于JetPack Compose的用法一直颇有争议,有人说在Android上,使用代码布局是技术的倒退,这一点我有一些自己的看法:Android把布局和代码分离,必须说非常有眼光,相比其他平台,例如ios,Android开发者非常幸福。由于开发者对于App的性能要求越来越苛刻,使用XML布局,每次都需要...

前言

我的第一份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")
    }
}

代码非常的简单,我们的目标就是实现这样一个代码布局的方式。细节上可能略微有差别。

荐
                                                        DSL实战:仿Flutter代码布局实战
我们就实现上图的布局。

因为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 = "这是一个描述")
                }

            }
        )

    }
}

最后看一下实现效果:
荐
                                                        DSL实战:仿Flutter代码布局实战

源码下载地址:https://github.com/li504799868/DSL-UI

总结

DSL特性的实现,其实仅仅是Kotlin高级函数发光的一部分,只要我们设计合理,将会大大减少开发成本,不得不说Google大力支持Kotlin的确是眼光独到,Kotlin的发展未来可期。还没有把Kotlin提上日程的朋友必须要抓紧时间了。

本文地址:https://blog.csdn.net/u011315960/article/details/107536258