企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
对于Android开发者来说,对findViewById()这个方法一定不会陌生:在我们对视图控件操作前,我们需要通过findViewById方法来找到其对应的实例。因为一个界面里视图控件的数量可能会非常多,所以在Android开发早期我们通常都会看到一大片的`findViewById(R. id.view_id)`样板代码。举一个最常见的例子: ``` import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.Button; import android.widget.EditText; public class LoginActivity extends AppCompatActivity { Button loginButton; EditText nameEditText; EditText passwordEditText; … @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); loginButton = findViewById(R.id.btn_login); nameEditText = findViewById(R.id.et_name); passwordEditText = findViewById(R.id.et_password); … } } ``` 在一个登录界面中,至少包含登录按钮(loginButton)、登录名输入框(nameEditText)、密码输入框(passwordEditText),实际情况只会更复杂。在老版本SDK中,findBiewById获取到的类型是View,我们还需要进行类型强制转换。 ``` loginButton = (Button)findViewById(R.id.btn_login); ``` 幸运的是,在Kotlin中我们可以利用扩展函数来简化这个烦琐的过程: ``` fun <T : View> Activity._view(@IdRes id: Int): T { return findViewById(id) as T } ``` 现在,在兼容老版本的情况下,我们可以将代码改为这样: ``` loginButton =_view(R.id.btn_login); nameEditText = _view(R.id.et_name); passwordEditText = _view(R.id.et_password); ``` 现在调用起来是比较方便了,但是部分极简主义的读者可能会想:当前我们还是需要创建loginButton、nameEditText、passwordEditText的实例,但是这些实例似乎只充当了一个“临时变量”的角色,我们依靠它进行一些点击事件绑定(onlick)、赋值操作后好像就没什么用处了。能不能将其也省略掉,直接对`R.id.*`操作呢?答案是可以,在Kotlin中我们可以利用高阶函数,做如下改动(此处以简化onclick为例子): ``` fun Int.onClick(click: ()->Unit){ // _view为我们之前定义的简化版findViewById val tmp = _view <View>(this).apply { setOnClickListener{ click() } } } ``` 我们就可以这样绑定登录按钮的点击事件: ``` R.id.btn_login.onClick { println("Login…") } ``` 可能有强迫症的读者会受不了`R.id.xx`这样的写法,并且每次都要写R.id前缀,某种情况下也会造成烦琐。那还有更简洁的写法吗?答案是肯定的,Kotlin为我们提供了扩展插件,gradle默认就集成了: ``` apply plugin: 'kotlin-android-extensions' ``` 回到最原始的LoginActivity,我们只用额外`import kotlinx.android.synthetic.main.activity_login.*`,即可直接用视图中组件的id名称来操作视图: ``` btn_login.setOnClickListener { println("MainKotlinActivity onClick Button") } ``` 这时候,或许还会有读者疑惑:虽然是省略了`R.id.`几个字符,但是引入是否会造成性能问题?值得引入、使用kotlin-android-extensions吗?还是用惯常的做法,让我们先对其反编译,看看其对应Java代码中是如何实现的: ``` public final class MainActivity extends BaseActivity { private HashMap _$_findViewCache; protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(2131296283); ((Te xtView)this._$_findCachedViewById(id.label)).setText((CharSequence)"Dive Into Kotlin"); ((Te xtView)this._$_findCachedViewById(id.label)).setOnClickListener((OnClickListener)null.INSTANCE); ((Bu tton)this._$_findCachedViewById(id.btn)).setOnClickListener((OnClickListener)null.INSTANCE); } 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(); } } } ``` 你会惊喜地发现,在第一次使用控件的时候,在缓存集合中进行查找,有就直接使用,没有就通过findViewById进行查找,并添加到缓存集合中。其还提供了`$clearFindViewById Cache()`方法用于清除缓存,在我们想要彻底替换界面控件时可以使用。 ***** 注意 Fragment的`onDestroyView()`方法中默认调用了`$clearFindViewByIdCache()`清除缓存,而Activity没有。 ***** 当然,我们**并没有完全离开findViewById,只是Kotlin的扩展插件利用缓存的方式让我们开发更方便、更快捷**。 还有很多场景都利用了扩展函数,由于篇幅的限制,这里不再介绍。感兴趣的读者可以了解一下Google推出的Android扩展库Android KXT(https://github.com/android/android-ktx)。