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.

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

6voto

Euporie Punkte 1798

Dies ist eine weitere Möglichkeit

1. ein BaseFragment wie folgt erstellen:

public abstract class BaseFragment extends Fragment implements OnClickListener

2.Verwendung

public class FragmentA extends BaseFragment 

anstelle von

public class FragmentA extends Fragment

3. in Ihrer Tätigkeit:

public class MainActivity extends ActionBarActivity implements OnClickListener

y

BaseFragment fragment = new FragmentA;

public void onClick(View v){
    fragment.onClick(v);
}

Ich hoffe, es hilft.

5voto

Chris Knight Punkte 23658

In meinem Anwendungsfall habe ich 50 ungerade ImageViews ich brauchte, um in einem einzigen onClick-Methode Haken. Meine Lösung ist, Schleife über die Ansichten innerhalb des Fragments und setzen Sie die gleichen onclick Listener auf jeder:

    final View.OnClickListener imageOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            chosenImage = ((ImageButton)v).getDrawable();
        }
    };

    ViewGroup root = (ViewGroup) getView().findViewById(R.id.imagesParentView);
    int childViewCount = root.getChildCount();
    for (int i=0; i < childViewCount; i++){
        View image = root.getChildAt(i);
        if (image instanceof ImageButton) {
            ((ImageButton)image).setOnClickListener(imageOnClickListener);
        }
    }

3voto

Nelson Ramirez Punkte 7684

Sie können einen Rückruf als ein Attribut Ihres XML-Layouts definieren. Der Artikel Benutzerdefinierte XML-Attribute für Ihre benutzerdefinierten Android-Widgets wird Ihnen zeigen, wie Sie dies für ein benutzerdefiniertes Widget tun können. Der Dank geht an Kevin Dion :)

Ich untersuche, ob ich der Basisklasse Fragment stilisierbare Attribute hinzufügen kann.

Der Grundgedanke ist, die gleiche Funktionalität zu haben, die View beim Umgang mit dem onClick-Callback implementiert.

3voto

Amir Punkte 15057

Wie ich die Antworten sehe, sind sie irgendwie alt. Kürzlich Google vorstellen. DataBinding was viel einfacher zu handhaben ist onClick oder die Zuweisung in Ihrer xml.

Hier ist ein gutes Beispiel, an dem Sie sehen können, wie man damit umgeht:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
           android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
   </LinearLayout>
</layout>

Es gibt auch ein sehr schönes Tutorial über DataBinding Sie können es finden Hier .

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