it-swarm.com.de

Umgang mit Schaltflächenklicks mithilfe von XML onClick in Fragmenten

Vor Honeycomb (Android 3) wurde jede Aktivität registriert, um Tastenklicks über das Tag onClick in der XML eines Layouts zu verarbeiten:

Android:onClick="myClickMethod"

Innerhalb dieser Methode können Sie view.getId() und eine switch-Anweisung verwenden, um die Schaltflächenlogik auszuführen.

Mit der Einführung von Honeycomb zerlege ich diese Aktivitäten in Fragmente, die in vielen verschiedenen Aktivitäten wiederverwendet werden können. Das meiste Verhalten der Schaltflächen ist unabhängig von der Aktivität, und ich möchte, dass sich der Code in der Fragmentdatei befindet ohne unter Verwendung der alten (vor 1.6) Methode zum Registrieren von OnClickListener für jede Schaltfläche .

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 sind, es immer noch die Hosting-Aktivität ist, die die Schaltflächenklicks empfängt, nicht die einzelnen Fragmente. Gibt es einen guten Ansatz für beide

  • Registrieren Sie das Fragment, um die Tastenklicks zu erhalten?
  • Übergeben Sie die Klickereignisse aus der Aktivität an das Fragment, zu dem sie gehören?
420
smith324

Sie könnten dies einfach tun:

Aktivität:

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 wollte, sind Fragmente wiederverwendbar

Schnittstelle:

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

Aktivität:

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
    }
}    
169
Blundell

Ich bevorzuge die Verwendung der folgenden Lösung für die Behandlung von onClick-Ereignissen. Dies funktioniert auch für Aktivitäten und Fragmente.

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;
        }
    }
}
587
Adorjan Princz

Das Problem ist meiner Meinung nach, dass die Ansicht immer noch die Aktivität ist, nicht das Fragment. Die Fragmente haben keine eigene unabhängige Ansicht und sind an die übergeordnete Aktivitätsansicht angehängt. Deshalb endet das Ereignis in der Aktivität, nicht im Fragment. Es ist bedauerlich, aber ich denke, Sie werden Code benötigen, damit dies funktioniert.

Während der Conversions habe ich lediglich einen Klicklistener hinzugefügt, der die alte Ereignisbehandlungsroutine 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);
    }
});
28
Brill Pappin

Ich habe dieses Problem kürzlich behoben, ohne dem Kontext Activity eine Methode hinzufügen oder OnClickListener implementieren zu müssen. Ich bin nicht sicher, ob es eine "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 Fragmentinstanz als Variable hinzu, und Sie können eine beliebige 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 das Fragment Verknü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;
    }

    ...

}
21
Aldo Canepa

ButterKnife ist wahrscheinlich die beste Lösung für das Durcheinanderproblem. Es verwendet Annotation-Prozessoren, um den sogenannten "old method" -Boilerplate-Code zu generieren.

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

Wie benutzt man

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

Implementierung

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

Ich würde mich lieber für die Behandlung von Klicks im Code entscheiden, als das Attribut onClick in XML zu verwenden, wenn ich mit Fragmenten arbeite.

Dies wird noch einfacher, wenn Sie Ihre Aktivitäten in Fragmente migrieren. Sie können den Click-Handler (zuvor in XML auf Android:onClick gesetzt) ​​direkt aus jedem case -Block aufrufen.

findViewById(R.id.button_login).setOnClickListener(clickListener);
...

OnClickListener clickListener = new OnClickListener() {
    @Override
    public void onClick(final View v) {
        switch(v.getId()) {
           case R.id.button_login:
              // Which is supposed to be called automatically in your
              // activity, which has now changed to a fragment.
              onLoginClick(v);
              break;

           case R.id.button_logout:
              ...
        }
    }
}

Wenn es darum geht, Klicks in Fragmenten zu verarbeiten, erscheint mir dies einfacher als Android:onClick.

6
Ronnie

Dies ist ein anderer Weg:

1.Erstelle ein BaseFragment wie folgt:

public abstract class BaseFragment extends Fragment implements OnClickListener

2.Verwenden

public class FragmentA extends BaseFragment 

anstatt von

public class FragmentA extends Fragment

3.In Ihrer Tätigkeit:

public class MainActivity extends ActionBarActivity implements OnClickListener

und

BaseFragment fragment = new FragmentA;

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

Ich hoffe es hilft.

5
Euporie

In meinem Anwendungsfall habe ich 50 ungerade ImageViews, die ich benötigt habe, um mich in eine einzelne onClick-Methode einzuklinken. Meine Lösung besteht darin, die Ansichten innerhalb des Fragments zu durchlaufen und jeweils denselben On-Click-Listener festzulegen:

    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);
        }
    }
3
Chris Knight

Soweit ich Antworten sehe, sind sie irgendwie alt. Vor kurzem hat Google DataBinding eingeführt, das viel einfacher zu handhaben ist onClick oder in Ihrer XML zuzuweisen.

Hier ist ein gutes Beispiel, wie Sie damit umgehen können:

<?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 nettes Tutorial über DataBinding, das Sie finden können hier .

2
Amir

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

Ich untersuche, ob ich der Basis-Fragment-Klasse stilisierbare Attribute hinzufügen kann.

Die Grundidee ist, die gleiche Funktionalität zu haben, die View beim Umgang mit dem onClick-Rückruf implementiert.

2
Nelson Ramirez

Wenn Sie sich mit Android in xml registrieren: Onclick = "", wird die respektierte Aktivität zurückgerufen, unter deren Kontext Ihr Fragment gehört (getActivity ()). Wird eine solche Methode in der Aktivität nicht gefunden, gibt das System eine Ausnahme aus.

1
Isham

Hinzufügen zu Blundells Antwort,
Wenn Sie mehr Fragmente haben, mit vielen onClicks:

Aktivität:

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
     }
  } 
1
amalBit

Ich möchte zu Adjorn Linkzs Antwort hinzufügen.

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 die Signatur der Methode handler mit der Signatur View.OnClickListener.onClick übereinstimmt. Auf diese Weise benötigen Sie die Schnittstelle View.OnClickListener nicht.

Außerdem benötigen Sie keine switch-Anweisungen.

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

1
Dragas

Möglicherweise möchten Sie EventBus für entkoppelte Ereignisse verwenden. Sie können Ereignisse ganz einfach abhören. Sie können auch sicherstellen, dass das Ereignis im UI-Thread empfangen wird (anstatt runOnUiThread .. für sich selbst für jedes Ereignisabonnement aufzurufen).

https://github.com/greenrobot/EventBus

von Github:

Android-optimierter Event-Bus, der die Kommunikation zwischen Aktivitäten, Fragmenten, Threads, Diensten usw. vereinfacht. Weniger Code, bessere Qualität

1
programmer

Das hat bei mir funktioniert: (Android Studio)

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

        View rootView = inflater.inflate(R.layout.update_credential, container, false);
        Button bt_login = (Button) rootView.findViewById(R.id.btnSend);

        bt_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                System.out.println("Hi its me");


            }// end onClick
        });

        return rootView;

    }// end onCreateView
0
Vinod Joshi

Beste Lösung IMHO:

in fragment:

protected void addClick(int id) {
    try {
        getView().findViewById(id).setOnClickListener(this);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public void onClick(View v) {
    if (v.getId()==R.id.myButton) {
        onMyButtonClick(v);
    }
}

dann in Fragments onViewStateRestored:

addClick(R.id.myButton);
0
user4806368

Ihre Aktivität erhält den Rückruf, wie er verwendet wurde:

mViewPagerCloth.setOnClickListener((YourActivityName)getActivity());

Wenn Ihr Fragment einen Rückruf erhalten soll, gehen Sie folgendermaßen vor:

mViewPagerCloth.setOnClickListener(this);

und implementiere onClickListener Interface auf Fragment

0
ColdFire