赞
踩
参考资料:
新技术 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
希望作为一个通用布局引入到其他布局中
<?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的类:
如果被引入的布局根标签是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 } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。