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.

619voto

Adorjan Princz Punkte 11524

Ich bevorzuge die folgende Lösung für die Behandlung von onClick-Ereignissen. Dies funktioniert auch für Activity und Fragments.

public class StartFragment extends Fragment implements OnClickListener{

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View v = inflater.inflate(R.layout.fragment_start, container, false);

        Button b = (Button) v.findViewById(R.id.StartButton);
        b.setOnClickListener(this);
        return v;
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.StartButton:

            ...

            break;
        }
    }
}

179voto

Blundell Punkte 72729

Sie könnten dies einfach tun:

Tätigkeit:

Fragment someFragment;    

//...onCreate etc instantiating your fragments

public void myClickMethod(View v) {
    someFragment.myClickMethod(v);
}

Fragment:

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

Als Antwort auf @Ameen, der weniger Kopplung wünscht, damit Fragmente wiederverwendbar sind

Schnittstelle:

public interface XmlClickable {
    void myClickMethod(View v);
}

Tätigkeit:

XmlClickable someFragment;    

//...onCreate, etc. instantiating your fragments casting to your interface.

public void myClickMethod(View v) {
    someFragment.myClickMethod(v);
}

Fragment:

public class SomeFragment implements XmlClickable {

//...onCreateView, etc.

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

31voto

Brill Pappin Punkte 4575

Das Problem ist meiner Meinung nach, dass die Ansicht immer noch die Aktivität und nicht das Fragment ist. Das Fragment hat keine eigene, unabhängige Ansicht und ist mit der Ansicht der übergeordneten Aktivität verbunden. Deshalb landet das Ereignis in der Aktivität, nicht im Fragment. Es ist bedauerlich, aber ich denke, Sie brauchen etwas Code, um dies zu erreichen.

Was ich während der Konvertierungen getan habe, ist einfach einen Click-Listener hinzufügen, der den alten Event-Handler aufruft.

zum Beispiel:

final Button loginButton = (Button) view.findViewById(R.id.loginButton);
loginButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(final View v) {
        onLoginClicked(v);
    }
});

31voto

Aldo Canepa Punkte 1483

Ich habe dieses Problem vor kurzem gelöst, ohne eine Methode zum Kontext Activity hinzufügen oder OnClickListener implementieren zu müssen. Ich bin nicht sicher, ob es auch keine "gültige" Lösung ist, aber es funktioniert.

Basierend auf: https://developer.Android.com/tools/data-binding/guide.html#binding_events

Dies kann mit Datenbindungen geschehen: Fügen Sie einfach Ihre Fragment-Instanz als Variable hinzu, dann können Sie jede Methode mit onClick verknüpfen.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.example.testapp.fragments.CustomFragment">

    <data>
        <variable android:name="fragment" android:type="com.example.testapp.fragments.CustomFragment"/>
    </data>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_place_black_24dp"
            android:onClick="@{() -> fragment.buttonClicked()}"/>
    </LinearLayout>
</layout>

Und der Fragmentverknüpfungscode wäre...

public class CustomFragment extends Fragment {

    ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_person_profile, container, false);
        FragmentCustomBinding binding = DataBindingUtil.bind(view);
        binding.setFragment(this);
        return view;
    }

    ...

}

7voto

sergio91pt Punkte 1448

ButterMesser ist wahrscheinlich die beste Lösung für das Problem der Unordnung. Sie verwendet Anmerkungsprozessoren, um den so genannten "alten Methoden"-Boilerplate-Code zu erzeugen.

Die onClick-Methode kann jedoch weiterhin mit einem benutzerdefinierten Inflator verwendet werden.

Wie zu verwenden

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup cnt, Bundle state) {
    inflater = FragmentInflatorFactory.inflatorFor(inflater, this);
    return inflater.inflate(R.layout.fragment_main, cnt, false);
}

Umsetzung

public class FragmentInflatorFactory implements LayoutInflater.Factory {

    private static final int[] sWantedAttrs = { android.R.attr.onClick };

    private static final Method sOnCreateViewMethod;
    static {
        // We could duplicate its functionallity.. or just ignore its a protected method.
        try {
            Method method = LayoutInflater.class.getDeclaredMethod(
                    "onCreateView", String.class, AttributeSet.class);
            method.setAccessible(true);
            sOnCreateViewMethod = method;
        } catch (NoSuchMethodException e) {
            // Public API: Should not happen.
            throw new RuntimeException(e);
        }
    }

    private final LayoutInflater mInflator;
    private final Object mFragment;

    public FragmentInflatorFactory(LayoutInflater delegate, Object fragment) {
        if (delegate == null || fragment == null) {
            throw new NullPointerException();
        }
        mInflator = delegate;
        mFragment = fragment;
    }

    public static LayoutInflater inflatorFor(LayoutInflater original, Object fragment) {
        LayoutInflater inflator = original.cloneInContext(original.getContext());
        FragmentInflatorFactory factory = new FragmentInflatorFactory(inflator, fragment);
        inflator.setFactory(factory);
        return inflator;
    }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        if ("fragment".equals(name)) {
            // Let the Activity ("private factory") handle it
            return null;
        }

        View view = null;

        if (name.indexOf('.') == -1) {
            try {
                view = (View) sOnCreateViewMethod.invoke(mInflator, name, attrs);
            } catch (IllegalAccessException e) {
                throw new AssertionError(e);
            } catch (InvocationTargetException e) {
                if (e.getCause() instanceof ClassNotFoundException) {
                    return null;
                }
                throw new RuntimeException(e);
            }
        } else {
            try {
                view = mInflator.createView(name, null, attrs);
            } catch (ClassNotFoundException e) {
                return null;
            }
        }

        TypedArray a = context.obtainStyledAttributes(attrs, sWantedAttrs);
        String methodName = a.getString(0);
        a.recycle();

        if (methodName != null) {
            view.setOnClickListener(new FragmentClickListener(mFragment, methodName));
        }
        return view;
    }

    private static class FragmentClickListener implements OnClickListener {

        private final Object mFragment;
        private final String mMethodName;
        private Method mMethod;

        public FragmentClickListener(Object fragment, String methodName) {
            mFragment = fragment;
            mMethodName = methodName;
        }

        @Override
        public void onClick(View v) {
            if (mMethod == null) {
                Class<?> clazz = mFragment.getClass();
                try {
                    mMethod = clazz.getMethod(mMethodName, View.class);
                } catch (NoSuchMethodException e) {
                    throw new IllegalStateException(
                            "Cannot find public method " + mMethodName + "(View) on "
                                    + clazz + " for onClick");
                }
            }

            try {
                mMethod.invoke(mFragment, v);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new AssertionError(e);
            }
        }
    }
}

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