作者:Antonio Leiva

时间:Aug 16, 2017

原文链接:https://antonioleiva.com/kotlin-android-extensions/

在 Kotlin1.1.4版本 发布后,原作者依据 Kotlin 新版本的一系列新特性,以及有读者关于如何在 Fragment 和 custom view 中使用Kotlin 等等向他提问,原作者决定针对这些内容进行更新、重写几个月的文章。

在这篇重写的文章中,他涵盖了所有KAE(1.1.4版本前后)可以完成的事情。现在你会喜欢在任何类(不只是activity, fragment 或 view)使用它们,包括一个新的注释来实现Parcelable。

你可能会厌倦日复一日地使用findViewById来恢复Androidview。或许你已放弃了,并开始使用著名的Butterknife库。那么你会喜欢上Kotlin Android Extensions。

Kotlin Android Extensions:这是什么?

Kotlin Android Extensions是Kotlin的一个插件,它包含在普通的那个插件中,这就允许以惊人的无缝方式从ActivitieFragmentView中恢复View。

该插件将生成一些额外的代码,允许你访问布局XML的View,就像它们是在布局中定义的属性一样,你可以使用 id 的名称。

它还构建本地视图缓存。所以首次使用一个属性时,它会做一个普通的findViewById。而接下来,View则是从缓存中恢复,因此访问速度更快。

怎样使用它们

让我们看看它的使用是多么容易。我会以一个Activity做第一个例子:

将Kotlin Android Extensions集成到我们的代码中

虽然这个插件被集成到普通的插件(你不需要安装新的插件)中,但是,你要使用它,还必须在Android模块中添加额外的应用:

 apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

只需要做这些,可以开始使用它了。

从XML中恢复 View

从这一刻起,恢复View就像将你在XML中定义的View ID直接用于你的Activity一样简单。

假设你有一个这样的XML:

 <?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <TextView
android:id="@+id/welcomeMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Hello World!"/> </FrameLayout>

如你所见,TextViewwelcomeMessage ID。

在你的 MainActivity 中,仅仅需要这样编写:

 override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) welcomeMessage.text = "Hello Kotlin!"
}

为了能够使用它,你需要如下的import,而IDE能够自动加入它。不是很容易吗?

 import kotlinx.android.synthetic.main.activity_main.*

如上所述,生成的代码将包括View缓存,因此再次询问视图,就不需要另一个findViewById。

让我们看看其背后都是什么。

Kotlin Android Extensions背后的魔力

在你开始使用Kotlin时,理解所使用特性时生成的字节码是非常有趣。这也有助于你了解在你的决定背后隐藏的成本

在Tools-->Kotlin下,有一个强大的功能,称为显示Kotlin字节码(Show Kotlin Bytecode。如果你点击它,你将看到当你打开的类文件编译后生成的字节码。

对大多数人来说,字节码并不是很有用,但是还有另一个选择:Decompile(反编译)

这将显示Kotlin生成的字节码的Java表示。所以你可以或多或少地了解你写的Kotlin代码对应Java的代码。

在我生成的Activity中使用它,并查看由Kotlin Android Extensions生成的代码。

有趣的是这一个:

 private HashMap _$_findViewCache;
...
public View _$_findCachedViewById(int var1) {
if(this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
} View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
if(var2 == null) {
var2 = this.findViewById(var1);
this._$_findViewCache.put(Integer.valueOf(var1), var2);
} return var2;
} public void _$_clearFindViewByIdCache() {
if(this._$_findViewCache != null) {
this._$_findViewCache.clear();
} }

这就是我们正在谈论的视图缓存(View Cache

在要求查看时,首先会尝试在缓存中找。如果它不存在,它会查找它,并将其添加到缓存。非常简单。

此外,它还添加了一个清除缓存的功能:clearFindViewByIdCache。如果你必须重建视图,因为旧视图将不再有效,就可以使用它。

那么这一行:

 welcomeMessage.text = "Hello Kotlin!"

则转换为:

 ((TextView)this._$_findCachedViewById(id.welcomeMessage)).setText((CharSequence)"Hello Kotlin!");

因此,属性不是真实的,插件不会为每个视图生成属性。在编译期间,只需要替换代码即可访问视图缓存,将其转换为正确的类型并调用该方法。

Fragment的 Kotlin Android Extensions

这个插件也能够用于Fragment。

Fragment的问题是View可以重新创建,而Fragment实例却保持有效。这会怎么样?这就意味着缓存中的视图将不再有效。

如果我们把View移动到Fragment中,我们来看看生成的代码。这是我创建这个简单的Fragment,它使用与上面写的相同的XML:

 class Fragment : Fragment() {

     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment, container, false)
} override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
welcomeMessage.text = "Hello Kotlin!"
}
}

onViewCreated中,我再次更改TextView的文本。生成怎样的字节码呢?

一切都与Activity中的一样,仅有一个微小的区别:

 // $FF: synthetic method
public void onDestroyView() {
super.onDestroyView();
this._$_clearFindViewByIdCache();
}

当View被销毁时,此方法将调用clearFindViewByIdCache,所以我们是安全的!

自定义View的Kotlin Android extensions

在自定义视图下,Kotlin Android extensions非常类似。假设我们有这样的View:

 <merge xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"> <ImageView
android:id="@+id/itemImage"
android:layout_width="match_parent"
android:layout_height="200dp"/> <TextView
android:id="@+id/itemTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"/> </merge>

我创建一个非常简单的自定义视图,并使用@JvmOverloads注释的新Intent生成构造函数:

 class CustomView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) { init {
LayoutInflater.from(context).inflate(R.layout.view_custom, this, true)
itemTitle.text = "Hello Kotlin!"
}
}

在上面的示例中,我将文本更改为itemTitle。生成的代码应该尝试从缓存中查找View。再次复制所有相同的代码已无意义了,但是你可以在更改文本的行中看到这一点:

  ((TextView)this._$_findCachedViewById(id.itemTitle)).setText((CharSequence)"Hello Kotlin!");

太棒了!在自定义View中,我们只是首次调用findViewById

从另一个View中恢复View

Kotlin Android Extensions提供的最后一个选择是直接从另一个视图使用属性

我用与上节非常相似的布局。假设一下这是在适配器(Adapter)中对实例进行inflate。

使用此插件,你还可以直接访问子视图(subview):

 val itemView = ...
itemView.itemImage.setImageResource(R.mipmap.ic_launcher)
itemView.itemTitle.text = "My Text"

虽然这个插件会帮你填写import,但是在这方面还是有点不同:

 import kotlinx.android.synthetic.main.view_item.view.*

这里有几件事情,你需要了解:

  • 在编译时,你可以从任何其他视图(View)引用任何视图。 这意味着你可以引用一个视图,该视图不是其的直接子节点。 但是,当试图尝试恢复不存在的视图时,执行会失败。
  • 在这种情况下,视图不像ActivityFragment那样被缓存。

为什么会这样?与之前的情况相反,这里的插件没有地方可以为缓存生成所需的代码。

如果你再次查看代码,它是当从视图调用属性时由插件生成的,你会看到:

 ((TextView)itemView.findViewById(id.itemTitle)).setText((CharSequence)"My Text");

如你所见,没有调用缓存。如果你的视图很复杂,而且你是在适配器中使用,请注意,它可能会影响性能。

或者你可以选择:Kotlin 1.1.4

版本1.1.4中的Kotlin Android Extensions

Kotlin新版本中,Android Extensions已经引入了一些新的有趣的功能:任何类中的缓存(有趣的包括ViewHolder)和一个新的@Parcelize注释。还有一种方法可以自定义生成的缓存。

稍后,我们会看到它们,但你需要知道这些功能并不是最终的版本所以你需要将它们添加到build.gradle中来启动它们

 androidExtensions {
experimental = true
}

在ViewHolder(或任何自定义类)中使用它

现在,你可以通过简单的方式在任何类中构建缓存。唯一要求是你的类要实现LayoutContainer接口。该接口将提供一个视图,它是由插件查找的子视图。假设,我们有一个ViewHolder,它保持前面示例中讲述的布局视图。你只需要做:

 class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
LayoutContainer { fun bind(title: String) {
itemTitle.text = "Hello Kotlin!"
}
}

containerView是我们从LayoutContainer接口覆盖的。但这就是你需要的。

之后,你就可以直接访问视图,无需使用前置itemView来访问子视图。

另外,如果你检查生成代码,你会看到它从缓存中获取视图:

 ((TextView)this._$_findCachedViewById(id.itemTitle)).setText((CharSequence)"Hello Kotlin!");

这里,我已经在ViewHolder上使用了它,但是你可以看到这是通用的,完全可以在任何类中使用。

Kotlin Android Extension实现Parcelable

用新的@Parcelize注释,你可以以非常简单的方式使任何类都能实现Parcelable

你只需要添加注释,插件就会完成所有复杂的工作:

 @Parcelize
class Model(val title: String, val amount: Int) : Parcelable

然后,如你所知,你可以将对象添加到任何intent中:

 val intent = Intent(this, DetailActivity::class.java)
intent.putExtra(DetailActivity.EXTRA, model)
startActivity(intent)

并且在任何位置(在这种情况下是在在目标Activity)上,从intent恢复对象:

 val model: Model = intent.getParcelableExtra(EXTRA)

自定义构建缓存

在这组实验中包含的新功能是一个新注释@ContainerOptions。这允许你以自定义方式构建缓存,甚至阻止类创建它

默认情况下,它将用Hashmap,如我们之前看到的那样。但是,这可以用Android框架的SparseArray来改变,这在某些情况下可能会更有效率。如果由于某种原因,你不需要一个类的缓存,你也可以使用该选项。

这是它的用法:

 @ContainerOptions(CacheImplementation.SPARSE_ARRAY)
class MainActivity : AppCompatActivity() {
...
}

目前,已有的选择是:

 public enum class CacheImplementation {
SPARSE_ARRAY,
HASH_MAP,
NO_CACHE; ...
}

结论

你已经看到Kotlin处理Android视图的如此简易。通过一个简单的插件,我们可以抛弃那些在inflation后视图恢复的所有可怕的代码。此插件将为我们创建所需的属性,而不会出现任何问题。

此外,Kotlin 1.1.4增加了一些有趣的特性,这些特性在以前的插件没有覆盖的情况下,是非常有用。

最新文章

  1. android中工作线程安全
  2. 怎样设置才能允许外网访问MySQL
  3. Web Project配置Hirbernate
  4. Ubuntu下开启root登陆
  5. Just like normal variables,
  6. Java 基础知识(一)
  7. SAP文件的上传下载 SMW0,二进制文件
  8. Maven中解决依赖冲突的问题
  9. 请详细描述(以硬盘启动)Linux系统从打开主机电源到进入登录界面整个过程的流程。
  10. Android Gradle defaultConfig详解及实用技巧
  11. phpstorm 如何设置识别vue文件
  12. WebSocket——为Web应用带来桌面应用般的灵活性【转载+整理】
  13. 【刷题】LOJ 6122 「网络流 24 题」航空路线问题
  14. Resolve PSExec &quot;Access is denied&quot;
  15. ios网络层优化深入浅出
  16. [转载]一步一步教你如何在Virtualbox虚拟机中安装Remix
  17. bzoj 2434 AC自动机+树状数组
  18. 【51NOD】1096 距离之和最小
  19. flex布局在ios8上的兼容性问题
  20. Flume架构及运行机制

热门文章

  1. SSH框架——(二)四层结构:DAO,Service,Controller,View层
  2. [转]MFC子线程更改图像数据后更新主窗口图像显示方法
  3. C# .NET开发图形图像程序时提示&quot;GDI+ 中发生一般性错误&quot;
  4. P1018 乘积最大(高精度加/乘)
  5. 利用SQL模糊匹配来验证字段是否是日期格式
  6. ARM Linux 内核 panic 之cache 一致性 ——cci-400 cache一致互联
  7. I/O复用——各种不同的IO模型
  8. Java项目排查cpu负载高
  9. DB数据源之SpringBoot+MyBatis踏坑过程(二)手工配置数据源与加载Mapper.xml扫描
  10. HDU1398 Square Coins(生成函数)