当前位置: 移动技术网 > 移动技术>移动开发>IOS > 荐 DSL实战:仿Flutter代码布局实战

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

2020年07月24日  | 移动技术网移动技术  | 我要评论

前言

我的第一份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提上日程的朋友必须要抓紧时间了。

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网