Jak dodać fragment do działania za pomocą programowo utworzonego widoku treści

240

Chcę dodać fragment do działania, które programowo implementuje jego układ. Przejrzałem dokumentację Fragmentu, ale nie ma wielu przykładów opisujących to, czego potrzebuję. Oto typ kodu, który próbowałem napisać:

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

Ten kod kompiluje się, ale ulega awarii na początku, prawdopodobnie dlatego, że mój FragmentTransaction.add()jest niepoprawny. Jaki jest właściwy sposób to zrobić?

Tony Wong
źródło

Odpowiedzi:

198

Okazuje się, że z tym kodem jest więcej niż jeden problem. Fragmentu nie można zadeklarować w ten sposób, w tym samym pliku java co działanie, ale nie jako publiczna klasa wewnętrzna. Struktura oczekuje, że konstruktor fragmentu (bez parametrów) będzie publiczny i widoczny. Przeniesienie fragmentu do działania jako wewnętrznej klasy lub utworzenie nowego pliku Java dla tego fragmentu rozwiązuje ten problem.

Drugi problem polega na tym, że gdy dodajesz fragment w ten sposób, musisz przekazać odwołanie do widoku zawierającego fragment, a widok ten musi mieć niestandardowy identyfikator. Użycie domyślnego identyfikatora spowoduje awarię aplikacji. Oto zaktualizowany kod:

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
źródło
115
Jeśli chcesz użyć fragmentu jako widoku zawartości najwyższego poziomu działania, możesz go użyć ft.add(android.R.id.content, newFragment). Konieczne jest utworzenie niestandardowego układu i ustawienie jego identyfikatora, jeśli kontener fragmentu nie jest widokiem zawartości działania.
Tony Wong
25
Zamiast na stałe kodować identyfikator, możesz zdefiniować go w formacie XML i odwołać się do niego jak do normalnego (idid.myid).
Jason Hanley,
1
Nie wiem, jak to zrobić, ale pamiętaj, że identyfikator musi być unikalny tylko w zakresie, w którym musisz go użyć.
Jason Hanley,
2
identyfikator musi być unikalny na swoim poziomie w ramach bieżącej dziedziczności zawierającego układ. Powiedzmy, że jest owinięty w układ liniowy, musi być jedyny w swoim rodzaju wśród innych widoków tego układu liniowego.
Shaun
1
Możesz dynamicznie utworzyć identyfikator za pomocą setId (View.NO_ID), a następnie getId (), aby zobaczyć, co to było.
rozszerzenie 8
71

Oto, co wymyśliłem po przeczytaniu komentarza Tony'ego Wonga :

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

Jeśli korzystasz z Kotlin, sprawdź, jakie rozszerzenia Google Kotlin oferują lub po prostu napisz własne.

JJD
źródło
Nie rób tego! Sprawdź if (savedInstanceState == null)przed utworzeniem fragmentu lub po obróceniu ekranu będziesz mieć dwa fragmenty lub fragmenty zmieniające kolejność. W ogóle nie używaj addmetody! Tylko replace. Albo będziesz miał dziwne zachowanie.
CoolMind
Skąd bierze się wartość „backStackStateName”? (Podczas korzystania z funkcji zamiany)
Vikzilla,
@vikzilla Odpowiednie odpowiedzi można znaleźć tutaj i w dokumentacji . W skrócie: backStackStateNameciąg znaków jest czymś zdefiniowanym przez Ciebie.
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();
        }
    }

Aby dodać fragment do działania lub działania FramentActivity, wymaga kontenera. Ten kontener powinien być „ Framelayout”, który może być zawarty w xml, lub możesz użyć domyślnego kontenera takiego jak „ android.R.id.content”, aby usunąć lub zastąpić fragment w działaniu.

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
źródło
29

Po przeczytaniu wszystkich odpowiedzi wymyśliłem elegancki sposób:

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

}

Zasadniczo nie musisz dodawać frameLayout jako kontenera swojego fragmentu, zamiast tego możesz dodać prosto fragment do katalogu głównego Androida Wyświetl kontener

WAŻNE: nie używaj zastępowania fragmentu, jak w większości pokazanych tutaj podejść, chyba że nie masz nic przeciwko utracie stanu instancji zmiennej fragmentu podczas procesu rekonstrukcji .

Xenione
źródło
dzięki za odpowiedź, to dodaje zakładkę fragmentu do całego ekranu? ale jak dodać do jednego układu ramki lub wyświetlić pager?
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;
   }

}

posługiwać się

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

}
użytkownik2212515
źródło
6

W przypadku interfejsu API na poziomie 17 lub wyższym View.generateViewId()rozwiąże ten problem. Metoda narzędzia zapewnia unikalny identyfikator, który nie jest używany w czasie kompilacji.

Sfseyhan
źródło
3
Witamy w Stack Overflow! Chociaż teoretycznie może to odpowiedzieć na pytanie, lepiej byłoby zawrzeć tutaj istotne części odpowiedzi i podać odnośnik.
SuperBiasedMan,
3

Aby dołączyć fragment do działania programowo w Kotlinie, możesz spojrzeć na następujący kod:

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

}

Jeśli znasz rozszerzenia w Kotlin, możesz ulepszyć ten kod, postępując zgodnie z tym artykułem.

bhavya_karia
źródło