Kotlin Android Extensions: 与 findViewById 说再见 (KAD 04) -- 更新版
时间: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的一个插件,它包含在普通的那个插件中,这就允许以惊人的无缝方式从Activitie,Fragment和View中恢复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>
如你所见,TextView
有welcomeMessage 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)引用任何视图。 这意味着你可以引用一个视图,该视图不是其的直接子节点。 但是,当试图尝试恢复不存在的视图时,执行会失败。
- 在这种情况下,视图不像Activity和Fragment那样被缓存。
为什么会这样?与之前的情况相反,这里的插件没有地方可以为缓存生成所需的代码。
如果你再次查看代码,它是当从视图调用属性时由插件生成的,你会看到:
- ((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增加了一些有趣的特性,这些特性在以前的插件没有覆盖的情况下,是非常有用。
Kotlin Android Extensions: 与 findViewById 说再见 (KAD 04) -- 更新版的更多相关文章
- Kotlin Android Extensions (译文)
原文链接: http://kotlinlang.org/docs/tutorials/android-plugin.html Kotlin Android Extensions 本教程介绍如何使用Ko ...
- Android开发之程序猿必需要懂得Android的重要设计理念2(5.20更新版)
上篇文章介绍了Android开发的设计理念的一部分,并没有得到博友们的多大认可,仅仅看到了一位博友在以下留言期待下一篇文章的发表,为了这小小的唯一支持.我决定继续把后面的8个要点介绍一下,自己也潜心反 ...
- 即刻开始使用Kotlin开发Android的12个原因(KAD 30)
作者:Antonio Leiva 时间:Jul, 11, 2017 原文链接:https://antonioleiva.com/reasons-kotlin-android/ 这组文章已到最后了,它们 ...
- Kotlin Android学习入门
1.基本语法 https://github.com/mcxiaoke/kotlin-notes/blob/master/kotlin-tutorial-basic.md 2.推荐两篇Kotlin An ...
- Android高效率编码-findViewById()的蜕变-注解,泛型,反射
Android高效率编码-findViewById()的蜕变-注解,泛型,反射 Android的老朋友findViewById()篇! 先看看他每天是在干什么 //好吧,很多重复的,只不过想表达项目里 ...
- Kotlin, Android的Swift
Kotlin, Android的Swift 苹果已经用Swift代替Objective-C,一种古老的语言,来进行iOS的开发了.明显Android开发也有这个趋势. 虽然现在已经可以选择Scala或 ...
- Kotlin Android项目静态检查工具的使用
Kotlin Android项目静态检查工具的使用 Kotlin Android项目可用的静态检查工具: Android官方的Lint, 第三方的ktlint和detekt. 静态检查工具 静态检查工 ...
- Kotlin & Android & Swift & Flutter & React Native
Kotlin & Android https://www.runoob.com/kotlin/kotlin-tutorial.html Swift 5 & iOS 12 https:/ ...
- 111 01 Android 零基础入门 02 Java面向对象 04 Java继承(上)02 继承的实现 01 继承的实现
111 01 Android 零基础入门 02 Java面向对象 04 Java继承(上)02 继承的实现 01 继承的实现 本文知识点: 继承的实现 说明:因为时间紧张,本人写博客过程中只是对知识点 ...
随机推荐
- PAT——1016. 部分A+B
正整数A的“DA(为1位整数)部分”定义为由A中所有DA组成的新整数PA.例如:给定A = 3862767,DA = 6,则A的“6部分”PA是66,因为A中有2个6. 现给定A.DA.B.DB,请编 ...
- hdu 1026 Ignatius and the Princess I(BFS+优先队列)
传送门: http://acm.hdu.edu.cn/showproblem.php?pid=1026 Ignatius and the Princess I Time Limit: 2000/100 ...
- 如何清理Macbook垃圾文件
如何清理Macbook垃圾文件,腾出更多硬盘空间 在Macbook使用久之后,会发现本来还富裕的硬盘,变得越来越少,尤其现在Macbook使用容量很小的固态硬盘.在此种情况下,该如何清理Macbook ...
- plsql误删除数据,提交事务后如何找回?
select * from tbs_rep_template as of timestamp to_timestamp('2018-07-12 14:23:00', 'yyyy-mm-dd hh24: ...
- js常用共同方法
var uh_rdsp = (function(){ //获取根目录 var getContextPath = function(){ var pathName = document.location ...
- 动态树LCT(Link-cut-tree)总结+模板题+各种题目
一.理解LCT的工作原理 先看一道例题: 让你维护一棵给定的树,需要支持下面两种操作: Change x val: 令x点的点权变为val Query x y: 计算x,y之间的唯一的最短路径的点 ...
- 竞赛题解 - Ikki's Story IV-Panda's Trick
Ikki's Story IV-Panda's Trick - 竞赛题解 也算是2-sat学习的一个节点吧 终于能够自己解决一道2-sat的题了 ·题目 一个圆上有n个点按顺时针编号为 0~n-1 , ...
- show status 查看各种状态
要查看MySQL运行状态,要优化MySQL运行效率都少不了要运行show status查看各种状态,下面是参考官方文档及网上资料整理出来的中文详细解释: 如有问题,欢迎指正 状态名 作用域 详细解释 ...
- 复习宝典之MyBatis
查看更多宝典,请点击<金三银四,你的专属面试宝典> 第五章:MyBatis MyBatis是一个可以自定义SQL.存储过程和高级映射的持久层框架. 1)创建sqlsession的流程 my ...
- 浅谈HashMap与线程安全 (JDK1.8)
HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型.HashMap 继承自 AbstractMap 是基于哈希表的 Map 接口的实现,以 Key-Value 的形式存在,即 ...