Comment ajouter un fragment à une activité avec une vue de contenu créée par programme

240

Je veux ajouter un fragment à une activité qui implémente sa mise en page par programme. J'ai parcouru la documentation Fragment mais il n'y a pas beaucoup d'exemples décrivant ce dont j'ai besoin. Voici le type de code que j'ai essayé d'écrire:

public class DebugExampleTwo extends Activity {

    private ExampleTwoFragment mFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FrameLayout frame = new FrameLayout(this);
        if (savedInstanceState == null) {
            mFragment = new ExampleTwoFragment();
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.add(frame.getId(), mFragment).commit();
        }

        setContentView(frame);
    }
}

...

public class ExampleTwoFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, 
                             ViewGroup container, 
                             Bundle savedInstanceState) {
        Button button = new Button(getActivity());
        button.setText("Hello There");
        return button;
    }
}

Ce code se compile mais se bloque au démarrage, probablement parce que my FragmentTransaction.add()est incorrect. Quelle est la bonne façon de procéder?

Tony Wong
la source

Réponses:

198

Il s'avère qu'il y a plus d'un problème avec ce code. Un fragment ne peut pas être déclaré de cette façon, à l'intérieur du même fichier java que l'activité mais pas en tant que classe interne publique. Le framework s'attend à ce que le constructeur du fragment (sans paramètres) soit public et visible. Le déplacement du fragment dans l'activité en tant que classe interne ou la création d'un nouveau fichier java pour le fragment corrige cela.

Le deuxième problème est que lorsque vous ajoutez un fragment de cette façon, vous devez transmettre une référence à la vue contenant le fragment et cette vue doit avoir un identifiant personnalisé. L'utilisation de l'ID par défaut plantera l'application. Voici le code mis à jour:

public class DebugExampleTwo extends Activity {

    private static final int CONTENT_VIEW_ID = 10101010;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        FrameLayout frame = new FrameLayout(this);
        frame.setId(CONTENT_VIEW_ID);
        setContentView(frame, new LayoutParams(
            LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

        if (savedInstanceState == null) {
            Fragment newFragment = new DebugExampleTwoFragment();
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.add(CONTENT_VIEW_ID, newFragment).commit();
        }
    }

    public static class DebugExampleTwoFragment extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            EditText v = new EditText(getActivity());
            v.setText("Hello Fragment!");
            return v;
        }
    }
}
Tony Wong
la source
115
Si vous souhaitez uniquement utiliser le fragment comme vue de contenu de niveau supérieur de l'activité, vous pouvez l'utiliser ft.add(android.R.id.content, newFragment). Il est uniquement nécessaire de créer une mise en page personnalisée et de définir son identifiant si le conteneur du fragment n'est pas la vue de contenu de l'activité.
Tony Wong
25
Au lieu de coder en dur l'identifiant, vous pouvez le définir en XML et le référencer normalement (R.id.myid).
Jason Hanley
1
Je ne sais pas comment faire, mais rappelez-vous qu'un identifiant doit uniquement être unique dans la portée dont vous avez besoin pour l'utiliser.
Jason Hanley
2
l'identifiant doit uniquement être unique dans son niveau dans la hiérarchie actuelle de la disposition contenante. Donc, disons qu'il est enveloppé dans une disposition linéaire, il ne doit être différent que des autres vues de cette disposition linéaire.
Shaun
1
Vous pouvez créer un ID dynamiquement à l'aide de setId (View.NO_ID), puis getId () pour voir de quoi il s'agit.
enl8enmentnow
71

Voici ce que j'ai trouvé après avoir lu le commentaire de Tony Wong :

public class DebugExampleTwo extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addFragment(android.R.id.content,
                    new DebugExampleTwoFragment(),
                    DebugExampleTwoFragment.FRAGMENT_TAG);
    }

}

...

public abstract class BaseActivity extends Activity {

    protected void addFragment(@IdRes int containerViewId,
                               @NonNull Fragment fragment,
                               @NonNull String fragmentTag) {
        getSupportFragmentManager()
                .beginTransaction()
                .add(containerViewId, fragment, fragmentTag)
                .disallowAddToBackStack()
                .commit();
    }

    protected void replaceFragment(@IdRes int containerViewId,
                                   @NonNull Fragment fragment,
                                   @NonNull String fragmentTag,
                                   @Nullable String backStackStateName) {
        getSupportFragmentManager()
                .beginTransaction()
                .replace(containerViewId, fragment, fragmentTag)
                .addToBackStack(backStackStateName)
                .commit();
    }

}

...

public class DebugExampleTwoFragment extends Fragment {

    public static final String FRAGMENT_TAG = 
        BuildConfig.APPLICATION_ID + ".DEBUG_EXAMPLE_TWO_FRAGMENT_TAG";

    // ...

}

Kotlin

Si vous utilisez Kotlin, assurez-vous de regarder ce que les extensions Kotlin de Google fournissent ou écrivez simplement la vôtre.

JJD
la source
Ne fais pas ça! Vérifiez if (savedInstanceState == null)avant la création du fragment, ou après avoir fait pivoter un écran, vous aurez deux fragments ou des fragments réorganisés. N'utilisez pas du tout la addméthode! Seulement replace. Ou vous aurez un comportement étrange.
CoolMind
Où obtenez-vous la valeur de "backStackStateName"? (Lors de l'utilisation de la fonction de remplacement)
vikzilla
@vikzilla Vous pouvez trouver de très bonnes réponses ici et dans la documentation . En bref: la backStackStateNamechaîne est quelque chose que vous définissez.
JJD
34
    public class Example1 extends FragmentActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
          DemoFragment fragmentDemo = (DemoFragment) 
          getSupportFragmentManager().findFragmentById(R.id.frame_container);
          //above part is to determine which fragment is in your frame_container
          setFragment(fragmentDemo);
                       (OR)
          setFragment(new TestFragment1());
        }

        // This could be moved into an abstract BaseActivity 
        // class for being re-used by several instances
        protected void setFragment(Fragment fragment) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = 
                fragmentManager.beginTransaction();
            fragmentTransaction.replace(android.R.id.content, fragment);
            fragmentTransaction.commit();
        }
    }

Pour ajouter un fragment dans une activité ou une activité Frament, il faut un conteneur. Ce conteneur doit être un " Framelayout", qui peut être inclus dans xml, sinon vous pouvez utiliser le conteneur par défaut comme " android.R.id.content" pour supprimer ou remplacer un fragment dans Activity.

main.xml

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 <!-- Framelayout to display Fragments -->
   <FrameLayout
        android:id="@+id/frame_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/imagenext"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_margin="16dp"
        android:src="@drawable/next" />
</RelativeLayout>
anand krish
la source
29

Après avoir lu toutes les réponses, j'ai trouvé une manière élégante:

public class MyActivity extends ActionBarActivity {

 Fragment fragment ;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    FragmentManager fm = getSupportFragmentManager();
    fragment = fm.findFragmentByTag("myFragmentTag");
    if (fragment == null) {
        FragmentTransaction ft = fm.beginTransaction();
        fragment =new MyFragment();
        ft.add(android.R.id.content,fragment,"myFragmentTag");
        ft.commit();
    }

}

Fondamentalement, vous n'avez pas besoin d'ajouter un frameLayout comme conteneur de votre fragment à la place, vous pouvez ajouter directement le fragment dans le conteneur racine Android View

IMPORTANT: n'utilisez pas replace fragment comme la plupart des approches présentées ici, sauf si cela ne vous dérange pas de perdre l'état d'instance variable de fragment pendant le processus de recréation .

Xenione
la source
merci pour la réponse, cela ajoute l'onglet de fragment à tout l'écran? mais comment ajouter une disposition de cadre ou un téléavertisseur?
flankechen
6
public abstract class SingleFragmentActivity extends Activity {

    public static final String FRAGMENT_TAG = "single";
    private Fragment fragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            fragment = onCreateFragment();
           getFragmentManager().beginTransaction()
                   .add(android.R.id.content, fragment, FRAGMENT_TAG)
                   .commit();
       } else {
           fragment = getFragmentManager().findFragmentByTag(FRAGMENT_TAG);
       }
   }

   public abstract Fragment onCreateFragment();

   public Fragment getFragment() {
       return fragment;
   }

}

utilisation

public class ViewCatalogItemActivity extends SingleFragmentActivity {
    @Override
    public Fragment onCreateFragment() {
        return new FragmentWorkShops();
    }

}
user2212515
la source
6

Pour le niveau d'API 17 ou supérieur, View.generateViewId()résoudra ce problème. La méthode utilitaire fournit un identifiant unique qui n'est pas utilisé lors de la génération.

Sfseyhan
la source
3
Bienvenue dans Stack Overflow! Bien que cela puisse théoriquement répondre à la question, il serait préférable d'inclure ici les parties essentielles de la réponse et de fournir le lien de référence.
SuperBiasMan
3

Pour attacher un fragment à une activité par programme dans Kotlin, vous pouvez consulter le code suivant:

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

            // create fragment instance
            val fragment : FragmentName = FragmentName.newInstance()

            // for passing data to fragment
            val bundle = Bundle()
            bundle.putString("data_to_be_passed", DATA)
            fragment.arguments = bundle

            // check is important to prevent activity from attaching the fragment if already its attached
            if (savedInstanceState == null) {
                supportFragmentManager
                    .beginTransaction()
                    .add(R.id.fragment_container, fragment, "fragment_name")
                    .commit()
            }
        }

    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.MainActivity">

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

FragmentName.kt

class FragmentName : Fragment() {

    companion object {
        fun newInstance() = FragmentName()
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        // receiving the data passed from activity here
        val data = arguments!!.getString("data_to_be_passed")
        return view
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
    }

}

Si vous connaissez les extensions de Kotlin, vous pouvez encore améliorer ce code en suivant cet article.

bhavya_karia
la source