Splitties(二)-Views DSL

文章目录[x]
  1. 1:views-dsl
  2. 1.1:扩展属性和方法
  3. 1.2:名为Ui的界面的接口
  4. 2:views-dsl-ide-preview
  5. 3:views-dsl-appcompat
  6. 3.1:View DSL的AppCompat扩展如何与xml一起使用
  7. 3.2:AppCompat如何与Splitties Views DSL一起使用
  8. 3.3:支持的控件
  9. 4:views-dsl-constraintlayout
  10. 4.1:ConstraintLayout量身定制的lParams扩展
  11. 4.2:ConstraintLayout.LayoutParams扩展用于安全和可读的用法
  12. 5:views-dsl-material
  13. 5.1:用于从材料控件实例化视图函数
  14. 6:views-dsl-recyclerview
  15. 6.1:带滚动条的RecyclerView
  16. 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代码创建UIKotlin代码比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种接收器类型:UiViewContext。 这种重载类型之一是内部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,可以非常轻松地使用在AndroidAppCompatMaterial 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样式

可以在AndroidStylesAppCompatStyles中找到coloredhorizontal和其他属性具有的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 DSLViews split具有可传递的依赖关系,该依赖关系提供了一组有用的Kotlin友好扩展功能和专用于View及其某些子类的属性。

Splitties Views DSL也可以很好地与xml布局配合使用。扩展功能是前面提到的视图功能的变体,它具有一个附加的第一个参数:要扩展的布局资源ID。此外,如果xml布局为根视图定义了一个ID,除非指定了明确的ID(包括View.NO_ID),否则它将保留。就像视图一样,为ContextViewUi定义了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代码直接放在ActivityFragment中,但是事实并非意味着应该这样做。 在同一个类中将UI代码与业务逻辑,数据存储代码,网络调用和其他样板混合在一起,将很快使进一步的工作(如功能添加和维护)变得非常困难。

Xml布局迫使将大多数UI代码放入单独的xml文件中来缓解此问题,但是通常需要补充代码(例如,用于处理过渡,动态可见性),并且通常将其放入FragmentActivity中, 事情变得更糟,因为将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 DSLAppCompat扩展。

View DSL的AppCompat扩展如何与xml一起使用

使用AppCompat主题时,LayoutInflater会通过兼容版本(例如AppCompatButtonAppCompatTextView等)替换在XML布局中找到的平台小部件(如TextViewButton)。

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()扩展函数看起来与LinearLayoutFrameLayout上的类似命名的扩展类似,但是有两个主要区别:

  • 由于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(...){…}。

其实现方式可以参考支持的ViewViewGroup

views-dsl-recyclerview

要使用这个库需要添加如下依赖:

implementation("com.louiscad.splitties:splitties-views-dsl-recyclerview:$splitties_version")

Views DSL的RecyclerView扩展。

带滚动条的RecyclerView

要在Android中的视图上获取滚动条,需要首先在android:scrollbars xml属性中启用它。 只有这样,才能使用isHorizontalScrollBarEnabledisVerticalScrollBarEnabled属性禁用并重新启用它们。为解决此问题,此库提供了一个recyclerView函数,会在xml中同时启用水平和垂直滚动条的情况下,使RecyclerView膨胀,但是只有当内容可以沿该方向滚动时,滚动条才会出现,因此可直接使用它即可。

使任何视图都可滚动

假设LinearLayoutTextView太长,无法容纳所有屏幕尺寸。 在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
}
点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00