Android插件findview,Kotlin Android扩展和findViewById说再见

本文链接

本文结合自己的感受,做一下简单的翻译。原文作者也是《Kotlin for Android developer》的作者。此译文供大家学习参考之用。

你们大概已经厌倦了日复一日使用findViewById来获取Android的页面元素,或者很可能你们已经放弃这样,使用闻名的ButterKnife库来实现。接下来你会喜欢Kotlin Android 扩展库的。

Kotlin Android 扩展库是什么?

Kotlin Android 扩展库是另外一种Kotlin常规插件,它使用一种神奇的方式,让你从Activity、Fragment和View这些元素集合中无缝获取view元素。这个插件生成的代码让你访问布局文件中的元素,就像访问属性一样,可以直接使用布局文件中的ID名称访问。它也构建了一个view缓存,当你第一次使用这个属性的时候,它会去做findViewById操作。但是第2次,这个就直接从缓存中获取view,所以访问起来就更快。

怎么使用他们

让我们看一下使用起来多简单。我先用一个Activity来做第一个例子:

在我们代码中集成Kotlin Android扩展库

虽然这个扩展插件将要集成到主库中(你不必新安装一个),但是目前,如果你要使用它,你不得不在Android 模块配置中添加一个扩展配置。

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

这些就是你全部要做的。现在你就可以开始用它工作了。(不知道是不是因为版本的问题,配置貌似没有这么简单有一些小坑,项目和App下面需要重复配置kotlin的依赖)

从布局XML中获取到页面元素

此时,在你的Activity中获取页面元素就和直接在XML中使用元素ID一样简单。想象一下你有如下的布局XML文件:

xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/welcomeMessage"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:text="Hello World!"/>

你可以看到TextView的ID是welcomeMessage。然后到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.*

我上面提到代码,其实生成的代码包含页面元素缓存,因此你再次获取这个页面元素的时候,就不需要再使用findViewById方法了。

让我们看一下实际使用情况吧。

Kotlin Android 神奇的扩展支持

当你开始用Kotlin工作,在使用其中的某些特性的时候,你会有兴趣去了解那些生成的字节码。

这里有一个强大的操作,在AS的菜单中,Tools –> Kotlin –> 显示Kotlin字节码。如果你点击它,你会看到你打开的已经编译过的Kotlin文件生成的字节码。这些字节码对大部分人来说未必有用,但是有另外的选项就是反编译。(在AS Kotlin Bytecode 窗口里面有反编译按钮)这样你会看到一个由Kotlin生成的,用Java表示的字节码。因此你能了解更多Kotlin和Java等价的写法。我正想在我的Activity这样做,然后看一下Kotlin扩展插件生成的kotlin。(比对Java和Kotlin等价的代码可以让从Java转换到Kotlin的使用过程中,帮助大家更好的理解Kotlin)

下面就是有趣的部分之一:

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();

}

}

这些就是我们之前说的页面元素缓存。

当我们想要获取一个页面元素的时候,首先会试图在缓存中找到它。如果不在缓存中,它会直接取这个页面元素,并且把这个页面元素缓存起来。其实就这么简单。同时,也添加了一个清空缓存的方法clearFindViewByIdCache。当你重新构建页面元素,这些旧的页面元素不再有效的时候,你可以用它。

然后下面这行:

welcomeMessage.text = "Hello Kotlin!"

被转换成如下:

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

因此这些属性不是真实的,这个插件不是用来生成每个页面元素属性的。在编译后代码被替换成访问页面元素缓存,并且调用相应方法及转成适合的类型。

在Fragment上使用Kotlin Android扩展库

这个插件也能在Fragment上面使用。

Fragment的情况是这些页面元素会被重新生成,但是Fragment实例会被保持。然后会发生什么呢?这个意味着缓存中的页面元素不再长期有效。

让我们看一下在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();

}

当这些Fragment开始销毁,这个方法会调用clearFindViewByIdCache,因此我们这样使用是安全的。

在自定义View上使用Kotlin Android扩展库

在自定义视图上也是类似的方式。我们有一个试图如下:

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/itemImage"

android:layout_width="match_parent"

android:layout_height="200dp"/>

android:id="@+id/itemTitle"

android:layout_width="match_parent"

android:layout_height="wrap_content"/>

我创建一个非常简单的自定义视图,使用@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的文本。生成代码试图在缓存中获取到页面元素。不需要再次复制相同的代码,但是你能看到文本的变化。

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

赞!在自定义视图中我们也只是第一次使用findViewById方法。

从其他视图获取一些页面元素

另外Kotlin Android 扩展库提供了从其他视图使用属性直接访问的能力。我使用和前面相似的布局,假设通过一个Adapter实例来渲染页面。

你就能通过扩展库直接访问这个子视图:

val itemView = ...

itemView.itemImage.setImageResource(R.mipmap.ic_launcher)

itemView.itemTitle.text = "My Text"

虽然插件能通过import帮你引入,还是有一些不同。

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

还有一些你需要了解的:

• 在编译时,你能从其他视图引用它的任意子元素。这意味着你能从这个视图引用非直接子元素的。但是在执行时由于插件试图获取不存在的页面元素时可能失败。

• 这个例子,这个页面元素没有被缓存在Activity和fragment中。

为什么呢?和前面的例子截然不同,这里插件没有使用缓存来替换生成代码。如果你查看插件生成代码,你可以看到当它从视图中获取属性,看到是如下代码:

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

如你看到的,这次不是从缓存中获取的。要小心,如果你的视图是复杂的并且使用的是Adapter。它可能对性能有影响。

或者可以使用Kotlin1.1.4。

在Kotlin1.1.4中使用Kotlin Android扩展库

从新版Kotlin开始,Android扩展库结合了一些新的有趣的特性:在任何类中缓存(包括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,来访问其子元素了。

而且,你检查一下生成的代码,你会看到从缓存中获取View。

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

我在ViewHolder中使用它,但是你能看到它也能被用来在任何其他类上面使用。

用Kotlin Android扩展库来实现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)~~~

并且在任何点从intent中接收这个对象(这个例子是在目标Activity内):

~~~ ruby

val model: Model = intent.getParcelableExtra(EXTRA)~~~

#### 自定义缓存构建方式

一个新的特性被包含在实验性配置中,一个新的叫@ContainerOptions注解。这个注解允许你自定义缓存构建,甚至可以在创建的时候阻止一个类使用缓存。默认,它会使用Hashmap进行缓存,在我们看之前。但是在Android框架中会使用SparseArray来替换,在相同情况下更有效率。或者,有一些理由,你不想缓存一些类,你也可以选择这样做。

这是怎么使用的:

~~~ ruby

@ContainerOptions(CacheImplementation.SPARSE_ARRAY)

class MainActivity : AppCompatActivity() {

...

}

当然,缓存方式可选项如下:

public enum class CacheImplementation {

SPARSE_ARRAY,

HASH_MAP,

NO_CACHE;

...

}

结论

用Kotlin你能看到获取Android页面元素是多么简单。用简单的插件,我们能忘掉所有那些从视图中获取元素的可怕代码。插件会帮助我们创建必要的元素,并转成对的类型,还没有问题。

而且Kotlin1.1.4加了一些有趣的特性,对一些使用场景有帮助,是之前的插件版本没有覆盖到的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值