So verwenden Sie ViewBinding mit einer abstrakten Basisklasse

Lesezeit: 19 Minuten

Dannys Benutzeravatar
Danni

Ich habe angefangen, ViewBinding zu verwenden. Nachdem ich nach einem Beispiel oder einem Rat gesucht hatte, habe ich diese Frage hier gepostet.

Wie verwende ich ViewBinding mit einer abstrakten Basisklasse, die dieselbe Logik für Ansichten verarbeitet, von denen erwartet wird, dass sie in jedem untergeordneten Layout vorhanden sind?

Szenario:

Ich habe eine Basisklasse public abstract class BaseFragment. Es gibt mehrere Fragmente, die diese Basisklasse erweitern. Diese Fragmente haben gemeinsame Ansichten, die von der Basisklassenimplementierung (mit der “alten” findViewById()). Beispielsweise wird erwartet, dass das Layout jedes Fragments eine TextView mit der ID text_title enthält. Hier erfahren Sie, wie es von der gehandhabt wird BaseFragment‘s onViewCreated():

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

Jetzt generiert die ViewBinding-API Bindungsklassen für jedes untergeordnete Fragment. Ich kann die Ansichten mit der Bindung referenzieren, aber ich kann die konkreten Bindungen aus der Basisklasse nicht verwenden. Auch wenn ich Generika in die Basisklasse eingeführt habe, gibt es zu viele Arten von Fragmentbindungen. Daher habe ich diese Lösung vorerst verworfen.

Was ist die empfohlene Methode zum Umgang mit den Ansichten der Bindung aus der abstrakten Basisklasse? Gibt es Best Practices? Ich habe keinen integrierten Mechanismus in der API gefunden, um dieses Szenario auf elegante Weise zu handhaben.

Wenn erwartet wird, dass die untergeordneten Fragmente gemeinsame Ansichten enthalten, könnte ich abstrakte Methoden bereitstellen, die die Ansichten aus den konkreten Bindungen der Fragmente zurückgeben und sie von der Basisklasse aus zugänglich machen. (Zum Beispiel protected abstract TextView getTitleView();). Aber ist dies ein Vorteil, anstatt zu verwenden findViewById()? Gibt es andere (bessere) Lösungen?

  • Ich denke du kannst schreiben public abstract int getLayoutResourse(); in Ihrem BaseFragment und übergeben Sie es an DataBindingUtil.inflate() Anstatt von R.layout.frag_layout oder ich habe die Frage nicht verstanden

    – Alex Rmcf

    16. Juni 2020 um 12:11 Uhr


  • @AlexRmcf zuerst: Ich verwende kein DataBinding, ich möchte einfach ViewBinding verwenden. Ja, das wäre möglich, eine zu bekommen ViewDataBinding aus der Grundklasse. Aber ich kann nicht auf Ansichten zugreifen über ViewDataBinding.textTitle beispielsweise aus der Basisklasse, ohne den konkreten Typ der Bindungsklasse zu kennen.

    – Danni

    16. Juni 2020 um 12:31 Uhr

  • Ich habe das gleiche Problem, ich habe einen Basis-ViewHolder, der Ansichten und untergeordnete Elemente enthält, die auch ihre eigenen Ansichten enthalten. Wie kann ich ihren Ansichtsbinder an den übergeordneten ViewHolder übergeben, ohne Folgendes tun zu müssen: “if (untergeordnete Instanz von X) dann Bindung = XBinding “

    – Nayk0

    6. Juli 2020 um 7:28 Uhr

  • hey hast du dafür eine lösung gefunden?

    – ansh sachdeva

    12. November 2020 um 14:06 Uhr

  • @anshsachdeva derzeit scheint es, als hätte ich eine Lösung. Ich muss noch herausfinden, ob es funktioniert und werde bald eine Antwort geben. Im besten Fall kann ich morgen mit einem positiven Ergebnis antworten 😉

    – Danni

    12. November 2020 um 16:53 Uhr

Benutzeravatar von Chetan Gupta
Chetan Gupta

Hallo, ich habe einen Blogbeitrag erstellt, der die Ansichtsbindung ausführlich behandelt und sowohl das Kompositionsmuster/Delegatmuster zur Implementierung der Ansichtsbindung als auch die Verwendung der Vererbungsprüfung über den Link enthält

Checkout für den vollständigen Code von BaseActivity und BaseFragment zusammen mit der Verwendung

👉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
    }
}

Für die Verwendung, Vorausmuster und Antimuster-Checkout-Blog Androidbites|ViewBinding

  • BindLayout ist BindingInflater ?

    – Hamid Mahmoodi

    8. Januar 2021 um 19:43 Uhr

  • Danke @Chetan, genau das, wonach ich gesucht habe. Ich habe Ihren Code ein wenig geändert, um “UNCHECKED_CAST” (in Kotlin) zu vermeiden: private var _binding: ViewBindingType? = null protected val binding get() = requireNotNull(_binding) abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> ViewBindingType

    – Myroslaw

    26. Januar 2021 um 13:01 Uhr


  • Hallo @ChetanGupta, bitte füge der Antwort auch eine Beispielaktivität/ein Beispielfragment hinzu.

    – Angad Singh

    30. Mai 2021 um 8:15 Uhr

  • Ich beschäftige mich derzeit mit dem gleichen Problem, aber ich sehe nicht, wie dies eine Lösung ist. Wie sollen Sie den generischen Code erstellen, der den Text für „text_title“ (im Fall von OP) in der Klasse ViewBindingFragment festlegt, wenn Sie noch nicht wissen, welche bestimmte Bindung verwendet wird?

    – Tharkius

    25. Januar um 12:26 Uhr

  • Welches Konzept/Ansatz wird verwendet? bindingInflater hier? Ist es funktionsfähig? Ich verstehe das Konzept des Aufrufs in Kotlin, aber nicht mit diesem. Kann mir das jemand erklären?

    – Bitweise DEVS

    26. Juni um 22:30 Uhr

Dannys Benutzeravatar
Danni

Ich habe eine anwendbare Lösung für mein konkretes Szenario gefunden und möchte sie mit Ihnen teilen.

Beachten Sie, dass dies keine Erklärung dafür ist, wie ViewBinding funktioniert.

Ich habe unten einen Pseudocode erstellt. (Von meiner Lösung migriert mit DialogFragments die ein anzeigen AlertDialog). Ich hoffe, es ist fast korrekt an Fragmente angepasst (onCreateView() vs. onCreateDialog()). Ich habe es so hinbekommen.

Stellen Sie sich vor, wir haben eine Zusammenfassung BaseFragment und zwei Erweiterungsklassen FragmentA und FragmentB.

Schauen Sie sich zunächst alle unsere Grundrisse an. Beachten Sie, dass ich die wiederverwendbaren Teile des Layouts in eine separate Datei verschoben habe, die später aus den Layouts des konkreten Fragments hinzugefügt wird. Bestimmte Ansichten bleiben in den Layouts ihrer Fragmente. Die Verwendung eines gemeinsamen Layouts ist für dieses Szenario wichtig.

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>

Als nächstes die Fragmentklassen. Zuerst unsere BaseFragment Implementierung.

onCreateView() ist der Ort, an dem die Bindungen aufgepumpt werden. Wir sind in der Lage, die zu binden CommonLayoutBinding basierend auf den Bindungen des Fragments, wo die common_layout.xml ist enthalten. Ich habe eine abstrakte Methode definiert onCreateViewBinding() oben angerufen onCreateView() das gibt die zurück ViewBinding aus FragmentA und FragmentB. Auf diese Weise stelle ich sicher, dass die Bindung des Fragments vorhanden ist, wenn ich die erstellen muss CommonLayoutBinding.

Als nächstes kann ich eine Instanz von erstellen CommonLayoutBinding durch Anruf commonBinding = CommonLayoutBinding.bind(binding.getRoot());. Beachten Sie, dass die Stammansicht von der Bindung des konkreten Fragments übergeben wird bind().

getCommonBinding() ermöglicht den Zugriff auf die CommonLayoutBinding aus den sich ausdehnenden Fragmenten. Wir könnten strenger sein: die BaseFragment sollte konkrete Methoden bereitstellen, die auf diese Bindung zugreifen, anstatt sie für ihre untergeordneten Klassen öffentlich zu machen.

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 sure 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;
}

Schauen Sie sich jetzt eine der untergeordneten Klassen an, FragmentA. Aus onCreateViewBinding() Wir erstellen unsere Bindung so, wie wir es von machen würden onCreateView(). Im Prinzip heißt es immer noch ab onCreateVIew(). Diese Bindung wird wie oben beschrieben von der Basisklasse verwendet. ich benutze getCommonBinding() um auf Ansichten zugreifen zu können common_layout.xml. Jede Kinderklasse von BaseFragment ist jetzt in der Lage, auf diese Ansichten von zuzugreifen ViewBinding.

Auf diese Weise kann ich die gesamte Logik, die auf gemeinsamen Ansichten basiert, in die Basisklasse verschieben.

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;
}

Vorteile:

  • Doppelter Code wurde reduziert, indem er in die Basisklasse verschoben wurde. Code in allen Fragmenten ist jetzt viel übersichtlicher und auf das Wesentliche reduziert
  • Saubereres Layout durch Verschieben wiederverwendbarer Ansichten in ein Layout, das über enthalten ist <include />

Nachteile:

  • Möglicherweise nicht anwendbar, wenn Ansichten nicht in eine häufig verwendete Layoutdatei verschoben werden können
    • Ansichten müssen möglicherweise zwischen Fragmenten/Layouts unterschiedlich positioniert werden
    • Viele <included /> Layouts würden zu vielen Binding-Klassen führen, dann wäre nichts gewonnen
  • Erfordert eine weitere Bindungsinstanz (CommonLayoutBinding). Es gibt nicht nur eine Bindungsklasse für jedes Kind (FragmentA, FragmentB), die Zugriff auf alle Ansichten in der Ansichtshierarchie bietet

Was ist, wenn Ansichten nicht in ein gemeinsames Layout verschoben werden können?

Ich bin sehr daran interessiert, wie dies als Best Practice gelöst werden kann! Denken wir darüber nach: Führen Sie eine Wrapper-Klasse um den Beton ein ViewBinding.

Wir könnten eine Schnittstelle einführen, die Zugriff auf häufig verwendete Ansichten bietet. Aus den Fragmenten packen wir unsere Bindungen in diese Wrapper-Klassen. Andererseits würde dies zu vielen Wrappern für jeden ViewBinding-Typ führen. Aber wir können diese Wrapper an die liefern BaseFragment Verwenden einer abstrakten Methode (eines Generikums). BaseFragment kann dann auf die Ansichten zugreifen oder sie mit den definierten Schnittstellenmethoden bearbeiten. Was denkst du?

Abschließend:

Vielleicht ist es einfach eine Einschränkung von ViewBinding, dass ein Layout eine eigene Binding-Klasse haben muss. Wenn Sie eine gute Lösung für den Fall gefunden haben, dass das Layout nicht geteilt werden kann und in jedem Layout als dupliziert deklariert werden muss, lassen Sie es mich bitte wissen.

Ich weiß nicht, ob dies die beste Vorgehensweise ist oder ob es bessere Lösungen gibt. Aber obwohl dies die einzige bekannte Lösung für meinen Anwendungsfall ist, scheint es ein guter Anfang zu sein!

Hier ist ein vollständiges Beispiel meiner BaseViewBindingFragment das:

  • erfordert KEINE abstract Eigenschaften oder Funktionen,
  • es stützt sich auf Java-Reflexion (nicht Kotlin-Reflexion) – siehe fun createBindingInstancewo VB Es wird ein generisches Typargument verwendet
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
    }
}

Ich habe diese abstrakte Klasse als Basis erstellt;

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

}

Verwendungszweck;

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

}

Die Basisklasse wird so ablaufen

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
}

Jetzt in Ihrer Aktivität, wo Sie verwenden möchten

class MainActivity : BaseActivity<ActivityMainBinding>(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.tvName.text="ankit"
    }

    override fun inflateLayout(layoutInflater: LayoutInflater)  = ActivityMainBinding.inflate(layoutInflater)
}

Verwenden Sie jetzt in onCreate nur die Bindung gemäß der Verwendung

Update 4. Februar 2021: Ich habe eine geschrieben Artikel nach Recherche und Inspiration aus vielen Quellen. Dieser Artikel würde mit meinen zukünftigen Erfahrungen mit Sichtbindung aktualisiert, da unser Unternehmen die synthetische Bindung jetzt um fast 80 % aufgegeben hat.


Ich habe auch eine Basisklassenlösung entwickelt, die effektiv finale Variablen verwendet. Mein Hauptziel war:

  1. Behandeln Sie den gesamten Bindungslebenszyklus in einer Basisklasse
  2. Lassen Sie die untergeordnete Klasse die verbindliche Klasseninstanz bereitstellen, ohne diese Route selbst zu verwenden (z. B. wenn ich eine abstrakte Funktion hätte abstract fun getBind():T , könnte die untergeordnete Klasse es implementieren und direkt aufrufen. Ich wollte das nicht, da das den ganzen Punkt, Bindungen in der Basisklasse zu halten, umstritten machen würde, glaube ich)

Hier ist es also. Zuerst der aktuelle Aufbau meiner App. Die Aktivitäten werden sich nicht selbst aufblasen, die Basisklasse würde es für sie tun:

Kinderaktivitäten und Fragmente:

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
        }


    }


}

Basisaktivitäten und Fragmente:


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


}

Wie Sie sehen können, waren die Kinderklassen immer einfach zu skalieren, da die Basisaktivitäten die ganze schwere Arbeit erledigen würden. und Da Kunststoffe in großem Umfang verwendet wurden, gab es kein großes Problem. Um Bindungsklassen mit den zuvor erwähnten Einschränkungen zu verwenden, würde ich:

  1. Benötigen Sie die untergeordneten Klassen, um Funktionen zu implementieren, die Daten an die übergeordneten Fragmente zurückgeben würden. Das ist der einfache Teil, indem Sie einfach abstraktere Funktionen erstellen, die die Instanz der Binding-Klasse des Kindes zurückgeben.

  2. Speichern Sie die Ansichtsbindung der untergeordneten Klasse in einer Variablen (z val binding:T), sodass die Basisklasse sie beim Zerstören annullieren und den Lebenszyklus entsprechend handhaben könnte. Ein wenig knifflig, da der Instanztyp der Binding-Klasse des Kindes nicht im Voraus bekannt ist. Aber den Elternteil als generisch zu machen ( <T:ViewBinding>) wird die Arbeit erledigen

  3. den Blick zurück auf das System für die Inflation richten. Wieder einfach, weil das System zum Glück für die meisten Komponenten eine aufgeblasene Ansicht akzeptiert und die Bindungsinstanz des Kindes es mir ermöglicht, eine Ansicht zurück zum System bereitzustellen

  4. Verhindern, dass die untergeordnete Klasse die in Punkt 1 erstellte Route direkt verwendet. Denken Sie darüber nach: Wenn eine untergeordnete Klasse eine Funktion hätte getBind(){...} die ihre eigene Bindungsklasseninstanz zurückgibt, warum verwenden sie diese nicht und verwenden stattdessen super.binding ? und was hindert sie daran, die zu verwenden getBind() Funktion in der onDestroy(), wo auf die Bindings lieber nicht zugegriffen werden soll?

Deshalb habe ich diese Funktion ungültig gemacht und ihr eine veränderliche Liste übergeben. Die untergeordnete Klasse würde nun ihre Bindung zu der Liste hinzufügen, auf die die übergeordnete Klasse zugreifen würde. Wenn sie dies nicht tun, wird ein NPE ausgelöst. Wenn sie versuchen, es in Destroye oder an einem anderen Ort zu verwenden, wird es erneut einen werfen illegalstate exception . Ich erstelle auch eine praktische High-Order-Funktion withBinding(..) für eine einfache Verwendung.

Basenbindungsaktivität und Fragment:



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

}

Kinderaktivität und Fragment:


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


}


Benutzeravatar von htafoya
htafoya

Ich denke, dass eine einfache Antwort zu verwenden ist bind Methode der gemeinsamen Klasse.

Ich weiß, dass dies nicht in ALLEN Fällen funktioniert, aber es wird für Ansichten mit ähnlichen Elementen funktionieren.

Wenn ich zwei Layouts habe row_type_1.xml und row_type_2.xml mit denen sie gemeinsame Elemente teilen, kann ich dann Folgendes tun:

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

Führen Sie dann für Typ 2 Folgendes aus, anstatt einen weiteren ViewHolder zu erstellen, der seine eigene Binding-Klasse erhält:

ROW_TYPE_2 -> {
    val type2Binding = RowType2Binding.inflate(LayoutInflater.from(parent.context), parent, false))
    CommonRowViewHolder(RowType1Binding.bind(type2Binding))
}

Wenn es sich stattdessen um eine Teilmenge von Komponenten handelt, könnte eine Vererbung erfolgen

CommonRowViewHolder: ViewHolder {
    fun bind(binding: RowType1Holder)
}

Type2RowViewHolder: CommonRowViewHolder {

    fun bind(binding: RowType2Holder) {
        super.bind(Type1RowViewHolder.bind(binding))
        
        //perform specific views for type 2 binding ...
    }
}

1395110cookie-checkSo verwenden Sie ViewBinding mit einer abstrakten Basisklasse

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy