IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Kotlin之DSL -> 正文阅读

[移动开发]Kotlin之DSL

Kotlin

DSL篇


前言

入职新公司后发现kotlin的语法知识跟不上,因为公司开发团队的习惯问题,原先的公司还是java+kt混编开发的模式,许多同事基本不用kt,一些不常规的语法应用很少接触使用,还存在使用错误的情况,所以现在要补一些ktolin语法使用的知识,而DSL就是第一个点

一、What’s the DSL?

先上一小段可爱的代码:

ConstraintLayout {
            layout_width = wrap_content
            layout_height = wrap_content
            background_res = R.drawable.ic1

            ImageView {
                layout_width = 22
                layout_height = 22
                top_toTopOf = parent_id
                end_toEndOf = parent_id
                margin_top = 9
                margin_end = 5
                padding = 5
                scaleType = scale_fit_xy
                src = R.drawable.ic2
            }

            TextView {
                layout_id = "tv1"
                layout_width = wrap_content
                layout_height = wrap_content
                textColor = "#ff3f4658"
                textSize = 16f
                textRes = R.string.tv1
                center_horizontal = true
                top_toBottomOf = "iv"
                margin_top = 3
            }

            TextView {
                layout_id = "tv2"
                layout_width = wrap_content
                layout_height = wrap_content
                textColor = "#ffffff"
                textSize = 12f
                center_horizontal = true
                top_toBottomOf = "tv1"
                margin_top = 8
                text = "deo"
            }

        }

土包子的我第一次看到这样的定义布局方式并没有感觉有啥新奇,不就是动态设置控件嘛,并没有什么高级的地方,反而觉得不如XML直观且无法预览,但一仔细看就不对了,按照常规的写法应该是从父布局开始逐个初始化对象,然后通过layouparmars设置布局属性添加到相应容器中,这种写法是什么情况,貌似直接嵌套还直接定义了控件属性,和xml的语法很像但貌似更简便(XML也可以看做是DSL)。此时只有一个字可以形容
在这里插入图片描述

通过偷偷摸摸的百度后知道这就是传说中的DSL构建的布局方式,这个东西是有了解过的,但没深入的看过(看完就忘),kt的部分语法感觉过于难以阅读所以一直抗拒(其实是菜看不懂),靠着apply、let、with苟延残喘的使用着,还感觉自己kt用的很nice。

这么陌生且高大上的词汇看的第一眼就感觉很难,自然反应的Google一下:
图一
给出的解释就是*领域专用语言***。 词汇太专业,依然不知道是啥。。。查的时候还看到另一个词:GPL ,有必要了解一下,General Purpose Language翻译过来就是通用语言,如Java、OC、C等等,这个好理解脑子一下就知道是啥了。这些语言使用很广泛,像java能 写android也能写后端,业务代码都是用这些语言写的。
对比一下GPL,DSL的针对性更强,只适用于特定的功能方向,就像文言文和白话文,一个用于作文记事,一个用于交际用语,举个可能不恰当的栗子,墓志铭就有特定的格式,第一他只能写给死人,第二有固定的格式,其他的像啥三言、五言、七言的,不是很准确但就是那个意思吧。

二、初探

1.DSL常见的应用

简单的了解过后感觉DSL更像是一种自己构建出来快捷的语法格式或者说结构,像最上面的布局代码其实最后做的工作应该和普通的写法(控件都最后都要初始化)是一样的,但对于项目来说大量频繁的重写就会省力很多且代码格式更好,当然好处不止于此。很多东西都见过也用过只是不知道原来这么D伊奥。
第一个众所周知的HTML就是典型的DSL,首先她不算是编程语言,它定义了特殊的网页结构标示不同的含义,还一个特点他不能像java那样写出复杂的逻辑,它们都是命令式的,瞬间清醒,此时DSL和GPL的区别通透了。
其他的一些我们接触过的包括Gradle、SQL,按照上面的特点,SQL语句能写逻辑吗?Gradle语法能写复杂逻辑吗?

他们的语句还有些共同点,仔细的回忆一下SQL、Gradle语句,是不是容易忘记,容易不认识,容易写错,可能一年没写过一条SQL的人大把抓,但他们都比较有特定的结构且只能用于实现特定功能,因为语法和常规的语言编写格式不同所以容易忘记。SQL编写语句管理数据,Gradle配置项目用于构建,HTML用于显示网页文本内容,而不像Java或者其他编程语言一样,没有特定的限制,想在哪用在哪用。

三.koltin中的DSL

要实现开头的布局方式需要怎么做呢?因为SDK也没定义这种规范,kt也没有直接提供,所以只能内部创造,手动满足。
在kotlin中实现DSL构建要靠两样东西:
.扩展函数;
.带接收者的 Lambda 表达式;

先说扩展函数,这个还是比较简单的。

1.扩展函数

fun String.customOperation(append:String) :String{
    return this+append
}

上面写了个String的扩展函数,自定义了一个操作,对接受者拼接了append字符串,在其他地方可以把customOperation当做系统的api直接调用。扩展函数的作用也一目鸟然,可以封装频率使用高的操作代码,可以像使用原生API一样使用扩展方法。
再看一下字节码:

 // access flags 0x19
  public final static customOperation(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
    // annotable parameter count: 2 (visible)
    // annotable parameter count: 2 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1
   L0
    ALOAD 0
    LDC "$this$customOperation"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
    ALOAD 1
    LDC "append"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 11 L1
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN
   L2
    LOCALVARIABLE $this$customOperation Ljava/lang/String; L0 L2 0
    LOCALVARIABLE append Ljava/lang/String; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  @Lkotlin/Metadata;(mv={1, 4, 0}, bv={1, 0, 3}, k=2, d1={"\u0000\n\n\u0000\n\u0002\u0010\u000e\n\u0002\u0008\u0002\u001a\u0012\u0010\u0000\u001a\u00020\u0001*\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u0001\u00a8\u0006\u0003"}, d2={"customOperation", "", "append", "My_Application.app"})
  // compiled from: Extends.kt
}

static 圈一下,一目了然,扩展方法被编译成静态方法,(Ljava/lang/String;Ljava/lang/String;) 圈一下,咦?怎么两个String呢?

2.带接收者的 Lambda 表达式

带接受者的Lambda是啥样子,首先函数式编程Lambda是kt的一大特色(简洁避免语法噪音),但高阶函数看起来就很累,阅读性感觉不太友好,过于简化(主要还是菜),rxjava、协程一起用的时候就更懵逼了,lowB的代码不香吗?

要知道啥是高阶函数,简单无脑的理解就是函数嵌套函数,在kt中lambda是可以当做参数进行传递的

fun createString(build:(StringBuilder)->Unit):String {
    val stringBuilder=StringBuilder()
    build(stringBuilder)
    return stringBuilder.toString()
}

如果想要通过StringBuild去创建一个自定义的拼接字符串可以用上面的方法,比如:

这就是lambda作为参数传递的一个简单栗子,但是在调用createString 的时候,在表达式中需要显示的调用it,这个it就是StringBuilder,而这个表达式的调用者是期望或者我们已经指定了就是一个StringBuilder,所以可以避免这种显示调用的现象,在声明表达式的时候为它指定调用者,所以修改createString传入的表达式:

fun createString(build:StringBuilder.()->Unit):String {
    val stringBuilder=StringBuilder()
    build(stringBuilder)
    return stringBuilder.toString()
}

这个时候可以看到调用函数的地方报错了

可以看到编译的提示,表达式的参数提示由it变成了this,这个时候我们直接可以使用StringBuilder的方法而不需要显示的使用it

在上面的方法中 build:StringBuilder.()->Unit就是一个带接受者的Lambda 表达式,它的声明语法格式是:
调用者类型.(参数类型)->返回值类型

3.用DSL构建布局

最上面的ConstraintLayout布局其实就是基于DSL构建的动态布局,哪具体如何实现的呢?就拿ConstraintLayout来说,
首先要定义一种声明方式来初始化对象,所以可以写一个基于Context的扩展函数

fun Context.ConstraintLayout(): ConstraintLayout {
    val constraintLayout =
        if (style != null) ConstraintLayout(
            ContextThemeWrapper(this, style)
        ) else ConstraintLayout(this)
    return constraintLayout
}

这种布局容器都可以这么声明,但对于宽高、背景、方向等等的一些属性也需要处理,对于每个布局都要设置的宽高属性如何定义呢?还是利用扩展函数来实现

inline var View.layout_width: Number
    get() {
        return 0
    }
    set(value) {
        val w = if (value.dp > 0) value.dp else value.toInt()
        val h = layoutParams?.height ?: 0
        updateLayoutParams<ViewGroup.LayoutParams> {
            width = w
            height = h
        }
    }

这里已经不是扩展函数了 ,应该说是扩展属性,layout_width是自定义的属性,这里和XML中的属性定义一样的。同理对于高度可以这么写:

inline var View.layout_height: Number
    get() {
        return 0
    }
    set(value) {
        val w = layoutParams?.width ?: 0
        val h = if (value.dp > 0) value.dp else value.toInt()
        updateLayoutParams<ViewGroup.LayoutParams> {
            width = w
            height = h
        }
    }

只是名字不同而已。外边距如何定义呢?同样

inline var View.margin_top: Number
    get() {
        return - 1
    }
    set(value) {
        updateLayoutParams<ViewGroup.MarginLayoutParams> {
            topMargin = value.dp
        }
    }

所以增加了对View的扩展属性后,对于所有的控件都可以去设置相应的属性了,上面的ConstraintLayout 就要做出相应的更改,要通过一个函数去对实例进行初始化,

inline fun Context.ConstraintLayout(
    style: Int? = null,
    init: ConstraintLayout.() -> Unit
): ConstraintLayout {
    val constraintLayout =
        if (style != null) ConstraintLayout(
            ContextThemeWrapper(this, style)
        ) else ConstraintLayout(this)
    return constraintLayout.apply(init)
}

这里的init就是上面说的带接受者的lamba表达式拉,所以代码里去实现一个ConstraintLayout布局就可以这样子拉

context.ConstraintLayout {
       layout_width = match_parent
       layout_height = wrap_content
}

而对于子控件,TextView举个栗子:

inline fun Context.TextView(
    style: Int? = null,
    init: AppCompatTextView.() -> Unit
): TextView {
    val textView =
        if (style != null) AppCompatTextView(
            ContextThemeWrapper(context, style)
        ) else AppCompatTextView(context)
    return textView
}

但如何达到内嵌的语法格式呢?首先,这种类型的子控件都是内嵌在布局中的,想想XML的实现,所以子控件基于ViewGroup扩展函数去实现就可以那么写了,修改一下上面的代码

inline fun ViewGroup.TextView(
    style: Int? = null,
    init: AppCompatTextView.() -> Unit
): TextView {
    val textView =
        if (style != null) AppCompatTextView(
            ContextThemeWrapper(context, style)
        ) else AppCompatTextView(context)
    return textView
}

这个时候就可以有这种写法了:

context.ConstraintLayout {
                layout_width = match_parent
                layout_height = wrap_content

                TextView {
                    layout_id = "tvSelector"
                    layout_width = match_parent
                    layout_height = 36
                    textSize = 13f
                    padding_top = 10
                    padding_bottom = 10
                    margin_top = 4
                }
           }     

还有个问题,这种写法的textview并没有加入到ConstraintLayout 中,所以我们还要修改下扩展函数:

inline fun ViewGroup.TextView(
    style: Int? = null,
    init: AppCompatTextView.() -> Unit
): TextView {
    val textView =
        if (style != null) AppCompatTextView(
            ContextThemeWrapper(context, style)
        ) else AppCompatTextView(context)
    return textView.apply(init).also { addView(it) }
}

这样一个简单的动态布局就出来了,没想象中那么高级,其实就是对扩展函数、高阶函数的运用。

总结

一.对于DSL构建的布局

Android基于XML视图的模式已经很久很久了,从用eclipse开发的时候就是这种模式,对于布局的加载好像也饱受一些诟病,DSL的构建感觉只是一种方式,更重要的是他提供了另一种某些场景更好使用的方式。

  1. 对于布局不复杂的构建来说相当nice,代码整洁;
  2. 关键的一点,动态构建的效率会高一些,因为XML的构建是IO操作相对耗时一些,对于一些inflate方式替换下来还是很好用的。
  3. 对于复杂的布局或者页面,感觉还是常规的实现更好一些,XML提供了预览,这个对于开发业务来说还是很重要的;
  4. 没必要因为性能原因因噎废食,菜鸡开发业务忙的时候是六亲不认的,半夜12点的时候如果一个几百行的动态布局没有预览还是要疯的,而一些属性、自定义控件或者使用少的控件可能都需要临时去实现。

二. Compose

Jetpack已经不是新鲜产物了,用起来还是挺香的,而Compose 也是jetpack的一个新的工具包,官方的介绍是:
简化并加快 Android 上的界面开发,帮助您使用更少的代码、强大的工具和直观的 Kotlin API
Kotlin API是重点,回想毕业的时候还有人说Kotlin不会作为官方语言,谁能想到今天呢。从给出的栗子来看和上面DSL构建还有几分相似,布局实现的风格完全变化,可组合函数来实现页面,也支持预览,简单的看了下,对于一个布局以代码块的形式出现,可以组合使用,自然的想到了Flutter的万物皆组件。不管咋样吧,Compose必然是趋势,也有必要了解。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-25 11:48:06  更:2021-07-25 11:48:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 17:38:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码