Wie füge ich einer Aktivität mit einer programmgesteuert erstellten Inhaltsansicht ein Fragment hinzu?

240

Ich möchte einer Aktivität ein Fragment hinzufügen, das ihr Layout programmgesteuert implementiert. Ich habe die Fragment-Dokumentation durchgesehen, aber es gibt nicht viele Beispiele, die beschreiben, was ich brauche. Hier ist die Art von Code, die ich zu schreiben versucht habe:

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

Dieser Code wird kompiliert, stürzt jedoch beim Start ab, wahrscheinlich weil mein Code FragmentTransaction.add()falsch ist. Was ist der richtige Weg, um dies zu tun?

Tony Wong
quelle

Antworten:

198

Es stellt sich heraus, dass es mehr als ein Problem mit diesem Code gibt. Ein Fragment kann nicht auf diese Weise in derselben Java-Datei wie die Aktivität deklariert werden, jedoch nicht als öffentliche innere Klasse. Das Framework erwartet, dass der Konstruktor des Fragments (ohne Parameter) öffentlich und sichtbar ist. Durch Verschieben des Fragments in die Aktivität als innere Klasse oder Erstellen einer neuen Java-Datei für das Fragment wird dies behoben.

Das zweite Problem besteht darin, dass Sie beim Hinzufügen eines Fragments auf diese Weise einen Verweis auf die enthaltende Ansicht des Fragments übergeben müssen und diese Ansicht eine benutzerdefinierte ID haben muss. Wenn Sie die Standard-ID verwenden, stürzt die App ab. Hier ist der aktualisierte Code:

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
quelle
115
Wenn Sie das Fragment nur als Inhaltsansicht der Aktivität auf oberster Ebene der Aktivität verwenden möchten, können Sie es verwenden ft.add(android.R.id.content, newFragment). Es ist nur erforderlich, ein benutzerdefiniertes Layout zu erstellen und seine ID festzulegen, wenn der Container des Fragments nicht die Inhaltsansicht der Aktivität ist.
Tony Wong
25
Anstatt die ID fest zu codieren, können Sie sie in XML definieren und als normal referenzieren (R.id.myid).
Jason Hanley
1
Ich weiß nicht, wie das geht, aber denken Sie daran, dass eine ID nur in dem Bereich eindeutig sein muss, in dem Sie sie verwenden müssen.
Jason Hanley
2
Die ID muss nur innerhalb der aktuellen Hierarchie des enthaltenen Layouts in ihrer Ebene eindeutig sein. Angenommen, es ist in ein lineares Layout eingebunden, es muss nur unter den anderen Ansichten in diesem linearen Layout eindeutig sein.
Shaun
1
Sie können eine ID dynamisch mit setId (View.NO_ID) und dann mit getId () erstellen, um zu sehen, was es war.
enl8enmentnow
71

Folgendes habe ich mir ausgedacht, nachdem ich Tony Wongs Kommentar gelesen hatte :

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

Wenn Sie Kotlin verwenden, schauen Sie sich an, was die Kotlin-Erweiterungen von Google bieten, oder schreiben Sie einfach Ihre eigenen.

JJD
quelle
Tu das nicht! Überprüfen Sie if (savedInstanceState == null)vor der Fragmenterstellung oder nach dem Drehen eines Bildschirms, ob zwei Fragmente oder Fragmente neu angeordnet werden. Verwenden Sie überhaupt keine addMethode! Nur replace. Oder du wirst komisches Verhalten haben.
CoolMind
Woher bekommen Sie den Wert für "backStackStateName"? (Bei Verwendung der Ersetzungsfunktion)
Vikzilla
@vikzilla Hier und in den Dokumenten finden Sie ziemlich gute Antworten . Kurz gesagt: Die backStackStateNameZeichenfolge wird von Ihnen definiert.
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();
        }
    }

Um ein Fragment zu einer Aktivität oder FramentActivity hinzuzufügen, ist ein Container erforderlich. Dieser Container sollte ein " Framelayout" sein, das in XML enthalten sein kann, oder Sie können den Standardcontainer für " android.R.id.content" verwenden, um ein Fragment in Activity zu entfernen oder zu ersetzen.

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

Nachdem ich alle Antworten gelesen hatte, kam ich auf elegante Weise:

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

}

Grundsätzlich müssen Sie kein FrameLayout als Container Ihres Fragments hinzufügen, sondern können das Fragment direkt in den Android Root View-Container einfügen

WICHTIG: Verwenden Sie das Ersetzen von Fragmenten nicht wie die meisten der hier gezeigten Ansätze, es sei denn, es macht Ihnen nichts aus, den Instanzstatus der Fragmentvariablen während des Wiederherstellungsprozesses zu verlieren .

Xenione
quelle
Vielen Dank für die Antwort. Dadurch wird die Registerkarte "Fragment" zum gesamten Bildschirm hinzugefügt. Aber wie fügt man einem Frame-Layout oder View-Pager hinzu?
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;
   }

}

verwenden

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

}
user2212515
quelle
6

Löst dieses Problem für API Level 17 oder höher View.generateViewId(). Die Dienstprogrammmethode stellt eine eindeutige ID bereit, die in der Erstellungszeit nicht verwendet wird.

Sfseyhan
quelle
3
Willkommen bei Stack Overflow! Während dies theoretisch die Frage beantworten kann, wäre es vorzuziehen , die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen.
SuperBiasedMan
3

Zum programmgesteuerten Anhängen eines Fragments an eine Aktivität in Kotlin können Sie den folgenden Code anzeigen:

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

}

Wenn Sie mit Erweiterungen in Kotlin vertraut sind , können Sie diesen Code noch verbessern, indem Sie diesem Artikel folgen .

bhavya_karia
quelle