赞
踩
在使用Java开发Android程序时,我们总是要写一大堆的findViewById
,枯燥又没什么意义,因此针对这个问题也出了很多开源库来解决,例如:Butter Knife(黄油刀)。但是ButterKnife还是要通过注解来让控件与资源id
之间进行绑定,并不算是非常方便。我去GitHub上看,发现在库说明的开始位置有下面的提示:
嗯。。。废弃了,已经直接让你转去看view binding
了。
在学习kotlin的时候,又学到了kotlin-android-extensions
插件,当时用的时候真是感觉像发现了新大陆一样,非常好用。之前AS版本新建Kotlin项目会自动导入这个插件,现在使用的话需要手动加上。
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
导入插件后,用到的地方直接以view id
拿就行。例如:
<TextView
android:id="@+id/tv_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv_msg.text = "Hello"
}
}
但是,当你的gradle升级到新版本的时候,会显示下面的提示。Google明确地告诉我们,kotlin-android-extensions
插件已被废弃,现在推荐使用ViewBinding
来进行替代。
很奇怪,这么好用的插件为啥要废弃,查了下主要有如下几个原因:
内存问题:根据kotlin-android-extensions
插件源码,它使用了一个HashMap
来存放所有的id
和对应的View
的缓存,如果缓存中有需要的View
,就直接获取,否则就通过findViewById
去创建,并写入HashMap
缓存当中,这样当下次再获取相同控件实例的话,就可以直接从HashMap
缓存中获取了,这就是它的原理。
额外的HashMap
数据结构来存储所有控件的实例,无形中增加了一些内存的开支。参考:kotlin-android-extensions插件也被废弃了?扶我起来
资源ID重名:由于kotlin-android-extensions
是通过view
的id
名直接引用的,所以多个布局间的同名id
,就需要手动对import
进行重命名处理,而且经常会引用错误的布局文件,导致运行崩溃。
Kotlin only:只能在Kotlin
中使用,无法在Java
项目中使用。
既然官方主推这个ViewBinding
,让我们来学习下。
想使用ViewBinding需要注意两件事:
android {
...
buildFeatures {
viewBinding true
}
}
一旦启动了ViewBinding
功能之后,Android Studio会自动为我们所编写的每一个布局文件都生成一个对应的Binding类。
Binding
类的命名规则是将布局文件按驼峰方式重命名后,再加上Binding
作为结尾。比如说,前面我们定义了一个activity_main.xml
布局,那么与它对应的Binding类就是ActivityMainBinding
。同理,如果是activity_login.xml
对应的Binding类就是ActivityLoginBinding
。
当然,如果有些布局文件你不希望为它生成对应的Binding
类,可以在该布局文件的根元素位置加入如下声明:
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
...
tools:viewBindingIgnore="true">
...
</LinearLayout>
看下在Activity中的使用:
class MainActivity : BaseActivity() {
private lateinit var mBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBinding.root)
mBinding.forceOffLine.setOnClickListener {
...
}
}
}
是的,就是这么简单,首先获取Binding
类,然后把根元素的实例传入到setContentView()
函数当中,这样Activity就可以成功显示activity_main.xml
这个布局的内容了。后面直接使用mBinding.forceOffLine
来获取按钮view
就可以了。
在Fragment中使用ViewBinding和在Activity基本是一样的,来看下代码:
class MainFragment : Fragment() { private var mMainBinding: FragmentMainBinding? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { mMainBinding = FragmentMainBinding.inflate(inflater, container, false) return mMainBinding?.root } override fun onDestroyView() { super.onDestroyView() mMainBinding = null } }
因为mMainBinding
在onCreateView
进行了初始化,所以在onDestroyView
需要置空,因此mMainBinding
需要设置为可空类型,在返回root
的时候需要判空。
class InfoAdapter(val infoList: List<String>) : RecyclerView.Adapter<InfoAdapter.ViewHolder>() { private lateinit var mBinding: ItemInfoBinding //2.继承类中传入binding.root,然后根据binding获取item中的view inner class ViewHolder(binding: ItemInfoBinding) : RecyclerView.ViewHolder(binding.root) { val tvInfo: TextView = binding.infos } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { //1.获取ItemInfoBinding mBinding = ItemInfoBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(mBinding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.tvInfo.text = infoList[position] } override fun getItemCount(): Int = infoList.size }
在Adapter
中主要修改两个地方,一个是在onCreateViewHolder
中获取ItemInfoBinding
,然后传入到ViewHolder
中。然后在ViewHolder
中根据传入的ItemInfoBinding
获取view来替代findViewById
。不过每个view
也需要一一获取,和之前的findViewById
相比,我觉得在Adapter
中优化的能力一般。
下面是定义的titlebar.xml布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:text="Back" /> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Title" android:textSize="20sp" /> <Button android:id="@+id/done" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:text="Done" /> </RelativeLayout>
在activity_main.xml
中引入这个布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
layout="@layout/titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...
</LinearLayout>
此时使用ActivityMainBinding
时无法拿到titlebar
中的控件的。想拿到控件需要在include
的时候给被引入的布局添加一个id
,当成一个普通的控件,代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/titleBar"
layout="@layout/titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...
</LinearLayout>
此时可通过mBinding
获取到引入布局控件。
class MainActivity : BaseActivity() {
private lateinit var mBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mBinding.root)
mBinding.titleBar.title.setText("talk")
}
}
merge
和include
最大的区别在于,使用merge
标签引入的布局在某些情况下可以减少一层布局的嵌套,而更少的布局嵌套通常就意味着更高的效率。
将上面的布局改下:
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <Button android:id="@+id/back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:text="Back" /> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Title" android:textSize="20sp" /> <Button android:id="@+id/done" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:text="Done" /> </merge>
使用了merge
标签,会将merge
标签内包含的内容直接填充到include
的位置,不会再添加任何额外的布局结构。
但是如果只修改上面的titlebar.xml
,程序运行程序将会直接崩溃(编译可以通过)。因为merge
标签并不是一个布局,所以我们无法像刚才那样在include
的时候给它指定一个id
。会报下面错误:
java.lang.NullPointerException: Missing required view with ID: com.jane.demo:id/titlebar
所以需要修改下activity_main.xml
去掉指令的id
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
layout="@layout/titlebar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...
</LinearLayout>
从上面测试知道,没有id
的话就无法拿到引入布局的控件,所以这时需要修改下代码:
class MainActivity : BaseActivity() {
private lateinit var mBinding: ActivityMainBinding
private lateinit var mTitleBarBinding: TitlebarBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMainBinding.inflate(layoutInflater)
mTitleBarBinding = TitlebarBinding.bind(mBinding.root)
setContentView(mBinding.root)
mTitleBarBinding.title.setText("Title")
}
}
我们调用TitlebarBinding.bind()
函数,让titlebar.xml
布局和activity_main.xml
布局能够关联起来。
以上是ViewBinding
的使用学习,码字不易,如果有帮助到大家请点赞收藏。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。