Kotlin Android扩展视图背后的编辑魔法是什么?

kotlinx.android.synthetic.main.<layout-name>.view.*格式的输入添加到Kotlin源代码时,它会改变Android Studio编辑器的行为。 具体来说,它现在认为任何类型的属性View有一个属性对应的布局文件中的每个视图分配一个ID。 我假设我正在做一个不是 ActivityFragment

例如,假设我在源文件中声明了一个ViewHolder ,并向其中添加了一个名为xView属性。 进一步说,我有一个名为item_test的布局,其中有三个视图声明已经分配了id, abc 。 当我添加一个布局名称 item_test上面的表单的综合导入突然x有三个新的属性, abc 。 或者,更正确地说,编辑器使得它看起来好像x具有这些属性。

经过一番研究,我得出以下结论:

  • 盲目地添加布局视图ID作为属性。 任何由这样一个综合导入暗示的视图id被添加到View类型的任何属性(或View的子类)中。 这包括由类继承的这种类型的属性。

  • 由于属性是盲目添加的,因此开发者有义务确保在合成属性被访问之前分配对应于合成导入的运行时视图,否则将引发异常。

  • 如果在一个文件中指定了两个或多个这样的导入,那么他们的view ID的联合会被盲目地添加到View类型的所有类属性中。

  • 允许多次导入的目的是解释一个布局文件包含另一个布局文件的情况。

这些结论是正确的吗?

围绕这个特性的实现还有其他有趣的细节吗?

我正在使用kotlin-gradle-plugin的版本1.1.2-5。

如果我理解正确,你会假设如下:

 import kotlinx.android.synthetic.main.activity_main.item_test import kotlinx.android.synthetic.main.activity_main.item_test_2 class MyClass { lateinit var x: View lateinit var y: View fun foo() { val foo1 = x.item_test // Compiles val foo2 = y.item_test // Compiles as well val bar1 = x.item_test_2 // Compiles too val bar2 = y.item_test_2 // Compiles yet again } } 

因此,在我看来,类型为View的任何属性都将具有与合成导入相关的综合属性,而不管其是否有效。 所以扩展的实现看起来是蛮力的,如果有什么错误的话,不会给开发人员带来麻烦。

那是对的。
现在, item_test导入本质上是View类的扩展属性(简单示例[1] ):

 val View.item_test get() = findViewById(R.id.item_test) 

对于布局中的每个视图,插件会生成这些属性,并将其放入相关文件中。 这些属性也存在于ActivityFragment
因此,当你调用x.item_test ,你本质上是调用x.findViewById(R.id.item_test) [1]
编译后,插件将这些调用替换为findViewById 。 所以上面程序的简化反编译版本[1] (用Java):

 final class MyClass { public View x; public View y; public final void foo() { TextView foo1 = (TextView) x.findViewById(id.item_test); TextView foo2 = (TextView) y.findViewById(id.item_test); TextView bar1 = (TextView) x.findViewById(id.item_test_2); TextView bar2 = (TextView) y.findViewById(id.item_test_2); } } 

由于这只是View一个扩展函数,所以没有理智检查来检查在xy的视图层次结构中是否存在item_test ,就像它们不存在findViewById 。 如果findViewById调用返回null ,则该值为null或抛出KotlinNullPointerException


  • 盲目地添加布局视图ID作为属性。 任何由这样一个综合导入暗示的视图id被添加到View类型的任何属性(或View的子类)中。 这包括由类继承的这种类型的属性。

是的,因为该属性是View的扩展属性,如上所述。

  • 由于属性是盲目添加的,因此开发者有义务确保在合成属性被访问之前分配对应于合成导入的运行时视图,否则将引发异常。

我不确定你在问什么 如果你的意思是你必须在调用x.item_test之前初始化x ,那就对了。 另外, x应该在其层次结构中有一个view item_test

  • 如果在一个文件中指定了两个或多个这样的导入,那么他们的view ID的联合会被盲目地添加到View类型的所有类属性中。 允许多次导入的目的是解释一个布局文件包含另一个布局文件的情况。

是。


[1]:实际上,在幕后,这些查找被缓存。 请参阅文档中的底层。