ViewBinding使用入门
Posted cfy137000
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ViewBinding使用入门相关的知识,希望对你有一定的参考价值。
ViewBinding
参考资料:
新技术 ViewBinding 最佳实践 & 原理击穿
更多 ViewBinding 的封装思路
1. kotlin-android-extensions(KAE) 的问题
根据Google官方的说法, KAE存在以下问题:
- 污染全局命名空间
- 不能暴露
可空性
信息 - 仅支持Kotlin代码
kotlin在1.4.20中 开始废弃这个库了(https://blog.jetbrains.com/kotlin/2020/11/kotlin-1-4-20-released/#Deprecation_of_Kotlin_Android_Extensions)
ReleaseNote中的原因是 KAT所填补的空白已经被ViewBinding所代替了, 所以他们不再支持了…
2. ViewBinding的使用
1. 开启ViewBinding
在model的build.gradle
中开启
android
viewBinding
enabled = true
之后每个xml布局都会生成对应ViewBinding类
如果想要忽略摸个布局文件, 可以添加tools:viewBindingIgnore="true"
属性
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
2. 基本用法
生成的ViewBinding类使用以下方式的命名规则:将 XML 文件的名称转换为驼峰式大小写,并在末尾添加“Binding”一词
例如, 有如下布局 名称为result_profile.xml
:
<LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>
所生成的绑定类的名称就为 ResultProfileBinding。此类具有两个字段:一个是名为 name 的 TextView,另一个是名为 button 的 Button。该布局中的 ImageView 没有 ID,因此绑定类中不存在对它的引用。
每个绑定类还包含一个 getRoot() 方法,用于为相应布局文件的根视图提供直接引用。在此示例中,ResultProfileBinding 类中的 getRoot() 方法会返回 LinearLayout 根视图。
每一个Binding类都提供了3中创建ViewBinding对象的方式, 可以根据情况选择:
位置:
ViewBinding的用法基本上都是在围绕着上面的3个方法
2.1 在Activity中只用
- 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Activity 使用。
- 通过调用 getRoot() 方法或使用 Kotlin 属性语法获取对根视图的引用。
- 将根视图传递到 setContentView(),使其成为屏幕上的活动视图。
private lateinit var binding: ResultProfileBinding
override fun onCreate(savedInstanceState: Bundle)
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
然后就可以使用对应的View了
binding.name.text = viewModel.name
binding.button.setOnClickListener viewModel.userClicked()
2.2 在Fragment中使用
- 调用生成的绑定类中包含的静态 inflate() 方法。此操作会创建该绑定类的实例以供 Fragment 使用。
- 通过调用 getRoot() 方法或使用 Kotlin 属性语法获取对根视图的引用。
- 从 onCreateView() 方法返回根视图,使其成为屏幕上的活动视图。
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View?
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
override fun onDestroyView()
super.onDestroyView()
_binding = null
2.3 在Adapter中使用ViewBinding
在Adapter中使用ViewBinding与之前没有什么不同
class SimpleDataAdapter : RecyclerView.Adapter<SimpleDataAdapter.ViewHolder>()
var dataList: List<Data> = emptyList()
set(value)
field = value
notifyDataSetChanged()
inner class ViewHolder(binding: ItemDataBinding) : RecyclerView.ViewHolder(binding.root)
val dataIv = binding.dataIv
val dataTv = binding.dataTv
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
val binding = ItemDataBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
override fun onBindViewHolder(holder: ViewHolder, position: Int)
dataList[position].let
holder.dataIv.setImageResource(it.img)
holder.dataTv.text = it.txt
override fun getItemCount(): Int = dataList.size
2.4 对引入布局使用ViewBinding
2.4.1 include
比如有如下布局: titlebar.xml
希望作为一个通用布局引入到其他布局中
<?xml version="1.0" encoding="utf-8"?>
<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的布局:
<?xml version="1.0" encoding="utf-8"?>
<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" />
</LinearLayout>
Activity中使用
class IncludeActivity : AppCompatActivity()
private lateinit var binding: ActivityIncludeBinding
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
binding = ActivityIncludeBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.titleBar.title.text = "Title"
binding.titleBar.back.setOnClickListener
binding.titleBar.done.setOnClickListener
这里可以看到 binding中根据id生成了一个titleBar的对象, 查看ViewBinding生成的代码可知, 这个titleBar还是一个ViewBinding的类:
2.4.2 merge
如果被引入的布局根标签是merge
, 则不能使用上述方式了, 疑问merge会将被引入的布局直接合并到对应的位置, 所以在ViewBinding中, 通过id找对应的ViewBinding的过程中会失败
相较于2.4.1的代码:
- 删除include标签上的id属性
- 在kotlin代码中, 自己赋值ViewBinding对象:
class MergeActivity : AppCompatActivity()
private lateinit var binding: ActivityMergeBinding
private lateinit var titlebarBinding: TitlebarMergeBinding
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
binding = ActivityMergeBinding.inflate(layoutInflater)
setContentView(binding.root)
titlebarBinding = TitlebarMergeBinding.bind(binding.root)
titlebarBinding.title.text = "Title"
titlebarBinding.back.setOnClickListener
titlebarBinding.done.setOnClickListener
3. 封装
封装的主要思路是尽量不写,或少写一些模板代码, 主要的思路就是在创建ViewBinding对象上实现自动创建
3.1 Activity
open class BaseReflectionAty<VB : ViewBinding> : AppCompatActivity()
protected lateinit var binding: VB
private set
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
generateViewBinding()
if (this::binding.isInitialized)
setContentView(binding.root)
binding.initViews()
open fun VB.initViews()
/**
* 生成ViewBinding
*/
private fun generateViewBinding()
val type = this::class.java.genericSuperclass as? ParameterizedType ?: return
val vbClazz = type.actualTypeArguments.find
// 找到泛型声明为 实现了 ViewBinding接口的类型
(it as Class<*>).genericInterfaces[0] == ViewBinding::class.java
as? Class<*> ?: return
val method = vbClazz.getMethod("inflate", LayoutInflater::class.java)
binding = method.invoke(null, layoutInflater) as VB
在onCreate时, 通过generateViewBinding
方法来生成对应的ViewBinding对象,使用时:
class MainActivity : BaseReflectionAty<ActivityMainBinding>()
override fun ActivityMainBinding.initViews()
mainTextView.setOnClickListener
Toast.makeText(this@MainActivity, "lalala", Toast.LENGTH_SHORT).show()
直接在initViews中使用布局文件中东的View即可
3.2 Fragment
Fragment的思路与Activity相同
class BaseReflectionFragment<VB : ViewBinding> : Fragment()
private var _binding: VB? = null
protected val binding: VB
get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View?
generateViewBinding(container)
if (_binding != null)
return binding.root
return super.onCreateView(inflater, container, savedInstanceState)
open fun VB.initViews()
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
_binding?.initViews()
override fun onDestroyView()
super.onDestroyView()
_binding = null
/**
* 生成ViewBinding
*/
private fun generateViewBinding(rootView: ViewGroup?)
val type = this::class.java.genericSuperclass as? ParameterizedType ?: return
val vbClazz = type.actualTypeArguments.find
// 找到泛型声明为 实现了 ViewBinding接口的类型
(it as Class<*>).genericInterfaces[0] == ViewBinding::class.java
as? Class<*> ?: return
val method = vbClazz.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
)
_binding = method.invoke(null, layoutInflater, rootView, false) as VB
3.3 Adapter
ViewHolder:
class BaseViewHolder<VB : ViewBinding>(val binding: VB) : RecyclerView.ViewHolder(binding.root)
Adapter:
abstract class ViewBindingAdapter<VB : ViewBinding> : RecyclerView.Adapter<BaseViewHolder<VB>>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<VB>
generateViewBinding(parent)?.let
return BaseViewHolder(it)
?: TODO("Not yet implemented")
private fun generateViewBinding(parent: ViewGroup): VB?
val type = this::class.java.genericSuperclass as? ParameterizedType ?: return null
val vbClazz = type.actualTypeArguments.find
// 找到泛型声明为 实现了 ViewBinding接口的类型
(it as Class<*>).genericInterfaces[0] == ViewBinding::class.java
as? Class<*> ?: return null
val layoutInflater = LayoutInflater.from(parent.context)
val method = vbClazz.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
)
return method.invoke(null, layoutInflater, parent, false) as VB
以上是关于ViewBinding使用入门的主要内容,如果未能解决你的问题,请参考以下文章