Как использовать ViewBinding с абстрактным базовым классом

Я начал использовать ViewBinding. После поиска примеров или советов, как использовать ViewBinding с абстрактным базовым классом, который должен обрабатывать ту же логику для представлений, которые, как ожидается, будут присутствовать в каждом дочернем макете, я решил опубликовать этот вопрос здесь.

Сценарий:
у меня есть базовый класс public abstract class BaseFragment. Есть несколько фрагментов, расширяющих этот базовый класс. Эти фрагменты имеют общие представления, которые обрабатываются из реализации базового класса (со «старым» findViewById()). Например, ожидается, что макет каждого фрагмента будет содержать TextView с идентификатором text_title. Вот как это делается из BaseFragment onViewCreated():

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()? Что вы думаете? Какие-нибудь другие (лучшие) решения? Давайте начнем обсуждение.


person Danny    schedule 16.06.2020    source источник
comment
Я думаю, вы можете написать public abstract int getLayoutResourse(); в своем BaseFragment и передать его DataBindingUtil.inflate() вместо R.layout.frag_layout, иначе я не понял вопроса   -  person Alex Rmcf    schedule 16.06.2020
comment
Сначала @AlexRmcf: я не использую DataBinding, я просто хочу использовать ViewBinding. Да, можно было бы получить ViewDataBinding из базового класса. Но я не могу получить доступ к представлениям через ViewDataBinding.textTitle, например, из базового класса, не зная конкретного типа класса привязки.   -  person Danny    schedule 16.06.2020
comment
У меня такая же проблема, у меня есть базовый ViewHolder, который содержит представления и дочерние элементы, которые также содержат их собственные представления, как я могу передать их View Binder родительскому ViewHolder без необходимости делать if (дочерний экземпляр X), затем привязка = XBinding   -  person Nayk0    schedule 06.07.2020
comment
эй, ты нашел решение этой проблемы?   -  person ansh sachdeva    schedule 12.11.2020
comment
@anshsachdeva в настоящее время кажется, что у меня есть одна проблема. Мне все еще нужно разобраться, работает ли это, и скоро я дам ответ. В лучшем случае завтра могу ответить положительным результатом;)   -  person Danny    schedule 12.11.2020
comment
@heisenberg с нетерпением этого ждет. Я также придумал несколько реализаций, но они все еще выглядят немного неопрятно, поскольку эффективно используют конечные переменные. Я надеюсь, что ваше решение не использует отражение, пытаясь держаться подальше от них: D   -  person ansh sachdeva    schedule 13.11.2020


Ответы (7)


Привет, я создал сообщение в блоге, в котором подробно рассматривается привязка представлений, а также включены как шаблон композиции / делегата для реализации привязки представления, так и использование проверки наследования из ссылки

оформление заказа для получения полного кода BaseActivity и BaseFragment вместе с использованием

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

person Chetan Gupta    schedule 06.12.2020
comment
bindLayout - это bindingInflater? - person hamid; 08.01.2021
comment
исправлено спасибо @hamid - person Chetan Gupta; 09.01.2021
comment
Спасибо, @Chetan, именно то, что я искал. Я немного изменил ваш код, чтобы избежать UNCHECKED_CAST (в Котлине): private var _binding: ViewBindingType? = null protected val binding get() = requireNotNull(_binding) abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ViewBindingType - person Myroslav Kolodii; 26.01.2021
comment
Привет, @ChetanGupta, пожалуйста, также включите в ответ пример активности / фрагмента. - person Angad Singh; 30.05.2021

Я нашел подходящее решение для своего конкретного сценария и не хочу делиться им с вами.

Обратите внимание, что это не объяснение того, как работает ViewBinding.

Я создал псевдокод ниже, чтобы поделиться с вами. (Перенос из моего решения с использованием DialogFragments, отображающего AlertDialog). Надеюсь, он почти правильно адаптирован к фрагментам (onCreateView() vs. onCreateDialog()). Я заставил так работать.

Представьте, что у нас есть абстрактный BaseFragment и два расширяющих класса 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>

Далее классы фрагментов. Сначала наша BaseFragment реализация.

onCreateView() - это место, где надуваются крепления. Мы можем связать CommonLayoutBinding на основе привязок фрагмента, в который включен common_layout.xml. Я определил абстрактный метод onCreateViewBinding(), вызываемый поверх onCreateView(), который возвращает VewBinding из FragmentA и FragmentB. Таким образом я гарантирую, что привязка фрагмента присутствует, когда мне нужно создать CommonLayoutBinding.

Затем я могу создать экземпляр CommonLayoutBinding, вызвав commonBinding = CommonLayoutBinding.bind(binding.getRoot());. Обратите внимание, что корневое представление из привязки конкретного фрагмента передается в 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;
}

Плюсы:

  • Уменьшено количество повторяющихся кодов за счет его перемещения в базовый класс. Код во всех фрагментах стал более понятным и сведен к основному.
  • Более чистый макет за счет перемещения многоразовых представлений в макет, включенный через <include />

Минусы:

  • Possibly not exact applicable where views can't be moved into a commonly used layout file
    • Possibly views needs to be positioned different between frgments/layouts
    • Многие <included /> макеты приведут к множеству классов привязки, тогда ничего не выиграет
  • Требуется другой экземпляр привязки (CommonLayoutBinding). Для каждого дочернего элемента (FragmentA, FragmentB) существует не только один класс привязки, который обеспечивает доступ ко всем представлениям в иерархии представлений.

Что делать, если представления не могут быть перемещены в общий макет?
Мне очень интересно, как решить эту проблему в качестве передовой практики! Давайте подумаем: представьте класс-оболочку вокруг конкретного ViewBinding. Мы могли бы представить интерфейс, обеспечивающий доступ к часто используемым представлениям. Из фрагментов мы оборачиваем наши привязки в эти классы-оболочки. С другой стороны, это привело бы к созданию множества оберток для каждого типа ViewBinding. Но мы можем предоставить эту оболочку для BaseFragment, используя абстрактный метод (дженерики). BaseFragment затем может получить доступ к представлениям или работать с ними, используя определенные методы интерфейса. Как вы думаете?

В заключение:
Может быть, это просто фактический предел ViewBinding, когда один макет должен иметь собственный класс Binding. Если вы нашли хорошее решение в случаях, когда макет не может быть опубликован и должен быть объявлен дублированным в каждом макете, дайте мне знать.

Я не знаю, является ли это наилучшей практикой или есть лучшие решения. Но пока это единственное известное решение для моего случая использования, это кажется хорошим началом!

person Danny    schedule 13.11.2020

Вот полный пример моего 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
    }
}
person Matej Hlatky    schedule 05.05.2021

Обновление от 4 февраля 2021 года: после исследования я написал статью и черпая вдохновение из многих источников. Эта статья будет обновлена ​​с учетом моего будущего опыта с привязкой вида, так как наша компания отказалась от синтетической привязки почти на 80%.


Я также придумал решение базового класса, которое эффективно использует конечные переменные. Моей главной целью было:

  1. обрабатывать весь жизненный цикл привязки в базовом классе
  2. пусть дочерний класс предоставит экземпляр класса привязки, не используя этот маршрут сам по себе (например, если бы у меня была абстрактная функция 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()


}

Как видите, детские классы всегда было легко масштабировать, так как базовые занятия выполняли бы всю тяжелую работу. и Поскольку синтетика широко использовалась, особой проблемы не было. Чтобы использовать классы привязки с ранее упомянутыми ограничениями, я бы:

  1. Нужны дочерние классы для реализации функций, которые будут предоставлять данные обратно родительским фрагментам. Это простая часть, достаточно просто создать более абстрактные функции, которые возвращают экземпляр дочернего класса привязки.

  2. Сохраните привязку представления дочернего класса в переменной (скажем, val binding:T), чтобы базовый класс мог обнулить ее при уничтожении и обработать жизненный цикл соответствующим образом. Это немного сложно, поскольку тип экземпляра дочернего класса Binding заранее неизвестен. Но если сделать родителя универсальным (<T:ViewBinding>), то все будет хорошо.

  3. возврат изображения обратно в систему для инфляции. опять же, легко, потому что, к счастью для большинства компонентов, система принимает расширенное представление, и наличие дочернего экземпляра привязки позволит мне предоставить представление обратно в систему

  4. Предотвращение прямого использования дочерним классом маршрута, созданного в точке 1. подумайте об этом: если у дочернего класса была функция getBind(){...}, которая возвращает их собственный экземпляр класса привязки, почему бы они не использовали это, а вместо этого использовали super.binding? и что им мешает использовать функцию getBind() в onDestroy (), где привязки не должны быть доступны?

Вот почему я сделал эту функцию недействительной и передал ей изменяемый список. дочерний класс теперь добавит свою привязку в список, к которому будет иметь доступ родительский класс. если они этого не сделают, это вызовет 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))
    }


}


person ansh sachdeva    schedule 17.11.2020
comment
На самом деле не отвечает на вопрос. Вы используете менеджер по умолчанию для viewBinding, с которым, возможно, все мы уже сделали. OP относится к наличию 2 или более похожих представлений с одинаковыми идентификаторами, как избежать управления 2 классами для доступа к общим свойствам. Скажем, у меня есть 2 ViewHolder с одинаковыми элементами, но с разным XML-дизайном, как передать один интерфейс Binding вместо того, чтобы теперь работать с двумя классами. - person htafoya; 03.05.2021

Я думаю, что простой ответ - использовать bind метод общего класса.

Я знаю, что это не будет работать во ВСЕХ случаях, но будет работать для представлений с похожими элементами.

Если у меня есть два макета row_type_1.xml и row_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 ...
    }
}
person htafoya    schedule 03.05.2021

Базовый класс будет выглядеть так

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 просто используйте привязку в соответствии с использованием

person Ankit Kumar    schedule 11.07.2021

Это немного измененная 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
    }
}
person Myroslav Kolodii    schedule 26.01.2021
comment
Возможно, вам стоит очистить привязку в onDestroyView () - person Shefchenko; 27.01.2021
comment
Спасибо, поправили. - person Myroslav Kolodii; 28.01.2021