- 1:views-dsl
- 1.1:扩展属性和方法
- 1.2:名为Ui的界面的接口
- 2:views-dsl-ide-preview
- 3:views-dsl-appcompat
- 3.1:View DSL的AppCompat扩展如何与xml一起使用
- 3.2:AppCompat如何与Splitties Views DSL一起使用
- 3.3:支持的控件
- 4:views-dsl-constraintlayout
- 4.1:ConstraintLayout量身定制的lParams扩展
- 4.2:ConstraintLayout.LayoutParams扩展用于安全和可读的用法
- 5:views-dsl-material
- 5.1:用于从材料控件实例化视图函数
- 6:views-dsl-recyclerview
- 6.1:带滚动条的RecyclerView
- 6.2:使任何视图都可滚动
在上一篇介绍Splitties的的文章中,详细介绍了Splitties的基础库。在本篇文章中,将着重介绍Views DSL。
DSL(Domain Specific Language)
领域特定语言(DSL)是专用于特定应用程序域的计算机语言。这与通用语言(GPL)相反,后者广泛适用于各个领域。 DSL种类繁多,从通用域广泛使用的语言(例如网页的HTML)到仅一种或几种软件使用的语言(例如MUSH软代码)。 DSL可以按语言种类进一步细分,包括特定于域的标记语言,特定于域的建模语言(更一般地,规范语言)和特定于域的编程语言。专用计算机语言在计算机时代一直存在,但是由于特定领域建模的兴起,术语“特定领域语言”已变得越来越流行。简单的DSL,特别是单个应用程序使用的DSL,有时被非正式地称为迷你语言。
使用 DSL 有很多优点。 使用 DSL 最明显的优点在于,一旦获得了一种语言和转换引擎,在 DSL 覆盖的软件开发特定方面的工作就会变得更有效率,因为不必手动完成繁琐的工作(例如用Java代码去写复杂的UI界面)。 如果从 DSL 程序生成源代码(而不是解释它),可以使用不错的、领域专用的抽象,而无需任何运行时开销,因为就像编译器一样,生成器可以移除抽象并生成有效的代码。这可以使你的思路将变得更加清晰,因为编写的代码不会被实现细节搞得混乱。 换言之,使用 DSL 允许将基本点与复杂性分开。
由于Kotlin丰富的语法糖,使得用Kotlin代码也可以实现DSL。如用实现UI界面的代码会更加直观简洁,这样就可以完全抛弃通过XML实现UI的方式,这样便可以使app的运行速度得到显著的提升。
Views DSL
在Splitties库中的Views DSL模块主要包含如下几个:
- views-dsl
- views-dsl-ide-preview
- views-dsl-appcompat
- views-dsl-constraintlayout
- views-dsl-material
- views-dsl-recyclerview
views-dsl
要使用这个库需要添加如下依赖:
implementation("com.louiscad.splitties:splitties-views-dsl:$splitties_version")
这个库可以使用可读的Kotlin代码创建UI。Kotlin代码比xml更简洁。
在深入研究API的细节之前,先看一个简单的示例:
val button = button {
textResource = R.string.button_splash_name
}
在上面的例子中,可以轻松地用简介的代码来定义一个Button。
扩展属性和方法
该库主要由扩展方法和属性组成,以最少的代码但最大的灵活性创建View。
只需调用构造函数,然后在apply {...}块中调用所需的方法就足以在App用户界面上使用Kotlin而不是xml,但是Splitties Views DSL可以使内容更易读,更简洁并且具有一些功能, 例如主题,样式和无缝的AppCompat支持,而没有样板。
创建和配置视图
View扩展函数是Splitties Views DSL的原语。 它们是通用的,因此它们可以实例化任何类型的View。有6个名为view的函数,因为有2种重载类型,并且它们可用于3种接收器类型:Ui,View和Context。 这种重载类型之一是内部API。关于效率,因为它们都是内联的,这意味着没有不必要的分配,否则会稍微降低性能。
这两个重载都允许以下3个可选参数:
- @IdRes id: Int:视图的ID。 如R.id.input_name
- @StyleRes theme: Int:将应用于视图的主题叠加层的资源。 如R.style.AppTheme_AppBarOverlay
- initView: V.() -> Unit:类似于申请创建的View的lambda。
View的第一个重载采用必需的第一个参数,该参数是一个具有Context并返回View的函数。 由于构造函数也是Kotlin中的方法,因此可以像这样直接使用方法引用:view(:: View)。 其他任何View子类也是如此(例如view(:: FrameLayou))。 也可以改用lambda:view({FrameLayout(it)})。 实际上,这就是在方法引用的自动完成不是最佳方法时应该采取的方法,然后使用IDE快速操作(alt /⌥选项+⏎enter)将其转换为方法引用。 当然,可以使用任何非构造方法引用的自定义方法引用,只要该方法采用Context参数并返回View或其任何子类即可。
val myView: MyCustomView = view(::MyCustomView, R.id.my_view) {
backgroundColor = Color.BLACK
}
View的第二个重载是内部API,它不需要任何必需的参数,而是依靠显式类型参数来正常工作。 只需view <TextView>()即可实例化TextView。 但是,此版本依赖于"view factory",该工厂可以根据需要自动提供所请求类型的子类。 如果使用Views DSL AppCompat,则由于底层View工厂,将自动使用view <Button>接收AppCompatButton的实例。
val submitBtn = view<Button>(R.id.btn_submit) { // 应该使用 `button { … }` 代替。
textResource = R.string.submit
}
使用xml中定义的样式
在某些情况下,需要使用xml定义的样式,例如使用在AppCompat中定义的样式(如Widget_AppCompat_Button_Colored)时。使用Splitties,可以非常轻松地使用在Android,AppCompat和Material Components中定义的xml样式。它还能够对xml中定义的自定义或第三方样式执行相同的操作。
1、使用Android样式
假设要创建一个水平的ProgressBar实例。 首先,创建一个AndroidStyles实例:
private val androidStyles = AndroidStyles(ctx)
然后,使用在progressBar属性上定义的函数:
val progressbar = androidStyles.progressBar.horizontal()
AndroidStyles中提供了Android平台中定义的其他样式。 只需让自动完成功能指导即可。
2、使用Material Components样式
由于默认情况下主题内不包含Material Components样式,因此需要先加载它们。 只需使用以下代码即可完成:
private val materialStyles = MaterialComponentsStyles(ctx)
也可以使用MaterialComponentsStyles实例使用样式。 如下:
val bePoliteBtn = materialStyles.button.outlined {
textResource = R.string.be_polite
}
3、使用AppCompat样式
由于AppCompat样式默认情况下不包含在主题内,因此需要先加载它们。 只需使用以下代码即可完成:
private val appCompatStyles = AppCompatStyles(ctx)
也可以使用AppCompatStyles实例使用样式。 如下:
val bePoliteBtn = appCompatStyles.button.colored {
textResource = R.string.be_rude
}
4、使用其他的xml样式
可以在AndroidStyles和AppCompatStyles中找到colored,horizontal和其他属性具有的XmlStyle类型。 在将样式加载到当前主题之后,可以很容易地实例化它并支持任何xml样式。
XmlStyle内联类具有:
- 类型参数,用于目标View类型。
- 一个Int值,一个主题属性(@AttrRes,而不是@StyleRes)。
正如上文所见,其构造函数不需要样式资源(例如R.style.Widget_AppCompat_ActionButton),而是主题属性资源(例如R.attr.Widget_AppCompat_ActionButton)。 这是由于Android中的限制,只能以编程方式使用主题内的xml样式。 但这并不意味着将不得不污染所有使用样式定义的主题。
Android允许将多个主题与applyStyle()方法结合使用,可以调用任何上下文具有的任何主题。 这样,可以应用仅包含一行代码即可包含所需的XML样式引用的主题。 这就是上面提到的AppCompatStyles(ctx)函数的作用。
ctx.theme.applyStyle(R.style.ThirdPartyStyles, false)
val clapButton = XmlStyle<Button>(R.attr.Widget_ThirdParty_FancyButton)(ctx) {
imageResource = R.drawable.ic_clap_white_24dp
}
5、View扩展
对于更具表现力的UI代码,Splitties Views DSL对Views split具有可传递的依赖关系,该依赖关系提供了一组有用的Kotlin友好扩展功能和专用于View及其某些子类的属性。
Splitties Views DSL也可以很好地与xml布局配合使用。扩展功能是前面提到的视图功能的变体,它具有一个附加的第一个参数:要扩展的布局资源ID。此外,如果xml布局为根视图定义了一个ID,除非指定了明确的ID(包括View.NO_ID),否则它将保留。就像视图一样,为Context,View和Ui定义了inflate。
val mySecretFancyView = inflate(R.layout.my_fancy_layout) {
isVisible = false
}
放置视图
ViewGroup.add()是ViewGroup.addView()的别名。
要将视图添加到代码中的ViewGroup中,可以使用View.addView()。 但是,如果已经很明显地处于以UI为中心的类中,而该类传递了显然是View的参数,那么反复查看就变得非常多余。
这就是为什么此库具有一个内联别名的原因,该别名仅为ViewGroup添加()。 它具有返回传递的View的额外好处,在某些情况下可以派上用场。ViewGroup.add()函数需要一个ViewGroup.LayoutParams实例。
名为Ui的界面的接口
此接口的声明如下所示:
interface Ui {
val ctx: Context
val root: View
}
为什么是接口
如上所述,可以将UI代码直接放在Activity或Fragment中,但是事实并非意味着应该这样做。 在同一个类中将UI代码与业务逻辑,数据存储代码,网络调用和其他样板混合在一起,将很快使进一步的工作(如功能添加和维护)变得非常困难。
Xml布局迫使将大多数UI代码放入单独的xml文件中来缓解此问题,但是通常需要补充代码(例如,用于处理过渡,动态可见性),并且通常将其放入Fragment或Activity中, 事情变得更糟,因为将UI代码分布在紧密耦合的至少两个位置。
它是由什么组成的
使用Splitties Views DSL,有一个名为Ui的可选接口,其实现旨在包含的UI代码。它具有ctx属性,因为在Android中,需要使用Context才能创建View。 它具有根属性,因为需要在最后显示一个视图。
另外,由于Ui是一个界面,因此可以通过创建子界面或子类以具有相同UI的不同实现的方式来发挥创意,这对于A/B测试,用户首选项(用户可以选择的不同样式)非常有用 ,配置(如屏幕方向)等。
实现Ui接口
在编写Ui实现时,将ctx属性作为第一个构造函数参数覆盖(例如,类MainUi(override val ctx:Context):Ui {),并通过为根参数分配一个View字段来将根参数覆盖为具有后备字段的属性(例如 覆盖val root = coordinatorLayout {...})。
然后创建视图(通常将它们作为最终属性,例如root),并将其添加到它们所属的ViewGroup中,以便它们是root的直接或间接子级(root是ViewGroup)。
/**
* @author Melrose
* @since 1.0.0
*/
class MainUi(override val ctx: Context) :Ui{
private val mainContent = textView {
textResource = R.string.string_main_title
textColorResource = R.color.colorPrimary
}
override val root = frameLayout {
fitsSystemWindows = true
add(mainContent,lParams{
gravity = gravityCenter
})
}
}
使用Ui实现
如果要在Activity子类中使用Ui实现,只需调用setContentView(ui)。 要从其他任何地方使用它,只需获取root属性。 在Fragment子类中,这意味着要从onCreateView()方法中返回它。
还可以使用在子接口或实现中声明的任何函数或属性。
/**
* @author Melrose
* @since 1.0.0
*/
class MainActivity :AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val ui = MainUi(this)
setContentView(ui)
}
}
views-dsl-ide-preview
要使用这个库需要添加如下依赖:
debugImplementation("com.louiscad.splitties:splitties-views-dsl-ide-preview:$splitties_version")
此库可以在IDE中预览Ui实现。改库提供了一个名为UiPreView的类,可以在xml布局文件中使用该类来预览使用Views DSL制作的Ui子类。
为了避免将未使用的代码嵌入到生产应用程序中,添加调试buildType的依赖项(在gradle文件中使用debugImplementation),并将预览xml布局文件放置在src/debug/res /layout /目录中(而不是src / main中) /res/layout/)。
<splitties.views.dsl.idepreview.UiPreView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:splitties_class_package_name_relative="main.MainUi"/>
为了使预览工作,Ui子类需要具有其第一个参数类型Context。 对于后续参数(如果适用),支持任何接口,但是在创建和绘制此视图时无需调用其方法,否则将引发异常。
如果预览无法正常工作或无法反映最新更改,则可能是因为没有在项目(实际上是模块)上执行compileDebugKotlin gradle任务。 IDE预览当前仅适用于编译的类和xml布局。 运行compileDebugKotlin gradle任务将节省您的时间,因为它不涉及打包完整apk的所有后续任务。
当使用splitties_class_package_name_relative属性时,UiPreView类将采用从Context返回的packageName并附加一个点和该属性的值,以获取Ui实现的类名称。但是,可能已经配置了构建,因此调试buildType的applicationId后缀通常为.debug,如以下示例中所示:
buildTypes {
debug {
applicationIdSuffix ".debug" // This changes the packageName returned from a Context
versionNameSuffix "-DEBUG"
}
release {
// Config of your release build
}
}
views-dsl-appcompat
要使用这个库需要添加如下依赖:
implementation("com.louiscad.splitties:splitties-views-dsl-appcompat:$splitties_version")
该库是View DSL的AppCompat扩展。
View DSL的AppCompat扩展如何与xml一起使用
使用AppCompat主题时,LayoutInflater会通过兼容版本(例如AppCompatButton,AppCompatTextView等)替换在XML布局中找到的平台小部件(如TextView和Button)。
AppCompat如何与Splitties Views DSL一起使用
由于LayoutInflater仅适用于xml,因此如果将View(:: TextView)与View DSL一起使用,则将获得TextView实例,而不是AppCompatTextView实例。 这意味着它将没有AppCompat功能和样式(例如TextView的自动大小)。
但是,如果使用textView(),则在依赖项中,它将自动委托给此拆分,并返回AppCompatTextView实例。这适用于所有AppCompat小部件。如果要使用在appcompat中定义的样式(如Widget_AppCompat_Button_Colored),只需在本地缓存AppCompatStyles实例并使用其属性和功能即可。
支持的控件
TextView
ImageView
Button
EditText
Spinner
ImageButton
CheckBox
RadioButton
CheckedTextView
AutoCompleteTextView
MultiAutoCompleteTextView
RatingBar
SeekBar
只需调用PascalCase构造函数的camelCase版本的相关方法即可。 例如,可以调用seekBar(){…},将得到一个AppCompatSeekBar实例。
views-dsl-constraintlayout
要使用这个库需要添加如下依赖:
implementation("com.louiscad.splitties:splitties-views-dsl-constraintlayout:$splitties_version")
View DSL的ConstraintLayout扩展。
ConstraintLayout量身定制的lParams扩展
ConstraintLayout上的lParams()扩展函数看起来与LinearLayout和FrameLayout上的类似命名的扩展类似,但是有两个主要区别:
- 由于ConstraintLayout子级本应具有约束,因此它们的默认宽度和高度是matchConstraints,而不是wrapContent。 这意味着必须为诸如TextViews和Button之类的东西指定height = wrapContent。
- 在xml中,ConstraintLayout不支持match_parent,并且会降低性能(替代方法是添加0dp + 2个父相对约束)。 此处不是这种情况,因为matchParent被重写为具有适当父级相对约束的matchConstraints。 结果是更易读的UI代码,而不会影响性能。
ConstraintLayout.LayoutParams扩展用于安全和可读的用法
此库还带来了一组在lParams(){…}中使用的扩展功能:
centerHorizontally()
centerVertically()
centerInParent()
topOfParent()
bottomOfParent()
startOfParent()
endOfParent()
leftOfParent()
rightOfParent()
alignVerticallyOn(…)
alignHorizontallyOn(…)
centerOn(…)
topToTopOf(…)
topToBottomOf(…)
bottomToTopOf(…)
bottomToBottomOf(…)
baselineToBaselineOf(…)
startToStartOf(…)
startToEndOf(…)
endToStartOf(…)
endToEndOf(…)
leftToLeftOf(…)
leftToRightOf(…)
rightToRightOf(…)
rightToLeftOf(…)
views-dsl-material
要使用这个库需要添加如下依赖:
implementation("com.louiscad.splitties:splitties-views-dsl-material:$splitties_version")
View DSL的材料组件扩展,支持材料组件中的所有小部件。
用于从材料控件实例化视图函数
除了使用view(:: AppBarLayout){…}和类似方法,可以使用appBarLayout(...){…}。
views-dsl-recyclerview
要使用这个库需要添加如下依赖:
implementation("com.louiscad.splitties:splitties-views-dsl-recyclerview:$splitties_version")
Views DSL的RecyclerView扩展。
带滚动条的RecyclerView
要在Android中的视图上获取滚动条,需要首先在android:scrollbars xml属性中启用它。 只有这样,才能使用isHorizontalScrollBarEnabled和isVerticalScrollBarEnabled属性禁用并重新启用它们。为解决此问题,此库提供了一个recyclerView函数,会在xml中同时启用水平和垂直滚动条的情况下,使RecyclerView膨胀,但是只有当内容可以沿该方向滚动时,滚动条才会出现,因此可直接使用它即可。
使任何视图都可滚动
假设LinearLayout或TextView太长,无法容纳所有屏幕尺寸。 在xml中,可能会使用NestedScrollView或较旧的ScrollView。 不幸的是,NestedScrollView的错误可能会在某些情况下中断内容,从而难以重现一致的情况,并且ScrollView不支持在应用程序中可能需要的嵌套滚动。另一方面,有RecyclerView,它没有这些问题。
因此,此库为View提供了wrapInRecyclerView扩展函数,该函数返回包装了View的RecyclerView。默认情况下它是垂直的,但是可以将horizontal参数设置为true。
另外,还可以指定ID,以便将滚动位置保存到实例状态并在需要时恢复。而且有一个可选的lambda来配置RecyclerView,这对于设置填充和禁用裁剪,设置背景等很有用。
val content = textView {
textResource = R.string.a_very_long_string
}.wrapInRecyclerView(id = R.id.main_content) {
verticalPadding = dip(8)
horizontalPadding = dip(16)
clipToPadding = false
}