如何将自定义视图组添加到 Anko DSL?

How to add custom view groups to Anko DSL?

Anko docs 告诉我们如何向 DSL 添加自定义视图。但是如果我的自定义视图是一个视图组,问题就来了。

class MyFrameLayout(context: Context) : FrameLayout(context)

fun ViewManager.myFrameLayout(init: MyFrameLayout.() -> Unit = {}) = ankoView({ MyFrameLayout(it) }, init)

class MyUI : AnkoComponent<Fragment> {
    override fun createView(ui: AnkoContext<Fragment>) = with(ui) {

        myFrameLayout {
            textView("hello").lparams { // error: Unresolved reference: lparams
                bottomMargin = dip(40)
            }
        }
    }
}

但如果我将 myFrameLayout 调用更改为 frameLayout,它就可以正常工作。那么使视图组与 Anko DSL 一起使用的正确方法是什么?

如果你从你的代码转到 Anko 的任何 lparams 声明,你可以看到在 Anko 生成的 DSL 代码中,lparamsT: View 的扩展函数,看起来像这个:

fun <T: View> T.lparams(
        width: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
        height: Int = android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
        init: android.widget.FrameLayout.LayoutParams.() -> Unit = defaultInit
): T {
    val layoutParams = android.widget.FrameLayout.LayoutParams(width, height)
    layoutParams.init()
    this@lparams.layoutParams = layoutParams
    return this
}

(以及不同 LayoutParams 构造函数的更多重载)

它在 class 内声明,因此它仅在具有该 class 接收者的函数中可见,请参阅 关于此 DSL 编程方法。


为了能够在 Anko DSL 中为您的自定义 ViewGroup 使用 lparams,您必须在自定义视图代码中声明一个或多个类似的函数,这将创建一个适当的 LayoutParams 为您的 class.

如果您还想在 DSL 外部隐藏 lparams 函数,您可以创建 MyFrameLayout 的子 class 并仅在 DSL 代码中使用它,使用 MyFrameLayout 其他地方本身。

之后,您可以在作为 init: MyFrameLayout.() -> Unit 传递给 fun ViewManager.myFrameLayout 的 lambda 中的任何 View 上调用 lparams

如果我们看一下 Anko 源代码,我们可以看到 frameLayout 实际上是 returns _FrameLayout class 的一个实例,其中这些 lparams 函数被定义。据我了解,这是必需的,因此这些 lparams 函数仅在布局构建代码中可用。

在 Anko 的 Layouts.kt 文件中,每个受支持的 ViewGroup 都有这些 _<ViewGroup> class。

因此,支持自定义视图组的直接方法是创建一个 _<ViewGroup> class 和 lparams 方法实现。问题是这个 _<ViewGroup> class 通常包含比我的 <ViewGroup> 本身更多的代码!

如果我想创建许多自定义视图组,使用这种方法添加对它们的 Anko 支持会很麻烦。假设我有 MyFrameLayout1MyFrameLayout2MyFrameLayout3 class。它们基本上是 FrameLayout,所以我希望对它们使用相同的布局参数。但我必须创建 _FrameLayout1_FrameLayout2_FrameLaoyt3 classes,它们只是 Anko 的 _FrameLayout 的 copy/paste。

所以我稍微改进了这个方法。我创建了一个 interface _FrameLayout:

interface _FrameLayout {
    // copy/paste from Anko's _FrameLayout
}

现在要支持任何自定义 FrameLayout subclass 我只需要:

class _MyFrameLayout(ctx: Context) : MyFrameLayout(ctx), _FrameLayout

fun ViewManager.myFrameLayout(init: _MyFrameLayout.() -> Unit = {})= ankoView({ _MyFrameLayout(it) }, init) 

这在创建许多自定义视图组时减少了 copy/paste 很多。

实际上你只需要扩展 anko 并声明你的自定义视图然后在 DSL 中正常使用它:

public inline fun ViewManager.customView() = customView {}
public inline fun ViewManager.customView(init: CustomView.() -> Unit) = ankoView({ CustomView(it) }, init)

然后在DSL中正常使用

frameLayout {
    customView()
}

如果您继承自_RelativeLayout 而不是 RelativeLayout,您可以按预期使用自定义布局。