Android - ViewBinding学习使用

一、为什么学

在使用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来进行替代。

gradel升级报错
很奇怪,这么好用的插件为啥要废弃,查了下主要有如下几个原因:

  • 内存问题:根据kotlin-android-extensions插件源码,它使用了一个HashMap来存放所有的id和对应的View的缓存,如果缓存中有需要的View,就直接获取,否则就通过findViewById去创建,并写入HashMap缓存当中,这样当下次再获取相同控件实例的话,就可以直接从HashMap缓存中获取了,这就是它的原理。
    额外的HashMap数据结构来存储所有控件的实例,无形中增加了一些内存的开支。参考:kotlin-android-extensions插件也被废弃了?扶我起来

  • 资源ID重名:由于kotlin-android-extensions是通过viewid名直接引用的,所以多个布局间的同名id,就需要手动对import进行重命名处理,而且经常会引用错误的布局文件,导致运行崩溃。

  • Kotlin only:只能在Kotlin中使用,无法在Java项目中使用。

既然官方主推这个ViewBinding,让我们来学习下。

二、ViewBinding使用

想使用ViewBinding需要注意两件事:

  • 第一,确保你的Android Studio是3.6或更高的版本;
  • 第二,项目工程模块的build.gradle中加入以下配置:
android {
    ...
    buildFeatures {
        viewBinding true
    }
}

1、在Activity中使用ViewBinding

一旦启动了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就可以了。

2、在Fragment中使用ViewBinding

在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
    }
}

因为mMainBindingonCreateView 进行了初始化,所以在onDestroyView需要置空,因此mMainBinding需要设置为可空类型,在返回root的时候需要判空。

3、在Adapter中使用ViewBinding

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中优化的能力一般。

4、对引入布局使用ViewBinding

  • 引入布局include

下面是定义的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

mergeinclude最大的区别在于,使用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的使用学习,码字不易,如果有帮助到大家请点赞收藏。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值