Come faccio ad aggiungere un frammento a un'attività con una vista del contenuto creata a livello di programmazione


240

Voglio aggiungere un frammento a un'attività che implementa il suo layout a livello di codice. Ho esaminato la documentazione di Fragment ma non ci sono molti esempi che descrivono ciò di cui ho bisogno. Ecco il tipo di codice che ho provato a scrivere:

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

Questo codice viene compilato ma si blocca all'avvio, probabilmente perché il mio FragmentTransaction.add()non è corretto. Qual è il modo corretto per farlo?

Risposte:


198

Si scopre che c'è più di un problema con quel codice. Un frammento non può essere dichiarato in questo modo, all'interno dello stesso file java dell'attività ma non come una classe interna pubblica. Il framework prevede che il costruttore del frammento (senza parametri) sia pubblico e visibile. Spostare il frammento in Activity come classe interna o creare un nuovo file java per il frammento risolve questo problema.

Il secondo problema è che quando si aggiunge un frammento in questo modo, è necessario passare un riferimento alla vista contenente il frammento e tale vista deve avere un ID personalizzato. L'uso dell'ID predefinito provoca l'arresto anomalo dell'app. Ecco il codice aggiornato:

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

115
Se si desidera utilizzare solo il frammento come visualizzazione del contenuto di livello superiore dell'attività, è possibile utilizzare ft.add(android.R.id.content, newFragment). È necessario solo creare un layout personalizzato e impostarne l'id se il contenitore del frammento non è la vista del contenuto dell'attività.
Tony Wong,

25
Invece di codificare l'ID, è possibile definirlo in XML e fare riferimento come normale (R.id.myid).
Jason Hanley

1
Non so come farlo, ma ricorda che un ID deve essere unico nell'ambito di cui hai bisogno per usarlo.
Jason Hanley

2
l'id deve solo essere unico nel suo livello all'interno dell'attuale gerarchia del layout contenitore. Quindi diciamo che è avvolto in un layout lineare, deve solo essere inammissibile tra le altre viste all'interno di quel layout lineare.
Shaun,

1
Puoi creare un ID in modo dinamico usando setId (View.NO_ID) e quindi getId () per vedere di cosa si trattava.
enl8enmentnow il

71

Ecco cosa mi è venuto in mente dopo aver letto il commento di 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

Se stai usando Kotlin assicurati di dare un'occhiata a ciò che forniscono le estensioni di Kotlin di Google o semplicemente scrivine uno tuo.


Non farlo! Controlla if (savedInstanceState == null)prima della creazione del frammento o dopo aver ruotato una schermata avrai due frammenti o riordini dei frammenti. Non usare affatto il addmetodo! Solo replace. O avrai un comportamento strano.
CoolMind,

Dove ottieni il valore per "backStackStateName"? (Quando si utilizza la funzione di sostituzione)
vikzilla

@vikzilla Puoi trovare risposte piuttosto buone qui e nei documenti . In breve: la backStackStateNamestringa è qualcosa che è definito da te.
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();
        }
    }

Per aggiungere un frammento in un'attività o in FramentActivity è necessario un contenitore. Quel contenitore dovrebbe essere un " Framelayout", che può essere incluso in xml oppure puoi usare il contenitore predefinito per quel " android.R.id.content" per rimuovere o sostituire un frammento in Attività.

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>

29

Dopo aver letto tutte le risposte mi è venuto in mente un modo elegante:

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

}

in pratica non è necessario aggiungere un frameLayout come contenitore del frammento, invece è possibile aggiungere direttamente il frammento nel root android Visualizza contenitore

IMPORTANTE: non utilizzare sostituisci frammento come la maggior parte dell'approccio mostrato qui, a meno che non ti dispiaccia perdere lo stato di istanza variabile del frammento durante il processo di ricreazione .


grazie per la risposta, questo aggiunge la scheda frammento a tutto lo schermo? ma come si aggiunge a un layout frame o si visualizza il cercapersone?
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;
   }

}

uso

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

}

6

Per livello API 17 o superiore, View.generateViewId()risolverà questo problema. Il metodo di utilità fornisce un ID univoco che non viene utilizzato in fase di compilazione.


3
Benvenuto in Stack Overflow! Sebbene ciò possa teoricamente rispondere alla domanda, sarebbe preferibile includere qui le parti essenziali della risposta e fornire il collegamento come riferimento.
SuperBiasedMan

3

Per associare un frammento a un'attività a livello di codice in Kotlin, puoi guardare il seguente codice:

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

}

Se hai familiarità con le estensioni in Kotlin, puoi persino migliorare questo codice seguendo questo articolo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.