如何将 ViewBinding 与抽象基础一起使用 class
How using ViewBinding with an abstract base class
我开始使用 ViewBinding。在搜索了一些示例或建议如何将 ViewBinding 与抽象基础 class 一起使用后,该抽象基础应该处理预期出现在每个子布局中的视图的相同逻辑,我最终在这里发布了这个问题。
场景:
我有一个基地 class public abstract class BaseFragment
。有多个 Fragments 扩展了这个基础 class。这些片段具有从基础 class 实现(使用 "old" findViewById()
)处理的公共视图。例如,每个片段的布局都应包含一个 ID 为 text_title 的 TextView。以下是 BaseFragment
的 onViewCreated()
的处理方式:
TextView title = view.findViewById(R.id.text_title);
// Do something with the view from the base class
现在 ViewBinding-API 为每个子片段生成绑定-classes。我可以使用绑定来引用视图。但我无法使用基础 class 中的具体绑定。即使在基础中引入泛型 class 也有太多类型的片段绑定,我现在放弃了这个解决方案。
从抽象基 class 处理绑定视图的推荐方法是什么?有什么最佳实践吗?未在 API 中找到内置机制来优雅地处理这种情况。
当期望子片段包含公共视图时,我可以提供抽象方法,return 来自片段具体绑定的视图,并使它们可以从基础 class 访问。 (例如 protected abstract TextView getTitleView();
)。但这是一个优势而不是使用 findViewById()
吗?你怎么看?还有其他(更好的)解决方案吗?请让我们开始讨论。
我找到了适用于我的具体场景的解决方案,我不想与您分享。
请注意,这不是对 ViewBinding
工作原理的解释。
我在下面创建了一些伪代码与您分享。 (使用显示 AlertDialog
的 DialogFragments
从我的解决方案迁移而来)。我希望它几乎可以正确适应 Fragments(onCreateView()
与 onCreateDialog()
)。我让它以这种方式工作。
假设我们有一个抽象 BaseFragment
和两个扩展 classes FragmentA
和 FragmentB
.
首先看看我们所有的布局。请注意,我将布局的可重用部分移出到一个单独的文件中,该文件稍后将包含在具体片段的布局中。特定视图保留在其片段的布局中。使用常用的布局对于这种情况很重要。
fragment_a.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- FragmentA-specific views -->
<EditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/edit_name">
<!-- Include the common layout -->
<include
layout="@layout/common_layout.xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</RelativeLayout>
fragment_b.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- FragmentB-specific, differs from FragmentA -->
<TextView
android:id="@+id/text_explain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/explain" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/text_explain">
<!-- Include the common layout -->
<include
layout="@layout/common_layout.xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</RelativeLayout>
common_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="android.widget.RelativeLayout">
<Button
android:id="@+id/button_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/up"/>
<Button
android:id="@+id/button_down"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button_up"
android:text="@string/down" />
</merge>
下一个片段 classes。首先是我们的 BaseFragment
实现。
onCreateView()
是绑定膨胀的地方。我们能够根据包含 common_layout.xml
的片段绑定来绑定 CommonLayoutBinding
。我定义了一个在 onCreateView()
之上调用的抽象方法 onCreateViewBinding()
,即 returns 来自 FragmentA
和 FragmentB
的 VewBinding
。这样我就可以确保在需要创建 CommonLayoutBinding
.
时片段的绑定存在
接下来我可以通过调用 commonBinding = CommonLayoutBinding.bind(binding.getRoot());
创建 CommonLayoutBinding
的实例。请注意,来自具体片段绑定的 root-view 被传递给 bind()
.
getCommonBinding()
允许从扩展片段提供对 CommonLayoutBinding
的访问。我们可以更严格:BaseFragment
应该提供访问该绑定的具体方法,而不是使其 public 变为 child-classes.
private CommonLayoutBinding commonBinding; // common_layout.xml
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// Make sure to create the concrete binding while it's required to
// create the commonBinding from it
ViewBinding binding = onCreateViewBinding(inflater);
// We're using the concrete layout of the child class to create our
// commonly used binding
commonBinding = CommonLayoutBinding.bind(binding.getRoot());
// ...
return binding.getRoot();
}
// Makes shure to create the concrete binding class from child-classes before
// the commonBinding can be bound
@NonNull
protected abstract ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container);
// Allows child-classes to access the commonBinding to access common
// used views
protected CommonLayoutBinding getCommonBinding() {
return commonBinding;
}
现在看看 child-classes、FragmentA
之一。
我们从 onCreateViewBinding()
开始创建我们的绑定,就像我们从 onCreateView()
开始做的那样。原则上它仍然是从 onCreateVIew()
调用的。如上所述,此绑定是从基础 class 使用的。我正在使用 getCommonBinding()
来访问来自 common_layout.xml
的视图。 BaseFragment
的每个 child class 现在都可以从 ViewBinding
.
访问这些视图
这样我就可以将所有基于共同观点的逻辑向上移动到基础class。
private FragmentABinding binding; // fragment_a.xml
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// Make sure commonBinding is present before calling super.onCreateView()
// (onCreateViewBinding() needs to deliver a result!)
View view = super.onCreateView(inflater, container, savedInstanceState);
binding.editName.setText("Test");
// ...
CommonLayoutBinding commonBinding = getCommonBinding();
commonBinding.buttonUp.setOnClickListener(v -> {
// Handle onClick-event...
});
// ...
return view;
}
// This comes from the base class and makes sure we have the required
// binding-instance, see BaseFragment
@Override
protected ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container) {
binding = FragmentABinding.inflate(inflater, container, false);
return binding;
}
优点:
- 通过将重复代码移至基础 class 减少了重复代码。所有片段中的代码现在都更加清晰并简化为基本要素
- 通过将可重复使用的视图移动到通过
<include />
包含的布局中,布局更加简洁
缺点:
- 可能不完全适用于无法将视图移动到常用布局文件中的情况
- 可能需要在 frgments/layouts
之间定位不同的视图
- 许多
<included />
布局会导致许多绑定 classes,那么就没有什么可赢的了
- 需要另一个绑定实例 (
CommonLayoutBinding
)。每个 child(FragmentA
、FragmentB
)不仅有一个绑定 class 提供对视图层次结构中所有视图的访问
如果无法将视图移动到通用布局中怎么办?
我对如何将其作为最佳实践来解决非常感兴趣!让我们考虑一下:在具体 ViewBinding
周围引入一个包装器 class。我们可以引入一个接口来提供对常用视图的访问。从片段中,我们将绑定包装在这些 wrapper-classes 中。另一方面,这将导致每个 ViewBinding-type 有很多包装器。但是我们可以使用抽象方法(泛型)将这些包装器提供给 BaseFragment
。 BaseFragment
然后可以使用定义的接口方法访问视图或处理它们。 你怎么看?
结论:
也许这只是一个布局需要拥有自己的 ViewBinding 的实际限制 Binding-class。 如果您在布局无法共享且需要在每个布局中声明重复的情况下找到好的解决方案,请告诉我。
不知道是不是这样best-practice或者有没有更好的解决办法。但在这是我的用例的唯一已知解决方案之前,这似乎是一个好的开始!
2021 年 2 月 4 日更新:在研究并从许多来源获得灵感后,我写了一篇 article。这篇文章将根据我未来在视图绑定方面的经验进行更新,因为我们公司现在已经放弃了将近 80% 的合成绑定。
我还提出了一个基本 Class 解决方案,它有效地使用了最终变量。我的主要目标是:
- 处理基中的所有绑定生命周期class
- 让 child class 提供绑定 class 实例而不单独使用该路由(例如,如果我有一个抽象函数
abstract fun getBind():T
, child class 可以实现它并直接调用它。我不希望那样,因为这会使在基础 class 中保持绑定的意义变得毫无意义,我相信 )
就是这样。首先是我的应用程序的当前结构。这些活动不会自行膨胀,基地 class 会为他们做:
Child 活动和片段:
class MainActivity : BaseActivityCurrent(){
var i = 0
override val contentView: Int
get() = R.layout.main_activity
override fun setup() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment())
.commitNow()
syntheticApproachActivity()
}
private fun syntheticApproachActivity() {
btText?.setOnClickListener { tvText?.text = "The current click count is ${++i}" }
}
private fun fidApproachActivity() {
val bt = findViewById<Button>(R.id.btText)
val tv = findViewById<TextView>(R.id.tvText)
bt.setOnClickListener { tv.text = "The current click count is ${++i}" }
}
}
//-----------------------------------------------------------
class MainFragment : BaseFragmentCurrent() {
override val contentView: Int
get() = R.layout.main_fragment
override fun setup() {
syntheticsApproach()
}
private fun syntheticsApproach() {
rbGroup?.setOnCheckedChangeListener{ _, id ->
when(id){
radioBt1?.id -> tvFragOutPut?.text = "You Opt in for additional content"
radioBt2?.id -> tvFragOutPut?.text = "You DO NOT Opt in for additional content"
}
}
}
private fun fidApproach(view: View) {
val rg: RadioGroup? = view.findViewById(R.id.rbGroup)
val rb1: RadioButton? = view.findViewById(R.id.radioBt1)
val rb2: RadioButton? = view.findViewById(R.id.radioBt2)
val tvOut: TextView? = view.findViewById(R.id.tvFragOutPut)
val cbDisable: CheckBox? = view.findViewById(R.id.cbox)
rg?.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
rb1?.id -> tvOut?.text = "You Opt in for additional content"
rb2?.id -> tvOut?.text = "You DO NOT Opt in for additional content"
}
}
rb1?.isChecked = true
rb2?.isChecked = false
cbDisable?.setOnCheckedChangeListener { _, bool ->
rb1?.isEnabled = bool
rb2?.isEnabled = bool
}
}
}
基地活动和片段:
abstract class BaseActivityCurrent :AppCompatActivity(){
abstract val contentView: Int
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(contentView)
setup()
}
abstract fun setup()
}
abstract class BaseFragmentCurrent : Fragment(){
abstract val contentView: Int
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(contentView,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setup()
}
abstract fun setup()
}
如您所见,children classes 总是很容易扩展,因为基础活动会完成所有繁重的工作。由于合成材料被广泛使用,因此没有太大问题。
要使用具有前面提到的约束的绑定 classes,我会:
需要 child classes 来实现将数据提供回 parent 片段的功能。这是简单的部分,只需创建 return child 的绑定 Class 的实例就可以完成的更多抽象函数。
将 child class 的视图绑定存储在一个变量中(比如 val binding:T
),这样基础 class 可以在在销毁广告时相应地处理生命周期。有点棘手,因为事先不知道 child 的绑定 class 实例类型。但是将 parent 设为通用 (<T:ViewBinding>
) 就可以了
return将 inflation 的视图返回系统。再次,很容易,因为谢天谢地,对于大多数组件,系统接受一个膨胀的视图,并且拥有 child 的绑定实例将让我向系统提供一个视图
防止childclass直接使用第1点创建的路线。想一想:如果 child class 有一个 getBind(){...}
函数 return 是他们自己的绑定 class 实例,为什么他们不使用那个而是取而代之使用 super.binding
?是什么阻止他们在 onDestroy() 中使用 getBind()
函数,而不是访问绑定?
这就是为什么我使该函数无效并将一个可变列表传递给它的原因。 child class 现在会将它们的绑定添加到 parent 将访问的列表中。如果他们不这样做,它将抛出一个 NPE。如果他们试图在销毁或其他地方使用它,它会再次抛出一个 illegalstate exception
。我还创建了一个方便的高阶函数 withBinding(..)
以便于使用。
碱基结合activity和片段:
abstract class BaseActivityFinal<VB_CHILD : ViewBinding> : AppCompatActivity() {
private var binding: VB_CHILD? = null
//lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getInflatedLayout(layoutInflater))
setup()
}
override fun onDestroy() {
super.onDestroy()
this.binding = null
}
//internal functions
private fun getInflatedLayout(inflater: LayoutInflater): View {
val tempList = mutableListOf<VB_CHILD>()
attachBinding(tempList, inflater)
this.binding = tempList[0]
return binding?.root?: error("Please add your inflated binding class instance at 0th position in list")
}
//abstract functions
abstract fun attachBinding(list: MutableList<VB_CHILD>, layoutInflater: LayoutInflater)
abstract fun setup()
//helpers
fun withBinding(block: (VB_CHILD.() -> Unit)?): VB_CHILD {
val bindingAfterRunning:VB_CHILD? = binding?.apply { block?.invoke(this) }
return bindingAfterRunning
?: error("Accessing binding outside of lifecycle: ${this::class.java.simpleName}")
}
}
//--------------------------------------------------------------------------
abstract class BaseFragmentFinal<VB_CHILD : ViewBinding> : Fragment() {
private var binding: VB_CHILD? = null
//lifecycle
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = getInflatedView(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setup()
}
override fun onDestroy() {
super.onDestroy()
this.binding = null
}
//internal functions
private fun getInflatedView(
inflater: LayoutInflater,
container: ViewGroup?,
attachToRoot: Boolean
): View {
val tempList = mutableListOf<VB_CHILD>()
attachBinding(tempList, inflater, container, attachToRoot)
this.binding = tempList[0]
return binding?.root
?: error("Please add your inflated binding class instance at 0th position in list")
}
//abstract functions
abstract fun attachBinding(
list: MutableList<VB_CHILD>,
layoutInflater: LayoutInflater,
container: ViewGroup?,
attachToRoot: Boolean
)
abstract fun setup()
//helpers
fun withBinding(block: (VB_CHILD.() -> Unit)?): VB_CHILD {
val bindingAfterRunning:VB_CHILD? = binding?.apply { block?.invoke(this) }
return bindingAfterRunning
?: error("Accessing binding outside of lifecycle: ${this::class.java.simpleName}")
}
}
Child activity 和片段:
class MainActivityFinal:BaseActivityFinal<MainActivityBinding>() {
var i = 0
override fun setup() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragmentFinal())
.commitNow()
viewBindingApproach()
}
private fun viewBindingApproach() {
withBinding {
btText.setOnClickListener { tvText.text = "The current click count is ${++i}" }
btText.performClick()
}
}
override fun attachBinding(list: MutableList<MainActivityBinding>, layoutInflater: LayoutInflater) {
list.add(MainActivityBinding.inflate(layoutInflater))
}
}
//-------------------------------------------------------------------
class MainFragmentFinal : BaseFragmentFinal<MainFragmentBinding>() {
override fun setup() {
bindingApproach()
}
private fun bindingApproach() {
withBinding {
rbGroup.setOnCheckedChangeListener{ _, id ->
when(id){
radioBt1.id -> tvFragOutPut.text = "You Opt in for additional content"
radioBt2.id -> tvFragOutPut.text = "You DO NOT Opt in for additional content"
}
}
radioBt1.isChecked = true
radioBt2.isChecked = false
cbox.setOnCheckedChangeListener { _, bool ->
radioBt1.isEnabled = !bool
radioBt2.isEnabled = !bool
}
}
}
override fun attachBinding(
list: MutableList<MainFragmentBinding>,
layoutInflater: LayoutInflater,
container: ViewGroup?,
attachToRoot: Boolean
) {
list.add(MainFragmentBinding.inflate(layoutInflater,container,attachToRoot))
}
}
您好,我创建了一个博客 post,其中深入介绍了视图绑定,还包括实现视图绑定的组合 patter/delegate 模式以及使用 link
查看 BaseActivity
和 BaseFragment
的完整代码以及用法
/*
* In Activity
* source : https://chetangupta.net/viewbinding/
* Author : ChetanGupta.net
*/
abstract class ViewBindingActivity<VB : ViewBinding> : AppCompatActivity() {
private var _binding: ViewBinding? = null
abstract val bindingInflater: (LayoutInflater) -> VB
@Suppress("UNCHECKED_CAST")
protected val binding: VB
get() = _binding as VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = bindingInflater.invoke(layoutInflater)
setContentView(requireNotNull(_binding).root)
setup()
}
abstract fun setup()
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
/*
* In Fragment
* source : https://chetangupta.net/viewbinding/
* Author : ChetanGupta.net
*/
abstract class ViewBindingFragment<VB : ViewBinding> : Fragment() {
private var _binding: ViewBinding? = null
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
@Suppress("UNCHECKED_CAST")
protected val binding: VB
get() = _binding as VB
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = bindingInflater.invoke(inflater, container, false)
return requireNotNull(_binding).root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setup()
}
abstract fun setup()
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
关于用法、高级模式和反模式结帐博客Androidbites|ViewBinding
这是对 的 Kotlin 版本稍作修改。
避免使用“UNCHECKED_CAST”。
Activity
abstract class BaseViewBindingActivity<ViewBindingType : ViewBinding> : AppCompatActivity() {
protected lateinit var binding: ViewBindingType
protected abstract val bindingInflater: (LayoutInflater) -> ViewBindingType
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = bindingInflater.invoke(layoutInflater)
val view = binding.root
setContentView(view)
}
}
片段
abstract class BaseViewBindingFragment<ViewBindingType : ViewBinding> : Fragment() {
private var _binding: ViewBindingType? = null
protected val binding get() = requireNotNull(_binding)
protected abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ViewBindingType
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = bindingInflater.invoke(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
我认为一个简单的回应是使用 class 的常见方法 bind
。
我知道这并不适用于所有情况,但它适用于具有相似元素的视图。
如果我有两个布局 row_type_1.xml
和 row_type_2.xml
它们共享共同的元素,那么我可以做一些事情:
ROW_TYPE_1 -> CommonRowViewHolder(
RowType1Binding.inflate(LayoutInflater.from(parent.context), parent, false))
然后对于类型 2,不是创建另一个接收其自己的绑定的 ViewHolder class,而是执行以下操作:
ROW_TYPE_2 -> {
val type2Binding = RowType2Binding.inflate(LayoutInflater.from(parent.context), parent, false))
CommonRowViewHolder(RowType1Binding.bind(type2Binding))
}
如果它是组件的子集,则可以放置继承
CommonRowViewHolder: ViewHolder {
fun bind(binding: RowType1Holder)
}
Type2RowViewHolder: CommonRowViewHolder {
fun bind(binding: RowType2Holder) {
super.bind(Type1RowViewHolder.bind(binding))
//perform specific views for type 2 binding ...
}
}
inline fun <reified BindingT : ViewBinding> AppCompatActivity.viewBindings(
crossinline bind: (View) -> BindingT
) = object : Lazy<BindingT> {
private var initialized: BindingT? = null
override val value: BindingT
get() = initialized ?: bind(
findViewById<ViewGroup>(android.R.id.content).getChildAt(0)
).also {
initialized = it
}
override fun isInitialized() = initialized != null
}
这是我的 BaseViewBindingFragment
的完整示例:
- 不需要任何
abstract
属性或函数,
- 它依赖于 Java 反射(不是 Kotlin 反射)- 参见
fun createBindingInstance
,其中使用 VB
泛型类型参数
package app.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import java.lang.reflect.ParameterizedType
/**
* Base application `Fragment` class with overridden [onCreateView] that inflates the view
* based on the [VB] type argument and set the [binding] property.
*
* @param VB The type of the View Binding class.
*/
open class BaseViewBindingFragment<VB : ViewBinding> : Fragment() {
/** The view binding instance. */
protected var binding: VB? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
createBindingInstance(inflater, container).also { binding = it }.root
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
/** Creates new [VB] instance using reflection. */
@Suppress("UNCHECKED_CAST")
protected open fun createBindingInstance(inflater: LayoutInflater, container: ViewGroup?): VB {
val vbType = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
val vbClass = vbType as Class<VB>
val method = vbClass.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
// Call VB.inflate(inflater, container, false) Java static method
return method.invoke(null, inflater, container, false) as VB
}
}
Base Class 会像这样
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity(){
protected lateinit var binding : VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = inflateLayout(layoutInflater)
setContentView(binding.root)
}
abstract fun inflateLayout(layoutInflater: LayoutInflater) : VB
}
现在在您要使用的 activity 位置
class MainActivity : BaseActivity<ActivityMainBinding>(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.tvName.text="ankit"
}
override fun inflateLayout(layoutInflater: LayoutInflater) = ActivityMainBinding.inflate(layoutInflater)
}
现在在 onCreate 中只需按用途使用绑定
我创建了这个摘要 class 作为基础;
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
private var _binding: VB? = null
val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = inflateViewBinding(inflater, container)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
abstract fun inflateViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
}
用法;
class HomeFragment : BaseFragment<FragmentHomeBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textViewTitle.text = ""
}
override fun inflateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding {
return FragmentHomeBinding.inflate(inflater, container, false)
}
}
我开始使用 ViewBinding。在搜索了一些示例或建议如何将 ViewBinding 与抽象基础 class 一起使用后,该抽象基础应该处理预期出现在每个子布局中的视图的相同逻辑,我最终在这里发布了这个问题。
场景:
我有一个基地 class public abstract class BaseFragment
。有多个 Fragments 扩展了这个基础 class。这些片段具有从基础 class 实现(使用 "old" findViewById()
)处理的公共视图。例如,每个片段的布局都应包含一个 ID 为 text_title 的 TextView。以下是 BaseFragment
的 onViewCreated()
的处理方式:
TextView title = view.findViewById(R.id.text_title);
// Do something with the view from the base class
现在 ViewBinding-API 为每个子片段生成绑定-classes。我可以使用绑定来引用视图。但我无法使用基础 class 中的具体绑定。即使在基础中引入泛型 class 也有太多类型的片段绑定,我现在放弃了这个解决方案。
从抽象基 class 处理绑定视图的推荐方法是什么?有什么最佳实践吗?未在 API 中找到内置机制来优雅地处理这种情况。
当期望子片段包含公共视图时,我可以提供抽象方法,return 来自片段具体绑定的视图,并使它们可以从基础 class 访问。 (例如 protected abstract TextView getTitleView();
)。但这是一个优势而不是使用 findViewById()
吗?你怎么看?还有其他(更好的)解决方案吗?请让我们开始讨论。
我找到了适用于我的具体场景的解决方案,我不想与您分享。
请注意,这不是对 ViewBinding
工作原理的解释。
我在下面创建了一些伪代码与您分享。 (使用显示 AlertDialog
的 DialogFragments
从我的解决方案迁移而来)。我希望它几乎可以正确适应 Fragments(onCreateView()
与 onCreateDialog()
)。我让它以这种方式工作。
假设我们有一个抽象 BaseFragment
和两个扩展 classes FragmentA
和 FragmentB
.
首先看看我们所有的布局。请注意,我将布局的可重用部分移出到一个单独的文件中,该文件稍后将包含在具体片段的布局中。特定视图保留在其片段的布局中。使用常用的布局对于这种情况很重要。
fragment_a.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- FragmentA-specific views -->
<EditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/edit_name">
<!-- Include the common layout -->
<include
layout="@layout/common_layout.xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</RelativeLayout>
fragment_b.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- FragmentB-specific, differs from FragmentA -->
<TextView
android:id="@+id/text_explain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/explain" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/text_explain">
<!-- Include the common layout -->
<include
layout="@layout/common_layout.xml"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</RelativeLayout>
common_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="android.widget.RelativeLayout">
<Button
android:id="@+id/button_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/up"/>
<Button
android:id="@+id/button_down"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button_up"
android:text="@string/down" />
</merge>
下一个片段 classes。首先是我们的 BaseFragment
实现。
onCreateView()
是绑定膨胀的地方。我们能够根据包含 common_layout.xml
的片段绑定来绑定 CommonLayoutBinding
。我定义了一个在 onCreateView()
之上调用的抽象方法 onCreateViewBinding()
,即 returns 来自 FragmentA
和 FragmentB
的 VewBinding
。这样我就可以确保在需要创建 CommonLayoutBinding
.
接下来我可以通过调用 commonBinding = CommonLayoutBinding.bind(binding.getRoot());
创建 CommonLayoutBinding
的实例。请注意,来自具体片段绑定的 root-view 被传递给 bind()
.
getCommonBinding()
允许从扩展片段提供对 CommonLayoutBinding
的访问。我们可以更严格:BaseFragment
应该提供访问该绑定的具体方法,而不是使其 public 变为 child-classes.
private CommonLayoutBinding commonBinding; // common_layout.xml
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// Make sure to create the concrete binding while it's required to
// create the commonBinding from it
ViewBinding binding = onCreateViewBinding(inflater);
// We're using the concrete layout of the child class to create our
// commonly used binding
commonBinding = CommonLayoutBinding.bind(binding.getRoot());
// ...
return binding.getRoot();
}
// Makes shure to create the concrete binding class from child-classes before
// the commonBinding can be bound
@NonNull
protected abstract ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container);
// Allows child-classes to access the commonBinding to access common
// used views
protected CommonLayoutBinding getCommonBinding() {
return commonBinding;
}
现在看看 child-classes、FragmentA
之一。
我们从 onCreateViewBinding()
开始创建我们的绑定,就像我们从 onCreateView()
开始做的那样。原则上它仍然是从 onCreateVIew()
调用的。如上所述,此绑定是从基础 class 使用的。我正在使用 getCommonBinding()
来访问来自 common_layout.xml
的视图。 BaseFragment
的每个 child class 现在都可以从 ViewBinding
.
这样我就可以将所有基于共同观点的逻辑向上移动到基础class。
private FragmentABinding binding; // fragment_a.xml
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// Make sure commonBinding is present before calling super.onCreateView()
// (onCreateViewBinding() needs to deliver a result!)
View view = super.onCreateView(inflater, container, savedInstanceState);
binding.editName.setText("Test");
// ...
CommonLayoutBinding commonBinding = getCommonBinding();
commonBinding.buttonUp.setOnClickListener(v -> {
// Handle onClick-event...
});
// ...
return view;
}
// This comes from the base class and makes sure we have the required
// binding-instance, see BaseFragment
@Override
protected ViewBinding onCreateViewBinding(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container) {
binding = FragmentABinding.inflate(inflater, container, false);
return binding;
}
优点:
- 通过将重复代码移至基础 class 减少了重复代码。所有片段中的代码现在都更加清晰并简化为基本要素
- 通过将可重复使用的视图移动到通过
<include />
包含的布局中,布局更加简洁
缺点:
- 可能不完全适用于无法将视图移动到常用布局文件中的情况
- 可能需要在 frgments/layouts 之间定位不同的视图
- 许多
<included />
布局会导致许多绑定 classes,那么就没有什么可赢的了
- 需要另一个绑定实例 (
CommonLayoutBinding
)。每个 child(FragmentA
、FragmentB
)不仅有一个绑定 class 提供对视图层次结构中所有视图的访问
如果无法将视图移动到通用布局中怎么办?
我对如何将其作为最佳实践来解决非常感兴趣!让我们考虑一下:在具体 ViewBinding
周围引入一个包装器 class。我们可以引入一个接口来提供对常用视图的访问。从片段中,我们将绑定包装在这些 wrapper-classes 中。另一方面,这将导致每个 ViewBinding-type 有很多包装器。但是我们可以使用抽象方法(泛型)将这些包装器提供给 BaseFragment
。 BaseFragment
然后可以使用定义的接口方法访问视图或处理它们。 你怎么看?
结论:
也许这只是一个布局需要拥有自己的 ViewBinding 的实际限制 Binding-class。 如果您在布局无法共享且需要在每个布局中声明重复的情况下找到好的解决方案,请告诉我。
不知道是不是这样best-practice或者有没有更好的解决办法。但在这是我的用例的唯一已知解决方案之前,这似乎是一个好的开始!
2021 年 2 月 4 日更新:在研究并从许多来源获得灵感后,我写了一篇 article。这篇文章将根据我未来在视图绑定方面的经验进行更新,因为我们公司现在已经放弃了将近 80% 的合成绑定。
我还提出了一个基本 Class 解决方案,它有效地使用了最终变量。我的主要目标是:
- 处理基中的所有绑定生命周期class
- 让 child class 提供绑定 class 实例而不单独使用该路由(例如,如果我有一个抽象函数
abstract fun getBind():T
, child class 可以实现它并直接调用它。我不希望那样,因为这会使在基础 class 中保持绑定的意义变得毫无意义,我相信 )
就是这样。首先是我的应用程序的当前结构。这些活动不会自行膨胀,基地 class 会为他们做:
Child 活动和片段:
class MainActivity : BaseActivityCurrent(){
var i = 0
override val contentView: Int
get() = R.layout.main_activity
override fun setup() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragment())
.commitNow()
syntheticApproachActivity()
}
private fun syntheticApproachActivity() {
btText?.setOnClickListener { tvText?.text = "The current click count is ${++i}" }
}
private fun fidApproachActivity() {
val bt = findViewById<Button>(R.id.btText)
val tv = findViewById<TextView>(R.id.tvText)
bt.setOnClickListener { tv.text = "The current click count is ${++i}" }
}
}
//-----------------------------------------------------------
class MainFragment : BaseFragmentCurrent() {
override val contentView: Int
get() = R.layout.main_fragment
override fun setup() {
syntheticsApproach()
}
private fun syntheticsApproach() {
rbGroup?.setOnCheckedChangeListener{ _, id ->
when(id){
radioBt1?.id -> tvFragOutPut?.text = "You Opt in for additional content"
radioBt2?.id -> tvFragOutPut?.text = "You DO NOT Opt in for additional content"
}
}
}
private fun fidApproach(view: View) {
val rg: RadioGroup? = view.findViewById(R.id.rbGroup)
val rb1: RadioButton? = view.findViewById(R.id.radioBt1)
val rb2: RadioButton? = view.findViewById(R.id.radioBt2)
val tvOut: TextView? = view.findViewById(R.id.tvFragOutPut)
val cbDisable: CheckBox? = view.findViewById(R.id.cbox)
rg?.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) {
rb1?.id -> tvOut?.text = "You Opt in for additional content"
rb2?.id -> tvOut?.text = "You DO NOT Opt in for additional content"
}
}
rb1?.isChecked = true
rb2?.isChecked = false
cbDisable?.setOnCheckedChangeListener { _, bool ->
rb1?.isEnabled = bool
rb2?.isEnabled = bool
}
}
}
基地活动和片段:
abstract class BaseActivityCurrent :AppCompatActivity(){
abstract val contentView: Int
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(contentView)
setup()
}
abstract fun setup()
}
abstract class BaseFragmentCurrent : Fragment(){
abstract val contentView: Int
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(contentView,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setup()
}
abstract fun setup()
}
如您所见,children classes 总是很容易扩展,因为基础活动会完成所有繁重的工作。由于合成材料被广泛使用,因此没有太大问题。 要使用具有前面提到的约束的绑定 classes,我会:
需要 child classes 来实现将数据提供回 parent 片段的功能。这是简单的部分,只需创建 return child 的绑定 Class 的实例就可以完成的更多抽象函数。
将 child class 的视图绑定存储在一个变量中(比如
val binding:T
),这样基础 class 可以在在销毁广告时相应地处理生命周期。有点棘手,因为事先不知道 child 的绑定 class 实例类型。但是将 parent 设为通用 (<T:ViewBinding>
) 就可以了return将 inflation 的视图返回系统。再次,很容易,因为谢天谢地,对于大多数组件,系统接受一个膨胀的视图,并且拥有 child 的绑定实例将让我向系统提供一个视图
防止childclass直接使用第1点创建的路线。想一想:如果 child class 有一个
getBind(){...}
函数 return 是他们自己的绑定 class 实例,为什么他们不使用那个而是取而代之使用super.binding
?是什么阻止他们在 onDestroy() 中使用getBind()
函数,而不是访问绑定?
这就是为什么我使该函数无效并将一个可变列表传递给它的原因。 child class 现在会将它们的绑定添加到 parent 将访问的列表中。如果他们不这样做,它将抛出一个 NPE。如果他们试图在销毁或其他地方使用它,它会再次抛出一个 illegalstate exception
。我还创建了一个方便的高阶函数 withBinding(..)
以便于使用。
碱基结合activity和片段:
abstract class BaseActivityFinal<VB_CHILD : ViewBinding> : AppCompatActivity() {
private var binding: VB_CHILD? = null
//lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getInflatedLayout(layoutInflater))
setup()
}
override fun onDestroy() {
super.onDestroy()
this.binding = null
}
//internal functions
private fun getInflatedLayout(inflater: LayoutInflater): View {
val tempList = mutableListOf<VB_CHILD>()
attachBinding(tempList, inflater)
this.binding = tempList[0]
return binding?.root?: error("Please add your inflated binding class instance at 0th position in list")
}
//abstract functions
abstract fun attachBinding(list: MutableList<VB_CHILD>, layoutInflater: LayoutInflater)
abstract fun setup()
//helpers
fun withBinding(block: (VB_CHILD.() -> Unit)?): VB_CHILD {
val bindingAfterRunning:VB_CHILD? = binding?.apply { block?.invoke(this) }
return bindingAfterRunning
?: error("Accessing binding outside of lifecycle: ${this::class.java.simpleName}")
}
}
//--------------------------------------------------------------------------
abstract class BaseFragmentFinal<VB_CHILD : ViewBinding> : Fragment() {
private var binding: VB_CHILD? = null
//lifecycle
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = getInflatedView(inflater, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setup()
}
override fun onDestroy() {
super.onDestroy()
this.binding = null
}
//internal functions
private fun getInflatedView(
inflater: LayoutInflater,
container: ViewGroup?,
attachToRoot: Boolean
): View {
val tempList = mutableListOf<VB_CHILD>()
attachBinding(tempList, inflater, container, attachToRoot)
this.binding = tempList[0]
return binding?.root
?: error("Please add your inflated binding class instance at 0th position in list")
}
//abstract functions
abstract fun attachBinding(
list: MutableList<VB_CHILD>,
layoutInflater: LayoutInflater,
container: ViewGroup?,
attachToRoot: Boolean
)
abstract fun setup()
//helpers
fun withBinding(block: (VB_CHILD.() -> Unit)?): VB_CHILD {
val bindingAfterRunning:VB_CHILD? = binding?.apply { block?.invoke(this) }
return bindingAfterRunning
?: error("Accessing binding outside of lifecycle: ${this::class.java.simpleName}")
}
}
Child activity 和片段:
class MainActivityFinal:BaseActivityFinal<MainActivityBinding>() {
var i = 0
override fun setup() {
supportFragmentManager.beginTransaction()
.replace(R.id.container, MainFragmentFinal())
.commitNow()
viewBindingApproach()
}
private fun viewBindingApproach() {
withBinding {
btText.setOnClickListener { tvText.text = "The current click count is ${++i}" }
btText.performClick()
}
}
override fun attachBinding(list: MutableList<MainActivityBinding>, layoutInflater: LayoutInflater) {
list.add(MainActivityBinding.inflate(layoutInflater))
}
}
//-------------------------------------------------------------------
class MainFragmentFinal : BaseFragmentFinal<MainFragmentBinding>() {
override fun setup() {
bindingApproach()
}
private fun bindingApproach() {
withBinding {
rbGroup.setOnCheckedChangeListener{ _, id ->
when(id){
radioBt1.id -> tvFragOutPut.text = "You Opt in for additional content"
radioBt2.id -> tvFragOutPut.text = "You DO NOT Opt in for additional content"
}
}
radioBt1.isChecked = true
radioBt2.isChecked = false
cbox.setOnCheckedChangeListener { _, bool ->
radioBt1.isEnabled = !bool
radioBt2.isEnabled = !bool
}
}
}
override fun attachBinding(
list: MutableList<MainFragmentBinding>,
layoutInflater: LayoutInflater,
container: ViewGroup?,
attachToRoot: Boolean
) {
list.add(MainFragmentBinding.inflate(layoutInflater,container,attachToRoot))
}
}
您好,我创建了一个博客 post,其中深入介绍了视图绑定,还包括实现视图绑定的组合 patter/delegate 模式以及使用 link
查看 BaseActivity
和 BaseFragment
的完整代码以及用法
/*
* In Activity
* source : https://chetangupta.net/viewbinding/
* Author : ChetanGupta.net
*/
abstract class ViewBindingActivity<VB : ViewBinding> : AppCompatActivity() {
private var _binding: ViewBinding? = null
abstract val bindingInflater: (LayoutInflater) -> VB
@Suppress("UNCHECKED_CAST")
protected val binding: VB
get() = _binding as VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = bindingInflater.invoke(layoutInflater)
setContentView(requireNotNull(_binding).root)
setup()
}
abstract fun setup()
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
/*
* In Fragment
* source : https://chetangupta.net/viewbinding/
* Author : ChetanGupta.net
*/
abstract class ViewBindingFragment<VB : ViewBinding> : Fragment() {
private var _binding: ViewBinding? = null
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
@Suppress("UNCHECKED_CAST")
protected val binding: VB
get() = _binding as VB
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = bindingInflater.invoke(inflater, container, false)
return requireNotNull(_binding).root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setup()
}
abstract fun setup()
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
关于用法、高级模式和反模式结帐博客Androidbites|ViewBinding
这是对
Activity
abstract class BaseViewBindingActivity<ViewBindingType : ViewBinding> : AppCompatActivity() {
protected lateinit var binding: ViewBindingType
protected abstract val bindingInflater: (LayoutInflater) -> ViewBindingType
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = bindingInflater.invoke(layoutInflater)
val view = binding.root
setContentView(view)
}
}
片段
abstract class BaseViewBindingFragment<ViewBindingType : ViewBinding> : Fragment() {
private var _binding: ViewBindingType? = null
protected val binding get() = requireNotNull(_binding)
protected abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ViewBindingType
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = bindingInflater.invoke(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
我认为一个简单的回应是使用 class 的常见方法 bind
。
我知道这并不适用于所有情况,但它适用于具有相似元素的视图。
如果我有两个布局 row_type_1.xml
和 row_type_2.xml
它们共享共同的元素,那么我可以做一些事情:
ROW_TYPE_1 -> CommonRowViewHolder(
RowType1Binding.inflate(LayoutInflater.from(parent.context), parent, false))
然后对于类型 2,不是创建另一个接收其自己的绑定的 ViewHolder class,而是执行以下操作:
ROW_TYPE_2 -> {
val type2Binding = RowType2Binding.inflate(LayoutInflater.from(parent.context), parent, false))
CommonRowViewHolder(RowType1Binding.bind(type2Binding))
}
如果它是组件的子集,则可以放置继承
CommonRowViewHolder: ViewHolder {
fun bind(binding: RowType1Holder)
}
Type2RowViewHolder: CommonRowViewHolder {
fun bind(binding: RowType2Holder) {
super.bind(Type1RowViewHolder.bind(binding))
//perform specific views for type 2 binding ...
}
}
inline fun <reified BindingT : ViewBinding> AppCompatActivity.viewBindings(
crossinline bind: (View) -> BindingT
) = object : Lazy<BindingT> {
private var initialized: BindingT? = null
override val value: BindingT
get() = initialized ?: bind(
findViewById<ViewGroup>(android.R.id.content).getChildAt(0)
).also {
initialized = it
}
override fun isInitialized() = initialized != null
}
这是我的 BaseViewBindingFragment
的完整示例:
- 不需要任何
abstract
属性或函数, - 它依赖于 Java 反射(不是 Kotlin 反射)- 参见
fun createBindingInstance
,其中使用VB
泛型类型参数
package app.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import java.lang.reflect.ParameterizedType
/**
* Base application `Fragment` class with overridden [onCreateView] that inflates the view
* based on the [VB] type argument and set the [binding] property.
*
* @param VB The type of the View Binding class.
*/
open class BaseViewBindingFragment<VB : ViewBinding> : Fragment() {
/** The view binding instance. */
protected var binding: VB? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
createBindingInstance(inflater, container).also { binding = it }.root
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
/** Creates new [VB] instance using reflection. */
@Suppress("UNCHECKED_CAST")
protected open fun createBindingInstance(inflater: LayoutInflater, container: ViewGroup?): VB {
val vbType = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
val vbClass = vbType as Class<VB>
val method = vbClass.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
// Call VB.inflate(inflater, container, false) Java static method
return method.invoke(null, inflater, container, false) as VB
}
}
Base Class 会像这样
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity(){
protected lateinit var binding : VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = inflateLayout(layoutInflater)
setContentView(binding.root)
}
abstract fun inflateLayout(layoutInflater: LayoutInflater) : VB
}
现在在您要使用的 activity 位置
class MainActivity : BaseActivity<ActivityMainBinding>(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.tvName.text="ankit"
}
override fun inflateLayout(layoutInflater: LayoutInflater) = ActivityMainBinding.inflate(layoutInflater)
}
现在在 onCreate 中只需按用途使用绑定
我创建了这个摘要 class 作为基础;
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
private var _binding: VB? = null
val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = inflateViewBinding(inflater, container)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
abstract fun inflateViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
}
用法;
class HomeFragment : BaseFragment<FragmentHomeBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textViewTitle.text = ""
}
override fun inflateViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeBinding {
return FragmentHomeBinding.inflate(inflater, container, false)
}
}