如何将 ViewBinding 与抽象基类一起使用

Posted

技术标签:

【中文标题】如何将 ViewBinding 与抽象基类一起使用【英文标题】:How using ViewBinding with an abstract base class 【发布时间】:2020-10-06 00:31:45 【问题描述】:

我开始使用 ViewBinding。在搜索了一些示例或建议后,该抽象基类应处理预期出现在每个子布局中的视图的相同逻辑,我最终在此处发布了这个问题。

场景: 我有一个基类public abstract class BaseFragment。有多个片段扩展了这个基类。这些片段具有从基类实现处理的公共视图(使用“旧”findViewById())。例如,每个片段的布局都应该包含一个 ID 为 text_title 的 TextView。以下是BaseFragmentonViewCreated() 的处理方式:

TextView title = view.findViewById(R.id.text_title);
// Do something with the view from the base class

现在 ViewBinding-API 为每个子片段生成绑定类。我可以使用绑定来引用视图。但我不能使用基类中的具体绑定。即使在基类中引入泛型,也有太多类型的片段绑定,我暂时放弃了这个解决方案。

从抽象基类处理绑定视图的推荐方法是什么?有没有最佳实践?没有在 API 中找到一种内置机制来优雅地处理这种情况。

当期望子片段包含公共视图时,我可以提供抽象方法,从片段的具体绑定中返回视图,并使它们可以从基类访问。 (例如protected abstract TextView getTitleView();)。但这比使用findViewById() 更有优势吗?你怎么看?还有其他(更好的)解决方案吗?请让我们开始讨论吧。

【问题讨论】:

我认为你可以在 BaseFragment 中写 public abstract int getLayoutResourse(); 并将其传递给 DataBindingUtil.inflate() 而不是 R.layout.frag_layout 或者我不明白这个问题 @AlexRmcf 首先:我不使用DataBinding,我只是想使用ViewBinding。是的,这将有可能从基类中获得ViewDataBinding。但是我无法通过ViewDataBinding.textTitle 访问视图,例如从基类访问视图,而不知道绑定类的具体类型。 我有同样的问题,我有一个基础 ViewHolder 包含视图和子视图,其中也包含他们自己的视图,我怎样才能将他们的 View Binder 传递给父 ViewHolder 而不必执行“if (child X) 的实例然后绑定 = XBinding" 嘿,您找到解决方案了吗? @anshsachdeva 目前看来我有一个解决方案。我仍然需要弄清楚它是否有效,并将很快给出答案。在最好的情况下,明天我可以做出积极的回应;) 【参考方案1】:

您好,我创建了一篇博客文章,其中深入介绍了视图绑定,还包括组合模式/委托模式以实现视图绑定以及使用链接中的继承签出

查看BaseActivityBaseFragment 的完整代码以及用法

?androidbites|ViewBinding

/*
 * 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

【讨论】:

bindLayout 是 bindingInflater 吗? 感谢@Chetan,正是我想要的。我修改了一些代码以避免“UNCHECKED_CAST”(在 Kotlin 中):private var _binding: ViewBindingType? = nullprotected val binding get() = requireNotNull(_binding)abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -&gt; ViewBindingType 嗨@ChetanGupta,请在答案中包括一个示例活动/片段。 我目前正在处理同样的问题,但我不明白这是一个解决方案。如果您还不知道将使用哪个特定绑定,您应该如何在 ViewBindingFragment 类中为“text_title”(在 OP 的情况下)设置文本的通用代码?【参考方案2】:

我找到了适用于我的具体场景的解决方案,我不想与你分享。

请注意,这不是对 ViewBinding 工作原理的解释。

我在下面创建了一些伪代码与您分享。 (使用显示AlertDialogDialogFragments 从我的解决方案迁移而来)。我希望它几乎可以正确地适应 Fragments(onCreateView()onCreateDialog())。我让它以这种方式工作。

假设我们有一个抽象 BaseFragment 和两个扩展类 FragmentAFragmentB

首先看看我们所有的布局。请注意,我将布局的可重用部分移出到一个单独的文件中,稍后将包含在具体片段的布局中。特定视图保留在其片段的布局中。对于这种情况,使用常用布局很重要。

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_
    android:layout_>
    
    <!-- FragmentA-specific views -->
    <EditText
        android:id="@+id/edit_name"
        android:layout_
        android:layout_
        android:inputType="text" />
    
    <RelativeLayout
        android:layout_
        android:layout_
        android:layout_below="@+id/edit_name">

        <!-- Include the common layout -->
        <include
            layout="@layout/common_layout.xml"
            android:layout_
            android:layout_ />
    </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_
    android:layout_>
    
    <!-- FragmentB-specific, differs from FragmentA -->
    <TextView
        android:id="@+id/text_explain"
        android:layout_
        android:layout_
        android:text="@string/explain" />
    
    <RelativeLayout
        android:layout_
        android:layout_
        android:layout_below="@+id/text_explain">

        <!-- Include the common layout -->
        <include
            layout="@layout/common_layout.xml"
            android:layout_
            android:layout_ />
    </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_
        android:layout_
        android:text="@string/up"/>

    <Button
        android:id="@+id/button_down"
        android:layout_
        android:layout_
        android:layout_below="@id/button_up"
        android:text="@string/down" />
</merge>

接下来是片段类。首先是我们的BaseFragment 实现。

onCreateView() 是绑定膨胀的地方。我们能够根据包含common_layout.xml 的片段绑定来绑定CommonLayoutBinding。我在onCreateView() 之上定义了一个抽象方法onCreateViewBinding(),它从FragmentAFragmentB 返回VewBinding。这样,当我需要创建 CommonLayoutBinding 时,我可以确保片段的绑定存在。

接下来我可以通过调用commonBinding = CommonLayoutBinding.bind(binding.getRoot()); 创建CommonLayoutBinding 的实例。请注意,具体片段绑定的根视图被传递给bind()

getCommonBinding() 允许从扩展片段提供对CommonLayoutBinding 的访问。我们可以更严格:BaseFragment 应该提供访问该绑定的具体方法,而不是将其公开给它的子类。

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;

现在看看其中一个子类FragmentA。 我们从onCreateViewBinding() 创建我们的绑定,就像我们从onCreateView() 一样。原则上,它仍然是从onCreateVIew() 调用的。如上所述,此绑定从基类中使用。我正在使用getCommonBinding() 来访问来自common_layout.xml 的视图。 BaseFragment 的每个子类现在都可以从 ViewBinding 访问这些视图。

这样我可以将所有基于通用视图的逻辑上移到基类中。

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;

优点:

通过将重复代码移至基类来减少重复代码。现在所有片段中的代码都更加清晰,并简化为基本要素 通过将可重用视图移动到通过&lt;include /&gt; 包含的布局中,布局更加简洁

缺点:

可能不完全适用于无法将视图移动到常用布局文件中的情况 可能视图需要在片段/布局之间定位不同 许多 &lt;included /&gt; 布局会导致许多 Binding 类,然后没有什么可取胜的 需要另一个绑定实例 (CommonLayoutBinding)。每个子级(FragmentAFragmentB)不仅有一个绑定类,它提供对视图层次结构中所有视图的访问

如果视图无法移动到通用布局中怎么办? 我对如何将其作为最佳实践来解决非常感兴趣!让我们考虑一下:在具体的ViewBinding 周围引入一个包装类。我们可以引入一个接口来提供对常用视图的访问。从片段中,我们将绑定包装在这些包装类中。另一方面,这将导致每个 ViewBinding 类型的许多包装器。但是我们可以使用抽象方法(泛型)将这些包装器提供给BaseFragmentBaseFragment 然后能够访问视图或使用定义的接口方法处理它们。 你怎么看?

结论: 也许这只是 ViewBinding 的实际限制,一种布局需要拥有自己的 Binding 类。 如果您在无法共享布局并且需要在每个布局中声明重复的情况下找到了一个好的解决方案,请告诉我。

我不知道这是否是最佳做法,或者是否有更好的解决方案。但在这是我用例的唯一已知解决方案之前,这似乎是一个好的开始!

【讨论】:

你为什么不接受这个作为答案?更多赞成票的答案是错误的。它没有解决任何问题,并且发布它的人可能甚至没有阅读您实际询问的内容就做到了。接受你的,因为这是正确的答案【参考方案3】:

这是我的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
    

【讨论】:

【参考方案4】:

基类会是这样的

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

现在在您要使用的活动中

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 中只使用按使用绑定

【讨论】:

【参考方案5】:

我创建了这个抽象类作为基础;

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)

【讨论】:

干得好,伙计!!!【参考方案6】:

2021 年 2 月 4 日更新:在研究并从许多来源获得灵感后,我写了一个 article。本文将根据我未来在视图绑定方面的经验进行更新,因为我们公司现在已经放弃了近 80% 的合成绑定。


我还提出了一个有效使用最终变量的基类解决方案。我的主要目标是:

    在基类中处理所有绑定生命周期 让子类提供绑定类实例而不单独使用该路由(例如,如果我有一个抽象函数abstract fun getBind():T,子类可以实现它并直接调用它。我不希望那样我相信,将绑定保持在基类中的全部意义都没有实际意义)

原来如此。首先是我的应用程序的当前结构。活动不会自行膨胀,基类会为它们做:

儿童活动和片段:

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()




正如您所见,儿童班总是很容易扩展,因为基础活动会完成所有繁重的工作。并且由于合成材料被广泛使用,因此没有太大问题。 要使用具有前面提到的约束的绑定类,我会:

    需要子类实现将数据提供回父片段的功能。这是简单的部分,只需创建更多返回子绑定类实例的抽象函数即可。

    将子类的视图绑定存储在一个变量中(比如val binding:T),这样基类可以在销毁时将其无效,并相应地处理生命周期。有点棘手,因为事先不知道孩子的 Binding 类实例类型。但是将父级设为通用(&lt;T:ViewBinding&gt;)就可以了

    将视图返回给系统进行膨胀。再一次,很简单,因为谢天谢地,对于大多数组件,系统接受一个膨胀的视图,并且拥有孩子的绑定实例将让我向系统提供一个视图

    防止子类直接使用第1点创建的路由。想一想:如果一个子类有一个函数 getBind()... 返回他们自己的绑定类实例,为什么他们不使用它而使用 super.binding 呢?是什么阻止了他们在 onDestroy() 中使用 getBind() 函数,而不应该访问绑定?

所以这就是为什么我将这个函数设为 void 并将一个可变列表传递给它。子类现在会将它们的绑定添加到父类将访问的列表中。如果他们不这样做,它会抛出一个 NPE 。如果他们试图在销毁或其他地方使用它,它将再次抛出 illegalstate exception 。我还创建了一个方便的高阶函数withBinding(..) 以方便使用。

基础绑定活动和片段:



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")
    



子活动和片段:


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))
    





【讨论】:

并没有真正回答这个问题。您正在为 viewBinding 使用默认管理器,也许我们所有人都已经这样做了。 OP 是指拥有 2 个或多个具有相同 ID 的相似视图,如何避免管理 2 个类来访问公共属性。假设我有 2 个 ViewHolders 具有相同的元素但不同的 XML 设计,如何传递单个 Binding 接口而不是现在必须操作 2 个类。【参考方案7】:

我认为一个简单的应对方法是使用公共类的bind 方法。

我知道这不适用于所有情况,但它适用于具有相似元素的视图。

如果我有两个布局 row_type_1.xmlrow_type_2.xml 共享公共元素,那么我可以执行以下操作:

ROW_TYPE_1 -> CommonRowViewHolder(
                    RowType1Binding.inflate(LayoutInflater.from(parent.context), parent, false))

那么对于类型 2,不要创建另一个 ViewHolder 来接收它自己的 Binding 类,而是执行以下操作:

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 ...
    

【讨论】:

【参考方案8】:
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


【讨论】:

【参考方案9】:

这是伟大的Chetan Gupta's answer 的略微修改的 Kotlin 版本。 避免使用“UNCHECKED_CAST”。

活动

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
    

【讨论】:

也许你应该清除 onDestroyView() 中的绑定

以上是关于如何将 ViewBinding 与抽象基类一起使用的主要内容,如果未能解决你的问题,请参考以下文章

如何在swift中使基类属性通用,以便可以与多种模型类型一起使用?

我们如何将 OCP 与委托一起使用?

一起Talk Android吧(第三百八十三回:视图绑定-ViewBinding)

如何将 Django 信号与抽象模型一起使用?

Django中的抽象基类模型与代理模型

抽象类 BaseVMActivity<VM : ViewModel, B : ViewBinding> 和抽象类 BaseVMActivity(VM : ViewModel, B : Vi