什么是Kotlin Android Extensions for Views背后的编辑魔术?

时间:2017-06-21 19:51:24

标签: android view properties kotlin synthetic

当我将表单kotlinx.android.synthetic.main.<layout-name>.view.*的导入添加到Kotlin源时,它会改变Android Studio编辑器的行为。具体来说,它现在认为类型View的任何属性都具有与布局文件中分配了id的每个视图对应的属性。我假设我在一个 ActivityFragment的课程中这样做。

例如,假设我在源文件中声明了ViewHolder,并为其添加了一个名为x的属性View。此外,假设我有一个名为item_test的布局,其中有三个已分配了ID的视图声明,abc。当我使用item_test布局名称添加上述表单的合成导入时,突然x有三个新属性,abc。或者,更准确地说,编辑器看起来好像x具有这些属性。

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

  • 添加布局视图ID作为属性是盲目完成的。此类合成导入所隐含的任何视图ID都将添加到类View(或View的子类)的类的任何属性中。这包括由类继承的此类型的属性。

  • 由于属性是盲目添加的,因此开发人员有责任确保在访问合成属性或抛出异常之前分配与合成导入相对应的运行时视图。

  • 如果在文件中指定了两个或更多此类导入,则其视图ID的并集将被盲目地添加到View类型的所有类属性中。

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

这些结论是否正确?

围绕此功能的实施还有其他有趣的细微之处吗?

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

1 个答案:

答案 0 :(得分:2)

如果我理解正确,您将采取以下措施:

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上的扩展功能,因此无法进行健全性检查以检查item_testx的视图层次结构中是否存在y,就像他们不在findViewById。如果findViewById调用返回null,则值为null或抛出KotlinNullPointerException

  
      
  • 添加布局视图ID作为属性是盲目完成的。此类合成导入所隐含的任何视图ID都将添加到View类型(或View的子类)的任何属性中。这包括由类继承的此类型的属性。
  •   

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

  
      
  • 由于属性是盲目添加的,因此开发人员有责任确保在访问合成属性之前分配与合成导入相对应的运行时视图,否则将抛出异常。
  •   

我不确定你在这里问的是什么。如果您的意思是在调用x之前必须初始化x.item_test,那就没错。此外,x应在其层次结构中包含视图item_test

  • 如果在文件中指定了两个或更多这样的导入,那么它们的视图id的并集将被盲目地添加到View类型的所有类属性中。 允许多次导入的目的是考虑一个布局文件包含另一个布局文件的情况。

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