参考资料:
新技术 ViewBinding 最佳实践 & 原理击穿
更多 ViewBinding 的封装思路
根据Google官方的说法, KAE存在以下问题:
可空性
信息kotlin在1.4.20中 开始废弃这个库了(https://blog.jetbrains.com/kotlin/2020/11/kotlin-1-4-20-released/#Deprecation_of_Kotlin_Android_Extensions)
ReleaseNote中的原因是 KAT所填补的空白已经被ViewBinding所代替了, 所以他们不再支持了…
在model的build.gradle
中开启
android {
viewBinding {
enabled = true
}
}
之后每个xml布局都会生成对应ViewBinding类
如果想要忽略摸个布局文件, 可以添加tools:viewBindingIgnore="true"
属性
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
LinearLayout>
生成的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个方法
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() }
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
}
在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
}
比如有如下布局: 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的布局:
<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的类:
如果被引入的布局根标签是merge
, 则不能使用上述方式了, 疑问merge会将被引入的布局直接合并到对应的位置, 所以在ViewBinding中, 通过id找对应的ViewBinding的过程中会失败
相较于2.4.1的代码:
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 {
}
}
}
封装的主要思路是尽量不写,或少写一些模板代码, 主要的思路就是在创建ViewBinding对象上实现自动创建
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即可
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
}
}
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
}
}