459 Stimmen

Behandlung von Schaltflächenklicks mit XML onClick in Fragmenten

Vor Honeycomb (Android 3) wurde jede Aktivität registriert, um Schaltflächenklicks über die onClick Tag in der XML-Datei eines Layouts:

android:onClick="myClickMethod"

Innerhalb dieser Methode können Sie Folgendes verwenden view.getId() und eine switch-Anweisung für die Logik der Schaltfläche.

Mit der Einführung von Honeycomb zerlege ich diese Aktivitäten in Fragmente, die in vielen verschiedenen Aktivitäten wiederverwendet werden können. Das Verhalten der Schaltflächen ist größtenteils Activity-unabhängig, und ich möchte, dass der Code in der Fragment-Datei enthalten ist ohne mit der alten (vor 1.6) Methode der Registrierung der OnClickListener für jede Taste.

final Button button = (Button) findViewById(R.id.button_id);
button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        // Perform action on click
    }
});

Das Problem ist, dass, wenn meine Layouts aufgeblasen werden, ist es immer noch die Hosting-Aktivität, die die Schaltfläche Klicks erhält, nicht die einzelnen Fragmente. Gibt es einen guten Ansatz für entweder

  • Das Fragment registrieren, um die Schaltflächenklicks zu empfangen?
  • Die Klick-Ereignisse von der Aktivität an das Fragment weitergeben, zu dem sie gehören?

1 Stimmen

Können Sie die Registrierung von Zuhörern nicht innerhalb des onCreate des Fragments vornehmen?

26 Stimmen

@jodes Ja, aber ich möchte nicht die setOnClickListener y findViewById für jede Schaltfläche, deshalb onClick wurde hinzugefügt, um die Dinge zu vereinfachen.

4 Stimmen

Wenn ich mir die akzeptierte Antwort ansehe, denke ich, dass die Verwendung von setOnClickListener lockerer gekoppelt ist als das Festhalten am XML onClick-Ansatz. Wenn die Aktivität jeden Klick an das richtige Fragment "weiterleiten" muss, bedeutet dies, dass der Code jedes Mal geändert werden muss, wenn ein Fragment hinzugefügt wird. Die Verwendung einer Schnittstelle zur Entkopplung von der Basisklasse des Fragments ist dabei nicht hilfreich. Wenn das Fragment mit der richtigen Schaltfläche selbst registriert, bleibt die Aktivität völlig agnostisch, die besser Stil IMO ist. Siehe auch die Antwort von Adorjan Princz.

1voto

amalBit Punkte 11775

Ergänzend zur Antwort von Blundell,
Wenn Sie mehr Fragmente haben, mit vielen onClicks:

Tätigkeit:

Fragment someFragment1 = (Fragment)getFragmentManager().findFragmentByTag("someFragment1 "); 
Fragment someFragment2 = (Fragment)getFragmentManager().findFragmentByTag("someFragment2 "); 
Fragment someFragment3 = (Fragment)getFragmentManager().findFragmentByTag("someFragment3 "); 

...onCreate etc instantiating your fragments

public void myClickMethod(View v){
  if (someFragment1.isVisible()) {
       someFragment1.myClickMethod(v);
  }else if(someFragment2.isVisible()){
       someFragment2.myClickMethod(v);
  }else if(someFragment3.isVisible()){
       someFragment3.myClickMethod(v); 
  }

} 

In deinem Fragment:

  public void myClickMethod(View v){
     switch(v.getid()){
       // Just like you were doing
     }
  }

1voto

d4vidi Punkte 2199

Obwohl ich einige nette Antworten gesehen habe, die sich auf Datenbindung stützen, habe ich keine gesehen, die diesen Ansatz voll ausschöpft - im Sinne der Ermöglichung von Fragmentauflösung bei gleichzeitiger Ermöglichung von fragmentfreien Layout-Definitionen in XML's.

Unter der Annahme einer Datenbindung ist aktiviert, hier ist eine allgemeine Lösung, die ich vorschlagen kann; Ein bisschen lang, aber es funktioniert definitiv (mit einigen Vorbehalten):

Schritt 1: Benutzerdefinierte OnClick-Implementierung

Dies führt eine fragmentierte Suche durch Kontexte durch, die mit der angetippten Ansicht (z. B. Schaltfläche) verbunden sind:

// CustomOnClick.kt

@file:JvmName("CustomOnClick")

package com.example

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import java.lang.reflect.Method

fun onClick(view: View, methodName: String) {
    resolveOnClickInvocation(view, methodName)?.invoke(view)
}

private data class OnClickInvocation(val obj: Any, val method: Method) {
    fun invoke(view: View) {
        method.invoke(obj, view)
    }
}

private fun resolveOnClickInvocation(view: View, methodName: String): OnClickInvocation? =
    searchContexts(view) { context ->
        var invocation: OnClickInvocation? = null
        if (context is Activity) {
            val activity = context as? FragmentActivity
                    ?: throw IllegalStateException("A non-FragmentActivity is not supported (looking up an onClick handler of $view)")

            invocation = getTopFragment(activity)?.let { fragment ->
                resolveInvocation(fragment, methodName)
            }?: resolveInvocation(context, methodName)
        }
        invocation
    }

private fun getTopFragment(activity: FragmentActivity): Fragment? {
    val fragments = activity.supportFragmentManager.fragments
    return if (fragments.isEmpty()) null else fragments.last()
}

private fun resolveInvocation(target: Any, methodName: String): OnClickInvocation? =
    try {
        val method = target.javaClass.getMethod(methodName, View::class.java)
        OnClickInvocation(target, method)
    } catch (e: NoSuchMethodException) {
        null
    }

private fun <T: Any> searchContexts(view: View, matcher: (context: Context) -> T?): T? {
    var context = view.context
    while (context != null && context is ContextWrapper) {
        val result = matcher(context)
        if (result == null) {
            context = context.baseContext
        } else {
            return result
        }
    }
    return null
}

Hinweis: basiert lose auf der ursprünglichen Android-Implementierung (siehe https://Android.googlesource.com/platform/frameworks/base/+/a175a5b/core/java/Android/view/View.java#3025 )

Schritt 2: Deklarative Anwendung in Layout-Dateien

Dann, in datenbindungsfähigen XML's:

<layout>
  <data>
     <import type="com.example.CustomOnClick"/>
  </data>

  <Button
    android:onClick='@{(v) -> CustomOnClick.onClick(v, "myClickMethod")}'
  </Button>
</layout>

Vorbehalte

  • Geht von einem 'modernen' FragmentActivity basierte Implementierung
  • Kann nur die Nachschlagemethode "top-most" (d.h. zuletzt ) Fragment im Stapel (obwohl das kann falls erforderlich, repariert werden)

1voto

Isham Punkte 422

Wenn Sie in xml mit Android:Onclick="" registrieren, wird der Callback an die entsprechende Activity gegeben, zu deren Kontext Ihr Fragment gehört (getActivity() ). Wenn eine solche Methode nicht in der Activity gefunden wird, löst das System eine Ausnahme aus.

1voto

programmer Punkte 2993

Sie könnten EventBus für entkoppelte Ereignisse in Betracht ziehen. Sie können sehr einfach auf Ereignisse warten. Sie können auch sicherstellen, dass das Ereignis auf dem UI-Thread empfangen wird (statt Aufruf runOnUiThread.. für sich selbst für jedes Ereignis Abonnement)

https://github.com/greenrobot/EventBus

von Github:

Android-optimierter Ereignisbus, der die Kommunikation zwischen Aktivitäten, Fragmenten, Threads, Diensten, etc. Weniger Code, bessere Qualität

1voto

Dragas Punkte 842

Ich möchte mich Adjorn Linkz's Meinung anschließen Antwort .

Wenn Sie mehrere Handler benötigen, können Sie einfach Lambda-Referenzen verwenden

void onViewCreated(View view, Bundle savedInstanceState)
{
    view.setOnClickListener(this::handler);
}
void handler(View v)
{
    ...
}

Der Trick dabei ist, dass handler Die Signatur der Methode entspricht View.OnClickListener.onClick Unterschrift. Auf diese Weise benötigen Sie nicht die View.OnClickListener Schnittstelle.

Außerdem brauchen Sie keine Switch-Anweisungen.

Leider ist diese Methode nur auf Schnittstellen beschränkt, die eine einzige Methode oder ein Lambda erfordern.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X