Najlepsze rozwiązanie dla zagnieżdżonych fragmentów w systemie Android 4.0, 4.1 (<4.2) bez korzystania z biblioteki obsługi


115

Piszę aplikację na tablety 4.0 i 4.1, dla których nie chcę korzystać z bibliotek wsparcia (jeśli nie są potrzebne), ale tylko w związku z tym 4.x api.

Więc moja platforma docelowa jest bardzo dobrze zdefiniowana jako:> = 4.0 i <= 4.1

Aplikacja ma układ wielopanelowy (dwa fragmenty, jeden mały po lewej, jeden fragment treści po prawej) oraz pasek akcji z zakładkami.

Podobne do tego:

wprowadź opis obrazu tutaj

Kliknięcie zakładki na pasku akcji zmienia fragment „zewnętrzny”, a fragment wewnętrzny jest fragmentem z dwoma zagnieżdżonymi fragmentami (1. mały fragment lewej listy, 2. szeroki fragment treści).

Zastanawiam się teraz, jaka jest najlepsza praktyka zastępowania fragmentów, a zwłaszcza fragmentów zagnieżdżonych. ViewPager jest częścią biblioteki obsługi, nie ma natywnej alternatywy 4.x dla tej klasy. Wydają się być „zdezaktualizowani” w moim rozumieniu. - http://developer.android.com/reference/android/support/v4/view/ViewPager.html

Następnie przeczytałem informacje o wydaniu dla Androida 4.2, dotyczące tego ChildFragmentManager, który byłby dobry, ale celuję w 4.0 i 4.1, więc tego też nie można użyć.

ChildFragmentManager jest dostępny tylko w 4.2

Niestety, nie ma prawie żadnych dobrych przykładów pokazujących najlepsze praktyki dotyczące wykorzystania fragmentów bez biblioteki obsługi, nawet w całych przewodnikach programistów Androida; a zwłaszcza nic odnośnie zagnieżdżonych fragmentów.

Zastanawiam się więc: czy po prostu nie można pisać aplikacji 4.1 z zagnieżdżonymi fragmentami bez korzystania z biblioteki wsparcia i wszystkiego, co się z nią wiąże? (trzeba użyć FragmentActivity zamiast Fragment, itp.?) Albo jaka byłaby najlepsza praktyka?


Problem, który obecnie mam w trakcie opracowywania, to dokładnie to stwierdzenie:

Biblioteka obsługi systemu Android obsługuje teraz również zagnieżdżone fragmenty, dzięki czemu można wdrażać projekty zagnieżdżonych fragmentów w systemie Android 1.6 i nowszych.

Uwaga: nie możesz nadać układu fragmentowi, jeśli ten układ zawiera plik <fragment>. Zagnieżdżone fragmenty są obsługiwane tylko wtedy, gdy są dodawane do fragmentu dynamicznie.

Ponieważ umieściłem zdefiniowane zagnieżdżone fragmenty w XML, co najwyraźniej powoduje błąd typu:

Caused by: java.lang.IllegalArgumentException: Binary XML file line #15: Duplicate id 0x7f090009, tag frgCustomerList, or parent id 0x7f090008 with another fragment for de.xyz.is.android.fragment.CustomerListFragment_

W tej chwili konkluzję dla siebie: nawet na 4.1, kiedy nie chcę nawet kierować na platformę 2.x, zagnieżdżone fragmenty, jak pokazano na zrzucie ekranu, nie są możliwe bez biblioteki obsługi.

(W rzeczywistości może to być bardziej wpis wiki niż pytanie, ale może ktoś inny zarządził tym wcześniej).

Aktualizacja:

Pomocna odpowiedź brzmi: Fragment Inside Fragment


22
Masz trzy opcje: 1. Celuj tylko w 4.2 z natywnymi zagnieżdżonymi fragmentami. 2. Cel 4.x z zagnieżdżonymi fragmentami z biblioteki obsługi. 3. Nie używaj zagnieżdżonych fragmentów w innych scenariuszach docelowych platformy. To powinno odpowiedzieć na twoje pytanie. Nie możesz też używać zagnieżdżonych fragmentów osadzonych w układzie xml, wszystkie muszą być dodane w kodzie. nie ma prawie żadnych dobrych przykładów, które pokazują najlepsze praktyki dotyczące wykorzystania fragmentów bez biblioteki pomocniczej - struktura fragmentów wsparcia replikuje wersję natywną, więc każdy przykład powinien działać w obie strony.
Luksprog

@Luksprog Dzięki za komentarze. Wolę twoje rozwiązanie 2, a framents działają dobrze w bibliotece wsparcia, ale zakładki w ActionBar nie - afaik, musiałbym używać ActionBarSherlock, ale zakładki nie byłyby wtedy zintegrowane w ActionBar, ale tylko poniżej (co nie jest ' t konieczne dla 4.x). ActionBar.TabListener obsługuje tylko fragmenty z android.app.Fragment, a nie z biblioteki wsparcia.
Mathias Conradt,

2
Nie znam aplikacji Kontakty na karcie Galaxy, ale pamiętaj, że zawsze możesz zmierzyć się z niestandardową implementacją ActionBar(zbudowaną przez firmę Samsung). Przyjrzyj się bliżej ActionBarSherlock, ma zakładki w ActionBar, jeśli jest miejsce.
Luksprog

4
@Luksprog Wydaje mi się, że dostarczyłeś już jedynej odpowiedzi, jaką można udzielić, czy byłbyś tak uprzejmy, aby podać ją jako właściwą odpowiedź.
Warpzit

1
@Pork Moim głównym powodem pytania jest: czy są jakieś obejścia dla zagnieżdżonych fragmentów bez konieczności korzystania z biblioteki obsługi i wszystkich innych elementów widoku. Oznacza to, że jeśli przejdę do biblioteki wsparcia, użyłbym FragmentActivity zamiast Fragment. Ale chcę użyć fragmentu, chcę tylko zastąpić zagnieżdżone fragmenty , ale nie wszystkie komponenty v4. To znaczy za pośrednictwem innych bibliotek open source itp. Na przykład powyższy zrzut ekranu działa na 4.0 i zastanawiam się, czy używają ABS, SupportLib, czy czegokolwiek innego.
Mathias Conradt

Odpowiedzi:


60

Ograniczenia

Tak więc zagnieżdżanie fragmentów w innym fragmencie nie jest możliwe w XML, niezależnie od używanej wersji FragmentManager.

Musisz więc dodawać fragmenty za pomocą kodu, może się to wydawać problemem, ale na dłuższą metę sprawia, że ​​układy są superelastyczne.

Więc zagnieżdżanie bez użycia getChildFragmentManger? Istotą childFragmentManagerjest to, że odracza ładowanie do czasu zakończenia poprzedniej transakcji fragmentowej. I oczywiście było to naturalnie obsługiwane tylko w 4.2 lub w bibliotece wsparcia.

Zagnieżdżanie bez ChildManagera - rozwiązanie

Rozwiązanie, jasne! Robię to już od dłuższego czasu (od czasu ViewPagerogłoszenia).

Zobacz poniżej; Jest to element Fragmentopóźniający ładowanie, więc Fragmentmożna go w nim załadować.

Jest dość prosta, Handlerjest naprawdę bardzo przydatną klasą, w rzeczywistości program obsługi czeka na wykonanie spacji w głównym wątku po zakończeniu zatwierdzania bieżącej transakcji fragmentu (ponieważ fragmenty zakłócają interfejs użytkownika, który uruchamiają w głównym wątku).

// Remember this is an example, you will need to modify to work with your code
private final Handler handler = new Handler();
private Runnable runPager;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    return inflater.inflate(R.layout.frag_layout, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState)
{
    super.onActivityCreated(savedInstanceState);
    runPager = new Runnable() {

        @Override
        public void run()
        {
          getFragmentManager().beginTransaction().addFragment(R.id.frag_container, MyFragment.newInstance()).commit();
        }
    };
    handler.post(runPager);
}

/**
 * @see android.support.v4.app.Fragment#onPause()
 */
@Override
public void onPause()
{
    super.onPause();
    handler.removeCallbacks(runPager);
}

Nie uważałbym tego za „najlepszą praktykę”, ale mam aplikacje na żywo korzystające z tego hacka i nie mam z tym jeszcze żadnych problemów.

Używam również tej metody do osadzania pagerów widoku - https://gist.github.com/chrisjenx/3405429


Jak radzisz sobie z zagnieżdżonymi fragmentami układu?
pablisco,

Jedynym sposobem, w jaki mogłem zobaczyć, jak to działa, jest użycie CustomLayoutInflater, gdy natkniesz się na fragmentelement, możesz zastąpić super implementację i spróbować samodzielnie ją przeanalizować / nadmuchać. Ale to będzie DUŻO wysiłku, cóż, poza zakresem pytania StackOverflow.
Chris.Jenkins

Cześć, czy ktoś może mi pomóc w tej kwestii? Naprawdę utknąłem ... stackoverflow.com/questions/32240138/…
Nicks

2

Najlepszym sposobem, aby to zrobić w wersji przed API 17, jest nie robić tego wcale. Próba zaimplementowania tego zachowania spowoduje problemy. Nie oznacza to jednak, że nie można go przekonująco podrobić przy użyciu obecnego API 14. To, co zrobiłem, było następujące:

1 - spójrz na komunikację między fragmentami http://developer.android.com/training/basics/fragments/communicating.html

2 - Przenieś swój układ XML FrameLayout z istniejącego fragmentu do układu działania i ukryj go, podając wysokość 0:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent">
<FrameLayout android:id="@+id/content"
          android:layout_width="300dp"
          android:layout_height="match_parent" />


<FrameLayout android:id="@+id/lstResults"
             android:layout_width="300dp"
             android:layout_height="0dp"
             android:layout_below="@+id/content"
             tools:layout="@layout/treeview_list_content"/>


<FrameLayout android:id="@+id/anomalies_fragment"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
        android:layout_toRightOf="@+id/content" />

3 - Zaimplementuj interfejs w fragmencie nadrzędnym

    OnListener mCallback;

// Container Activity must implement this interface
public interface OnListener 
{
    public void onDoSomethingToInitChildFrame(/*parameters*/);
    public void showResults();
    public void hideResults();
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);

    // This makes sure that the container activity has implemented
    // the callback interface. If not, it throws an exception
    try {
        mCallback = (OnFilterAppliedListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnListener");
    }
}

@Override
public void onActivityCreated(Bundle savedInstanceState) 
{
    super.onActivityCreated(savedInstanceState);

    mCallback.showResults();
}

@Override
public void onPause()
{
    super.onPause();

    mCallback.hideResults();
}

public void onClickButton(View view)
{
    // do click action here

    mCallback.onDoSomethingToInitChildFrame(/*parameters*/);
}

4 - Zaimplementuj interfejs w działaniu nadrzędnym

public class YourActivity extends Activity implementuje yourParentFragment.OnListener {

public void onDoSomethingToInitChildFrame(/*parameters*/)
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment == null)
    {
        childFragment = new yourChildFragment(/*parameters*/);
        ft.add(R.id.lstResults, childFragment, "Results");
    }
    else
    {
        ft.detach(childFragment);

        ((yourChildFragment)childFragment).ResetContent(/*parameters*/);

        ft.attach(childFragment);
    }
    ft.commit();

    showResultsPane();
}

public void showResults()
{
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.attach(childFragment);
    ft.commit();

    showResultsPane();
}

public void showResultsPane()
{
    //resize the elements to show the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
    findViewById(R.id.lstResults).getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
}

public void hideResults()
{
    //resize the elements to hide the results pane
    findViewById(R.id.content).getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
    findViewById(R.id.lstResults).getLayoutParams().height = 0;

    FragmentTransaction ft = getFragmentManager().beginTransaction();
    Fragment childFragment = getFragmentManager().findFragmentByTag("Results");
    if(childFragment != null)
        ft.detach(childFragment);
    ft.commit();
}

}

5 - Ciesz się, ta metoda zapewnia taką samą płynną funkcjonalność jak w przypadku funkcji getChildFragmentManager () w środowisku poprzedzającym API 17. Jak być może zauważyłeś, fragment podrzędny nie jest już tak naprawdę dzieckiem fragmentu nadrzędnego, ale teraz jest dzieckiem działania, naprawdę nie można tego uniknąć.


1

Musiałem sobie poradzić z tym dokładnie problemem ze względu na połączenie NavigationDrawer, TabHost i ViewPager, które powodowało komplikacje przy korzystaniu z biblioteki wsparcia z powodu TabHost. A potem musiałem również obsługiwać min API JellyBean 4.1, więc używanie zagnieżdżonych fragmentów z getChildFragmentManager nie było opcją.

Więc mój problem można wydestylować do ...

TabHost (dla najwyższego poziomu)
+ ViewPager (tylko dla jednego z fragmentów z zakładkami najwyższego poziomu)
= potrzeba zagnieżdżonych fragmentów (których JellyBean 4.1 nie obsługuje)

Moim rozwiązaniem było stworzenie iluzji zagnieżdżonych fragmentów bez faktycznego zagnieżdżania fragmentów. Zrobiłem to, wykorzystując główne działanie TabHost i ViewPager do zarządzania dwoma podobnymi widokami, których widocznością zarządza się przez przełączanie layout_weight między 0 a 1.

//Hide the fragment used by TabHost by setting height and weight to 0
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 0);
mTabHostedView.setLayoutParams(lp);
//Show the fragment used by ViewPager by setting height to 0 but weight to 1
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1);
mPagedView.setLayoutParams(lp);

Dzięki temu mój fałszywy „Zagnieżdżony fragment” mógł działać jako niezależny widok, o ile ręcznie zarządzałem odpowiednimi wagami układu.

Oto moja activity_main.xml:

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ringofblades.stackoverflow.app.MainActivity">

    <TabHost
        android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <FrameLayout android:id="@android:id/tabcontent"
                android:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"/>
            <android.support.v4.view.ViewPager
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/pager"
                android:background="@drawable/background_image"
                android:layout_width="match_parent"
                android:layout_weight="0.5"
                android:layout_height="0dp"
                tools:context="com.ringofblades.stackoverflow.app.MainActivity">
                <FrameLayout
                    android:id="@+id/container"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </android.support.v4.view.ViewPager>
            <TabWidget android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </TabHost>

    <fragment android:id="@+id/navigation_drawer"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:name="com.ringofblades.stackoverflow.app.NavigationDrawerFragment"
        tools:layout="@layout/fragment_navigation_drawer" />
</android.support.v4.widget.DrawerLayout>

Zwróć uwagę, że „@ + id / pager” i „@ + id / container” są rodzeństwem z „android: layout_weight =„ 0.5 ”” i „android: layout_height =„ 0dp ””. Dzieje się tak, żebym mógł to zobaczyć w podglądzie dla dowolnego rozmiaru ekranu. W każdym razie ich wagi będą manipulowane w kodzie podczas działania.


Cześć, jestem ciekawy, dlaczego zdecydowałeś się użyć TabHost zamiast ActionBar z Tabs? Sam przerzuciłem się z TabHost na po prostu ActionBar, a mój kod stał się czystszy i bardziej kompaktowy ...
IgorGanapolsky

O ile pamiętam, jedną wadą używania zakładek w ActionBar jest to, że decyduje się automatycznie wyświetlać je jako rozwijane menu spinner (w przypadku małego ekranu) i to nie było dla mnie dobre. Ale nie jestem w 100% pewien.
WindRider

@Igor, przeczytałem gdzieś tutaj, SOże używanie ActionBarzakładek z a Navigation Drawernie jest dobre, ponieważ automatycznie umieszcza zakładki nad widokiem twojej szuflady. Przepraszam, nie mam linku do wykonania kopii zapasowej.
Azurespot


1
Wow, dzięki za link @Igor! Na pewno to sprawdzę. Nadal jestem początkującym, więc mam milion innych rzeczy do nauczenia się dzięki Androidowi, ale ta wydaje się być klejnotem! Dzięki jeszcze raz.
Azurespot

1

Opierając się na odpowiedzi @ Chris.Jenkins, jest to rozwiązanie, które działało dobrze dla mnie, do usuwania fragmentów podczas zdarzeń cyklu życia (które mają tendencję do rzucania wyjątków IllegalStateExceptions). Wykorzystuje to kombinację podejścia Handler i sprawdzenia Activity.isFinishing () (w przeciwnym razie zgłosi błąd „Nie można wykonać tej akcji po onSaveInstanceState).

import android.app.Activity;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

public abstract class BaseFragment extends Fragment {
    private final Handler handler = new Handler();

    /**
     * Removes the {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragment The {@link Fragment} to schedule for removal.
     */
    protected void removeFragment(@Nullable final Fragment fragment) {
        if (fragment == null) return;

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    getFragmentManager().beginTransaction()
                            .remove(fragment)
                            .commitAllowingStateLoss();
                }
            }
        });
    }

    /**
     * Removes each {@link Fragment} using {@link #getFragmentManager()}, wrapped in a {@link Handler} to
     * compensate for illegal states.
     *
     * @param fragments The {@link Fragment}s to schedule for removal.
     */
    protected void removeFragments(final Fragment... fragments) {
        final FragmentManager fragmentManager = getFragmentManager();
        final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        for (Fragment fragment : fragments) {
            if (fragment != null) {
                fragmentTransaction.remove(fragment);
            }
        }

        final Activity activity = getActivity();
        handler.post(new Runnable() {
            @Override
            public void run() {
                if (activity != null && !activity.isFinishing()) {
                    fragmentTransaction.commitAllowingStateLoss();
                }
            }
        });
    }
}

Stosowanie:

class MyFragment extends Fragment {
    @Override
    public void onDestroyView() {
        removeFragments(mFragment1, mFragment2, mFragment3);
        super.onDestroyView();
    }
}

1

Chociaż PO może mieć szczególne okoliczności, które uniemożliwiają mu korzystanie z Biblioteki wsparcia, większość ludzi powinna z niej korzystać. Dokumentacja Androida zaleca to, dzięki czemu Twoja aplikacja będzie dostępna dla jak najszerszego grona odbiorców.

W mojej pełniejszej odpowiedzi zrobiłem przykład demonstrujący, jak używać zagnieżdżonych fragmentów z biblioteką pomocniczą.

wprowadź opis obrazu tutaj


Byłem OP. Powodem, dla którego nie korzystano z biblioteki pomocy technicznej, był fakt, że była to wewnętrzna aplikacja firmy, w której używany sprzęt został wyraźnie zdefiniowany jako> = 4.0 i <= 4.1. Nie było potrzeby docierania do szerokiego grona odbiorców, był to personel wewnętrzny i brak zamiaru korzystania z aplikacji poza firmą. Jedynym powodem, dla którego biblioteka wsparcia jest wstecznie kompatybilna, jest to, że można by się spodziewać, że wszystko, co można zrobić z biblioteką wsparcia, powinno być możliwe bez niej. Dlaczego „natywna” wyższa wersja miałaby mniej funkcji niż biblioteka obsługująca, której jedynym celem jest po prostu zgodność z poprzednimi wersjami.
Mathias Conradt

1
Niemniej jednak, oczywiście, mógłbyś skorzystać z biblioteki wsparcia i ja też mogłem. Po prostu nie rozumiałem, dlaczego Google oferuje funkcje TYLKO w bibliotece wsparcia, ale nie na zewnątrz, lub dlaczego nazywają to nawet biblioteką wsparcia i nie uczynią tego ogólnym standardem, jeśli i tak jest to najlepsza praktyka. Oto dobry artykuł o bibliotece wsparcia: martiancraft.com/blog/2015/06/android-support-library
Mathias

@Mathias, fajny artykuł.
Suragch
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.