web-dev-qa-db-fra.com

Comment gérer les clics de bouton en utilisant XML onClick dans Fragments

Avant Honeycomb (Android 3), chaque activité était enregistrée pour gérer les clics de bouton via la balise onClick dans un XML de mise en page:

Android:onClick="myClickMethod"

Dans cette méthode, vous pouvez utiliser view.getId() et une instruction switch pour effectuer la logique du bouton.

Avec l'introduction de Honeycomb, je divise ces activités en fragments pouvant être réutilisés dans de nombreuses activités différentes. La plupart des boutons ont un comportement indépendant de l'activité, et j'aimerais que le code réside dans le fichier Fragments sans en utilisant l'ancienne méthode (antérieure à la 1.6) pour enregistrer le OnClickListener de chaque bouton. .

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

Le problème est que lorsque ma mise en page est gonflée, c'est toujours l'activité d'hébergement qui reçoit les clics du bouton, pas les fragments individuels. Y at-il une bonne approche soit

  • Enregistrez le fragment pour recevoir les clics de bouton?
  • Transmettre les événements de clic de l'activité au fragment auquel ils appartiennent?
420
smith324

Vous pouvez simplement faire ceci:

Activité:

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

En réponse à @Ameen qui souhaitait moins de couplage pour que les fragments soient réutilisables

Interface:

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

Activité:

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

Je préfère utiliser la solution suivante pour gérer les événements onClick. Cela fonctionne aussi bien pour l'activité que pour les 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;
        }
    }
}
587
Adorjan Princz

Le problème, selon moi, est que la vue reste l’activité, pas le fragment. Les fragments ne possèdent pas de vue indépendante et sont attachés à la vue des activités parentes. C'est pourquoi l'événement se termine dans l'activité, pas dans le fragment. C'est regrettable, mais je pense que vous aurez besoin de code pour que cela fonctionne.

Ce que j'ai fait lors des conversions, c'est simplement d'ajouter un écouteur de clic qui appelle l'ancien gestionnaire d'événements.

par exemple:

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

J'ai récemment résolu ce problème sans avoir à ajouter de méthode au contexte Activity ni à implémenter OnClickListener. Je ne suis pas sûr que ce soit une solution "valide" non plus, mais cela fonctionne.

Basé sur: https://developer.Android.com/tools/data-binding/guide.html#binding_events

Cela peut être fait avec des liaisons de données: ajoutez simplement votre instance de fragment en tant que variable, puis vous pourrez lier n'importe quelle méthode avec onClick.

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

Et le fragment de code de liaison serait ...

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 est probablement la meilleure solution au problème d'encombrement. Il utilise des processeurs d'annotation pour générer le code standard appelé "ancienne méthode".

Mais la méthode onClick peut toujours être utilisée avec un gonfleur personnalisé.

Comment utiliser

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

La mise en oeuvre

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

Je préférerais utiliser le code dans le code plutôt que d'utiliser l'attribut onClick en XML lorsque vous utilisez des fragments.

Cela devient encore plus facile lorsque vous migrez vos activités vers des fragments. Vous pouvez simplement appeler le gestionnaire de clics (précédemment défini sur Android:onClick en XML) directement à partir de chaque bloc case.

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:
              ...
        }
    }
}

Pour ce qui est de la gestion des clics dans les fragments, cela me semble plus simple que Android:onClick.

6
Ronnie

C'est une autre façon:

1.Créez un fragment de base comme ceci:

public abstract class BaseFragment extends Fragment implements OnClickListener

2.Utiliser

public class FragmentA extends BaseFragment 

au lieu de

public class FragmentA extends Fragment

3.Dans votre activité:

public class MainActivity extends ActionBarActivity implements OnClickListener

et

BaseFragment fragment = new FragmentA;

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

J'espère que ça aide.

5
Euporie

Dans mon cas d'utilisation, j'ai environ 50 images visuelles impaires dont je devais me connecter à une seule méthode onClick. Ma solution consiste à parcourir les vues à l'intérieur du fragment et à définir le même écouteur onclick sur chacun:

    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

Comme je vois les réponses ils sont en quelque sorte vieux. Récemment Google introduit DataBinding qui est beaucoup plus facile à gérer onClick ou l'attribution dans votre xml.

Voici un bon exemple que vous pouvez voir comment gérer ceci:

<?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>

Il existe également un très bon tutoriel sur DataBinding que vous pouvez trouver Ici .

2
Amir

Vous pouvez définir un rappel en tant qu'attribut de votre présentation XML. L'article Attributs XML personnalisés pour votre personnalisation Android Widgets vous montrera comment procéder widget personnalisé. Le mérite en revient à Kevin Dion :)

Je cherche à savoir si je peux ajouter des attributs pouvant être stylés à la classe de fragments de base.

L'idée de base est d'avoir les mêmes fonctionnalités que View implémente lorsqu'il s'agit du rappel onClick.

2
Nelson Ramirez

Si vous vous inscrivez en xml sous Android: Onclick = "", un rappel sera donné à l'activité respectée dans le contexte duquel votre fragment appartient (getActivity ()). Si cette méthode ne figure pas dans l'activité, le système lève une exception.

1
Isham

Ajoutant à la réponse de Blundell,
Si vous avez plus de fragments, avec beaucoup de onClicks:

Activité:

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

} 

Dans votre fragment:

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

J'aimerais ajouter à Adjorn Linkz réponse .

Si vous avez besoin de plusieurs gestionnaires, vous pouvez simplement utiliser les références lambda

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

Le truc ici est que la signature de la méthode handler correspond à la signature View.OnClickListener.onClick. De cette façon, vous n'aurez plus besoin de l'interface View.OnClickListener.

En outre, vous n'aurez besoin d'aucune instruction switch.

Malheureusement, cette méthode n'est limitée qu'aux interfaces qui nécessitent une seule méthode, ou une lambda.

1
Dragas

Vous voudrez peut-être envisager d’utiliser EventBus pour des événements découplés. Vous pouvez écouter les événements très facilement. Vous pouvez également vous assurer que l'événement est bien reçu sur le thread d'interface utilisateur (au lieu d'appeler runOnUiThread .. pour vous-même pour chaque abonnement à un événement).

https://github.com/greenrobot/EventBus

de Github:

Bus d'événements optimisé pour Android qui simplifie la communication entre activités, fragments, threads, services, etc. Moins de code, meilleure qualité

1
programmer

Cela a fonctionné pour moi: (studio Android)

 @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

Meilleure solution IMHO:

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

puis dans onViewStateRestored de Fragment:

addClick(R.id.myButton);
0
user4806368

Votre activité reçoit le rappel, comme vous l'avez probablement utilisé:

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

Si vous voulez que votre fragment reçoive un rappel, procédez comme suit:

mViewPagerCloth.setOnClickListener(this);

et implémenter l'interface onClickListener sur Fragment

0
ColdFire