Zamień fragment w ViewPager


268

Próbuję użyć Fragment za ViewPagerpomocą FragmentPagerAdapter. To, czego szukam, to zamiana fragmentu umieszczonego na pierwszej stronie na ViewPagerinny.

Pager składa się z dwóch stron. Pierwszym z nich jest FirstPagerFragment, drugim jest SecondPagerFragment. Kliknięcie przycisku na pierwszej stronie. Chciałbym zamienić FirstPagerFragmentna NextFragment.

Oto mój kod poniżej.

public class FragmentPagerActivity extends FragmentActivity {

    static final int NUM_ITEMS = 2;

    MyAdapter mAdapter;
    ViewPager mPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment_pager);

        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);

    }


    /**
     * Pager Adapter
     */
    public static class MyAdapter extends FragmentPagerAdapter {
        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return NUM_ITEMS;
        }

        @Override
        public Fragment getItem(int position) {

            if(position == 0) {
                return FirstPageFragment.newInstance();
            } else {
                return SecondPageFragment.newInstance();
            }

        }
    }


    /**
     * Second Page FRAGMENT
     */
    public static class SecondPageFragment extends Fragment {

        public static SecondPageFragment newInstance() {
            SecondPageFragment f = new SecondPageFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            return inflater.inflate(R.layout.second, container, false);

        }
    }

    /**
     * FIRST PAGE FRAGMENT
     */
    public static class FirstPageFragment extends Fragment {

        Button button;

        public static FirstPageFragment newInstance() {
            FirstPageFragment f = new FirstPageFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            View root = inflater.inflate(R.layout.first, container, false);
            button = (Button) root.findViewById(R.id.button);
            button.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    FragmentTransaction trans = getFragmentManager().beginTransaction();
                                    trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
                    trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
                    trans.addToBackStack(null);
                    trans.commit();

                }

            });

            return root;
        }

        /**
     * Next Page FRAGMENT in the First Page
     */
    public static class NextFragment extends Fragment {

        public static NextFragment newInstance() {
            NextFragment f = new NextFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            return inflater.inflate(R.layout.next, container, false);

        }
    }
}

... a tutaj pliki xml

fragment_pager.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:padding="4dip"
        android:gravity="center_horizontal"
        android:layout_width="match_parent" android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1">
    </android.support.v4.view.ViewPager>

</LinearLayout>

first.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/first_fragment_root_id"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <Button android:id="@+id/button"
     android:layout_width="wrap_content" android:layout_height="wrap_content"
     android:text="to next"/>

</LinearLayout>

Teraz problem ... w którym identyfikatorze powinienem użyć

trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());

?

Jeśli użyję R.id.first_fragment_root_id, zamiana działa, ale Hierarchy Viewer pokazuje dziwne zachowanie, jak poniżej.

Na początku jest tak

po wymianie sytuacja wygląda następująco

Jak widać, coś jest nie tak, spodziewam się znaleźć taki sam stan, jak na pierwszym zdjęciu po zastąpieniu fragmentu.


Czy wygenerowałeś jakiś schemat układu, czy zrobiłeś to ręcznie?
Jeroen Vannevel 18.04.15

@Noodles: Muszę zrobić to samo. Twoje pytanie wygenerowało długi wątek z wieloma różnymi odpowiedziami. Co w końcu działało najlepiej? Wielkie dzięki!
narb

1
@JeroenVannevel, być może już to rozgryzłeś, ale ten schemat układu to developer.android.com/tools/performance/hierarchy-viewer/…
Ankit Popli

masz problem, potrzebujesz pomocy stackoverflow.com/questions/40149039/…
albert

Odpowiedzi:


162

Istnieje inne rozwiązanie, które nie wymaga modyfikacji kodu źródłowego ViewPageri FragmentStatePagerAdapter, i działa z FragmentPagerAdapterklasą podstawową używaną przez autora.

Chciałbym zacząć od odpowiedzi na pytanie autora dotyczące tego, z którego identyfikatora powinien korzystać; jest to identyfikator kontenera, tj. identyfikator samego pagera widokowego. Jednak, jak zapewne zauważyłeś, użycie tego identyfikatora w kodzie powoduje, że nic się nie dzieje. Wyjaśnię dlaczego:

Po pierwsze, aby ViewPagerponownie zapełnić strony, musisz wywołać notifyDataSetChanged()rezydującą w klasie bazowej adaptera.

Po drugie, ViewPagerużywa getItemPosition()metody abstrakcyjnej, aby sprawdzić, które strony powinny zostać zniszczone, a które należy zachować. Domyślna implementacja tej funkcji zawsze zwraca wartość POSITION_UNCHANGED, co powoduje ViewPagerzachowanie wszystkich bieżących stron, aw konsekwencji nie dołączenie nowej strony. Dlatego, aby zastąpienie fragmentu działało, getItemPosition()musi zostać zastąpione w adapterze i musi powrócić, POSITION_NONEgdy zostanie wywołane ze starym, ukrytym, fragmentem jako argumentem.

Oznacza to również, że twój adapter zawsze musi wiedzieć, który fragment powinien być wyświetlany w pozycji 0 FirstPageFragmentlub NextFragment. Jednym ze sposobów na to jest dostarczenie detektora podczas tworzenia FirstPageFragment, który zostanie wywołany, gdy nadejdzie czas na zmianę fragmentów. Myślę, że to dobra rzecz, aby umożliwić adapterowi fragmentów obsługę wszystkich przełączników fragmentów i wywołań do ViewPageri FragmentManager.

Po trzecie, FragmentPagerAdapterbuforuje używane fragmenty nazwą pochodzącą od pozycji, więc jeśli na pozycji 0 istniał fragment, nie zostanie on zastąpiony, nawet jeśli klasa jest nowa. Istnieją dwa rozwiązania, ale najprostszym jest użycie remove()funkcji FragmentTransaction, która również usunie jego tag.

To było dużo tekstu, oto kod, który powinien działać w twoim przypadku:

public class MyAdapter extends FragmentPagerAdapter
{
    static final int NUM_ITEMS = 2;
    private final FragmentManager mFragmentManager;
    private Fragment mFragmentAtPos0;

    public MyAdapter(FragmentManager fm)
    {
        super(fm);
        mFragmentManager = fm;
    }

    @Override
    public Fragment getItem(int position)
    {
        if (position == 0)
        {
            if (mFragmentAtPos0 == null)
            {
                mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener()
                {
                    public void onSwitchToNextFragment()
                    {
                        mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
                        mFragmentAtPos0 = NextFragment.newInstance();
                        notifyDataSetChanged();
                    }
                });
            }
            return mFragmentAtPos0;
        }
        else
            return SecondPageFragment.newInstance();
    }

    @Override
    public int getCount()
    {
        return NUM_ITEMS;
    }

    @Override
    public int getItemPosition(Object object)
    {
        if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }
}

public interface FirstPageFragmentListener
{
    void onSwitchToNextFragment();
}

Mam nadzieję, że to pomoże każdemu!


24
działa również dla mnie, ale jak zaimplementować przycisk Wstecz, aby wyświetlić pierwszy fragment?
user1159819,

6
Świetne wyjaśnienie, ale czy możesz opublikować pełny kod źródłowy tego, w szczególności klasy FirstPageFragment i SecondPageFragment.
georgiecasey

6
Jak dodać FirstPageFragmentListener do pakietu w newInstance ()?
Miguel

4
Czy możesz dodać kod, który obsługuje FirstPageFragment.newInstance()parametr listener?
MiguelHincapieC

6
Działa to tylko dla mnie, jeśli wywołam commitNow () zamiast commit () podczas usuwania fragmentu. Nie jestem pewien, dlaczego mój przypadek użycia jest inny, ale mam nadzieję, że zaoszczędzi to komuś trochę czasu.
Ian Lovejoy

37

Od 13 listopada 2012 r. Wydaje się, że zmiana fragmentów w ViewPager stała się znacznie łatwiejsza. Google wypuścił system Android 4.2 z obsługą zagnieżdżonych fragmentów, a także jest obsługiwany w nowej bibliotece obsługi systemu Android v11, więc będzie to działało aż do wersji 1.6

Jest to bardzo podobne do normalnego sposobu zastępowania fragmentu, z wyjątkiem użycia getChildFragmentManager. Wydaje się, że działa, ale zagnieżdżony fragment backstack nie jest wyskakujący, gdy użytkownik kliknie przycisk Wstecz. Zgodnie z rozwiązaniem w tym połączonym pytaniu należy ręcznie wywołać funkcję popBackStackImmediate () w menedżerze podrzędnym fragmentu. Musisz więc zastąpić funkcję onBackPressed () działania ViewPager, w której uzyskasz bieżący fragment ViewPager i wywołasz na nim funkcję getChildFragmentManager (). PopBackStackImmediate ().

Uzyskiwanie aktualnie wyświetlanego fragmentu jest również trochę hackerskie, użyłem tego brudnego rozwiązania „android: switcher: VIEWPAGER_ID: INDEX”, ale możesz również samodzielnie śledzić wszystkie fragmenty ViewPager, jak wyjaśniono w drugim rozwiązaniu na tej stronie .

Oto mój kod dla ViewPager z 4 ListViews z widokiem szczegółowym pokazanym w ViewPager, gdy użytkownik kliknie wiersz i przy działającym przycisku Wstecz. Próbowałem dołączyć tylko odpowiedni kod ze względu na zwięzłość, więc zostaw komentarz, jeśli chcesz przesłać pełną aplikację do GitHub.

HomeActivity.java

 public class HomeActivity extends SherlockFragmentActivity {
FragmentAdapter mAdapter;
ViewPager mPager;
TabPageIndicator mIndicator;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    mAdapter = new FragmentAdapter(getSupportFragmentManager());
    mPager = (ViewPager)findViewById(R.id.pager);
    mPager.setAdapter(mAdapter);
    mIndicator = (TabPageIndicator)findViewById(R.id.indicator);
    mIndicator.setViewPager(mPager);
}

// This the important bit to make sure the back button works when you're nesting fragments. Very hacky, all it takes is some Google engineer to change that ViewPager view tag to break this in a future Android update.
@Override
public void onBackPressed() {
    Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":"+mPager.getCurrentItem());
    if (fragment != null) // could be null if not instantiated yet
    {
        if (fragment.getView() != null) {
            // Pop the backstack on the ChildManager if there is any. If not, close this activity as normal.
            if (!fragment.getChildFragmentManager().popBackStackImmediate()) {
                finish();
            }
        }
    }
}

class FragmentAdapter extends FragmentPagerAdapter {        
    public FragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
        case 0:
            return ListProductsFragment.newInstance();
        case 1:
            return ListActiveSubstancesFragment.newInstance();
        case 2:
            return ListProductFunctionsFragment.newInstance();
        case 3:
            return ListCropsFragment.newInstance();
        default:
            return null;
        }
    }

    @Override
    public int getCount() {
        return 4;
    }

 }
}

ListProductsFragment.java

public class ListProductsFragment extends SherlockFragment {
private ListView list;

public static ListProductsFragment newInstance() {
    ListProductsFragment f = new ListProductsFragment();
    return f;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View V = inflater.inflate(R.layout.list, container, false);
    list = (ListView)V.findViewById(android.R.id.list);
    list.setOnItemClickListener(new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view,
            int position, long id) {
          // This is important bit
          Fragment productDetailFragment = FragmentProductDetail.newInstance();
          FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
          transaction.addToBackStack(null);
          transaction.replace(R.id.products_list_linear, productDetailFragment).commit();
        }
      });       
    return V;
}
}

7
Czy będzie możliwe przesłanie kodu do github? dzięki.
Gautham

4
@georgiecasey: Pobrałem API 17, Android 4.2, który pozwolił mi zacząć korzystać z getChildFragmentManager (). Ale pewnie czegoś mi brakuje w twoim rozwiązaniu, ponieważ teraz nakładam się na stare + nowe fragmenty na ekranie. Uwaga: próbuję użyć twojego rozwiązania w porozumieniu z przykładowym kodem Google TabsAdapter od developer.android.com/reference/android/support/v4/view/… . Dziękujemy za wszelkie sugestie i / lub kod referencyjny.
gcl1

27
Do czego odnosi się R.id.products_list_linear? Link do swojego kodu, jak oferowano.
howettl

1
@howettl odniesienia do identyfikatora zewnętrznego fragmentu członka. Kluczem do rozwiązania tego problemu jest użycie zagnieżdżonych fragmentów (w ten sposób nie musisz manipulować adapterem przeglądarki).
Indrek Kõue

2
Czy to kiedykolwiek trafiło na github?
Baruch

32

Na podstawie odpowiedzi @wize, która okazała się pomocna i elegancka, mogłem częściowo osiągnąć to, czego chciałem, ponieważ chciałem, aby możliwość powrotu do pierwszego fragmentu po wymianie. Osiągnąłem to trochę modyfikując nieco jego kod.

Byłby to FragmentPagerAdapter:

public static class MyAdapter extends FragmentPagerAdapter {
    private final class CalendarPageListener implements
            CalendarPageFragmentListener {
        public void onSwitchToNextFragment() {
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
                    .commit();
            if (mFragmentAtPos0 instanceof FirstFragment){
                mFragmentAtPos0 = NextFragment.newInstance(listener);
            }else{ // Instance of NextFragment
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            notifyDataSetChanged();
        }
    }

    CalendarPageListener listener = new CalendarPageListener();;
    private Fragment mFragmentAtPos0;
    private FragmentManager mFragmentManager;

    public MyAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
    }

    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    @Override
    public int getItemPosition(Object object) {
        if (object instanceof FirstFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
        if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }

    @Override
    public Fragment getItem(int position) {
        if (position == 0)
            return Portada.newInstance();
        if (position == 1) { // Position where you want to replace fragments
            if (mFragmentAtPos0 == null) {
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            return mFragmentAtPos0;
        }
        if (position == 2)
            return Clasificacion.newInstance();
        if (position == 3)
            return Informacion.newInstance();

        return null;
    }
}

public interface CalendarPageFragmentListener {
    void onSwitchToNextFragment();
}

Aby wykonać zamianę, po prostu zdefiniuj pole statyczne typu CalendarPageFragmentListeneri zainicjowane newInstancemetodami odpowiednich fragmentów i wywołaj FirstFragment.pageListener.onSwitchToNextFragment()lub NextFragment.pageListener.onSwitchToNextFragment()ponownie.


Jak przeprowadzasz wymianę? nie można zainicjować nasłuchiwania bez przesłaniania metody onSwitchToNext.
Leandros,

1
czy nie stracisz stanu zapisanego w mFragmentAtPos0, jeśli obrócisz urządzenie?
seb

@seb, faktycznie poprawiłem to rozwiązanie, aby zapisać mFragmentAtPos0odniesienie podczas zapisywania stanu aktywności. To nie jest najbardziej eleganckie rozwiązanie, ale działa.
mdelolmo

Możesz zobaczyć moją odpowiedź . Zastępuje fragmenty i wraca do pierwszego.
Lyd,

@mdelolmo Czy możesz rzucić okiem na to pytanie dotyczące ViewPager? Dziękuję stackoverflow.com/questions/27937250/…
Hoa Vu

21

Wdrożyłem rozwiązanie dla:

  • Dynamiczna zamiana fragmentów w zakładce
  • Utrzymanie historii według zakładki
  • Praca ze zmianami orientacji

Aby to osiągnąć, sztuczki są następujące:

  • Użyj metody replaceDataSetChanged (), aby zastosować zamianę fragmentu
  • Używaj menedżera fragmentów tylko do cofania, a nie do zamiany fragmentów
  • Zachowaj historię za pomocą wzorca memento (stosu)

Kod adaptera jest następujący:

public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener {

/** The sherlock fragment activity. */
private final SherlockFragmentActivity mActivity;

/** The action bar. */
private final ActionBar mActionBar;

/** The pager. */
private final ViewPager mPager;

/** The tabs. */
private List<TabInfo> mTabs = new LinkedList<TabInfo>();

/** The total number of tabs. */
private int TOTAL_TABS;

private Map<Integer, Stack<TabInfo>> history = new HashMap<Integer, Stack<TabInfo>>();

/**
 * Creates a new instance.
 *
 * @param activity the activity
 * @param pager    the pager
 */
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
    super(activity.getSupportFragmentManager());
    activity.getSupportFragmentManager();
    this.mActivity = activity;
    this.mActionBar = activity.getSupportActionBar();
    this.mPager = pager;
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}

/**
 * Adds the tab.
 *
 * @param image         the image
 * @param fragmentClass the class
 * @param args          the arguments
 */
public void addTab(final Drawable image, final Class fragmentClass, final Bundle args) {
    final TabInfo tabInfo = new TabInfo(fragmentClass, args);
    final ActionBar.Tab tab = mActionBar.newTab();
    tab.setTabListener(this);
    tab.setTag(tabInfo);
    tab.setIcon(image);

    mTabs.add(tabInfo);
    mActionBar.addTab(tab);

    notifyDataSetChanged();
}

@Override
public Fragment getItem(final int position) {
    final TabInfo tabInfo = mTabs.get(position);
    return Fragment.instantiate(mActivity, tabInfo.fragmentClass.getName(), tabInfo.args);
}

@Override
public int getItemPosition(final Object object) {
    /* Get the current position. */
    int position = mActionBar.getSelectedTab().getPosition();

    /* The default value. */
    int pos = POSITION_NONE;
    if (history.get(position).isEmpty()) {
        return POSITION_NONE;
    }

    /* Checks if the object exists in current history. */
    for (Stack<TabInfo> stack : history.values()) {
        TabInfo c = stack.peek();
        if (c.fragmentClass.getName().equals(object.getClass().getName())) {
            pos = POSITION_UNCHANGED;
            break;
        }
    }
    return pos;
}

@Override
public int getCount() {
    return mTabs.size();
}

@Override
public void onPageScrollStateChanged(int arg0) {
}

@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}

@Override
public void onPageSelected(int position) {
    mActionBar.setSelectedNavigationItem(position);
}

@Override
public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction ft) {
    TabInfo tabInfo = (TabInfo) tab.getTag();
    for (int i = 0; i < mTabs.size(); i++) {
        if (mTabs.get(i).equals(tabInfo)) {
            mPager.setCurrentItem(i);
        }
    }
}

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

public void replace(final int position, final Class fragmentClass, final Bundle args) {
    /* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();
}

/**
 * Updates the tabs.
 *
 * @param tabInfo
 *          the new tab info
 * @param position
 *          the position
 */
private void updateTabs(final TabInfo tabInfo, final int position) {
    mTabs.remove(position);
    mTabs.add(position, tabInfo);
    mActionBar.getTabAt(position).setTag(tabInfo);
}

/**
 * Creates the history using the current state.
 */
public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

/**
 * Called on back
 */
public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

/**
 * Returns if the history is empty.
 *
 * @param position
 *          the position
 * @return  the flag if empty
 */
private boolean historyIsEmpty(final int position) {
    return history == null || history.isEmpty() || history.get(position).isEmpty();
}

private boolean isLastItemInHistory(final int position) {
    return history.get(position).size() == 1;
}

/**
 * Returns the previous state by the position provided.
 *
 * @param position
 *          the position
 * @return  the tab info
 */
private TabInfo getPrevious(final int position) {
    TabInfo currentTabInfo = history.get(position).pop();
    if (!history.get(position).isEmpty()) {
        currentTabInfo = history.get(position).peek();
    }
    return currentTabInfo;
}

/** The tab info class */
private static class TabInfo {

    /** The fragment class. */
    public Class fragmentClass;

    /** The args.*/
    public Bundle args;

    /**
     * Creates a new instance.
     *
     * @param fragmentClass
     *          the fragment class
     * @param args
     *          the args
     */
    public TabInfo(Class fragmentClass, Bundle args) {
        this.fragmentClass = fragmentClass;
        this.args = args;
    }

    @Override
    public boolean equals(final Object o) {
        return this.fragmentClass.getName().equals(o.getClass().getName());
    }

    @Override
    public int hashCode() {
        return fragmentClass.getName() != null ? fragmentClass.getName().hashCode() : 0;
    }

    @Override
    public String toString() {
        return "TabInfo{" +
                "fragmentClass=" + fragmentClass +
                '}';
    }
}

Przy pierwszym dodaniu wszystkich kart musimy wywołać metodę createHistory (), aby utworzyć początkową historię

public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

Za każdym razem, gdy chcesz zastąpić fragment do określonej karty, którą wywołujesz: replace (końcowa pozycja int, końcowy fragment klasyClass, końcowe argumenty pakietu)

/* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();

Po wciśnięciu back musisz wywołać metodę back ():

public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

Rozwiązanie działa z sherlockowym paskiem akcji i gestem przeciągnięcia.


wspaniale! mógłbyś umieścić to na githubie?
Nicramus

Ta implementacja jest najczystsza ze wszystkich podanych tutaj i rozwiązuje każdy problem. To powinna być zaakceptowana odpowiedź.
dazedviper

Czy ktoś miał problemy z tą implementacją? Mam 2 z nich. 1. Pierwszy powrót nie rejestruje się - muszę kliknąć dwukrotnie, aby się zarejestrować. 2. Po kliknięciu wstecz (dwukrotnie) na zakładce a i przejściu do zakładki b, gdy wrócę do zakładki a, po prostu pokazuje zawartość zakładki b
yaronbic

7

tl; dr: Użyj fragmentu hosta, który jest odpowiedzialny za zastąpienie hostowanej zawartości i śledzi historię nawigacji wstecznej (jak w przeglądarce).

Ponieważ twój przypadek użycia składa się ze stałej liczby zakładek, moje rozwiązanie działa dobrze: Chodzi o to, aby wypełnić ViewPager instancjami niestandardowej klasy HostFragment, która jest w stanie zastąpić hostowaną zawartość i zachowuje własną historię nawigacji wstecz. Aby zastąpić hostowany fragment, wywołujesz metodę hostfragment.replaceFragment():

public void replaceFragment(Fragment fragment, boolean addToBackstack) {
    if (addToBackstack) {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
    } else {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
    }
}

Wszystko, co robi ta metoda, to zastąpienie układu ramki idem R.id.hosted_fragmentfragmentem dostarczonym do metody.

Sprawdź mój samouczek na ten temat, aby uzyskać dalsze szczegóły i pełny działający przykład na GitHub!


Chociaż wydaje się to nieporadne, uważam, że jest to najlepszy (pod względem struktury i łatwości konserwacji) sposób na zrobienie tego.
Sira Lam

Skończyłem z headbuttingiem NPE. : / Nie mogę znaleźć Widoku, który ma zostać zastąpiony
Fariz Fakkel

6

Niektóre z przedstawionych rozwiązań bardzo pomogły mi częściowo rozwiązać problem, ale wciąż brakuje jednej ważnej rzeczy w rozwiązaniach, które spowodowały nieoczekiwane wyjątki i zawartość czarnej strony zamiast fragmentacji treści w niektórych przypadkach.

Chodzi o to, że klasa FragmentPagerAdapter używa ID elementu do przechowywania buforowanych fragmentów w FragmentManager . Z tego powodu należy również przesłonić metodę getItemId (int position) , aby zwracała ona np. Pozycję dla stron najwyższego poziomu i pozycję 100+ dla stron ze szczegółami. W przeciwnym razie wcześniej utworzony fragment najwyższego poziomu zostałby zwrócony z pamięci podręcznej zamiast fragmentu poziomu szczegółowego.

Ponadto dzielę się tutaj kompletnym przykładem, jak zaimplementować działanie podobne do kart ze stronami fragmentów za pomocą ViewPager i przycisków kart za pomocą RadioGroup, która umożliwia zastąpienie stron najwyższego poziomu szczegółowymi stronami, a także obsługuje przycisk Wstecz. Ta implementacja obsługuje tylko jeden poziom układania wstecznego (lista elementów - szczegóły pozycji), ale wielopoziomowa implementacja układania wstecznego jest prosta. Ten przykład działa całkiem dobrze w normalnych przypadkach, z wyjątkiem tego, że generuje wyjątek NullPointerException w przypadku, gdy przełączasz się np. Na drugą stronę, zmieniasz fragment pierwszej strony (chociaż nie jest widoczny) i powracasz do pierwszej strony. Kiedy to rozwiążę, opublikuję rozwiązanie tego problemu:

public class TabsActivity extends FragmentActivity {

  public static final int PAGE_COUNT = 3;
  public static final int FIRST_PAGE = 0;
  public static final int SECOND_PAGE = 1;
  public static final int THIRD_PAGE = 2;

  /**
   * Opens a new inferior page at specified tab position and adds the current page into back
   * stack.
   */
  public void startPage(int position, Fragment content) {
    // Replace page adapter fragment at position.
    mPagerAdapter.start(position, content);
  }

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

    // Initialize basic layout.
    this.setContentView(R.layout.tabs_activity);

    // Add tab fragments to view pager.
    {
      // Create fragments adapter.
      mPagerAdapter = new PagerAdapter(pager);
      ViewPager pager = (ViewPager) super.findViewById(R.id.tabs_view_pager);
      pager.setAdapter(mPagerAdapter);

      // Update active tab in tab bar when page changes.
      pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int index, float value, int nextIndex) {
          // Not used.
        }

        @Override
        public void onPageSelected(int index) {
          RadioGroup tabs_radio_group = (RadioGroup) TabsActivity.this.findViewById(
            R.id.tabs_radio_group);
          switch (index) {
            case 0: {
              tabs_radio_group.check(R.id.first_radio_button);
            }
            break;
            case 1: {
              tabs_radio_group.check(R.id.second_radio_button);
            }
            break;
            case 2: {
              tabs_radio_group.check(R.id.third_radio_button);
            }
            break;
          }
        }

        @Override
        public void onPageScrollStateChanged(int index) {
          // Not used.
        }
      });
    }

    // Set "tabs" radio group on checked change listener that changes the displayed page.
    RadioGroup radio_group = (RadioGroup) this.findViewById(R.id.tabs_radio_group);
    radio_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
      @Override
      public void onCheckedChanged(RadioGroup radioGroup, int id) {
        // Get view pager representing tabs.
        ViewPager view_pager = (ViewPager) TabsActivity.this.findViewById(R.id.tabs_view_pager);
        if (view_pager == null) {
          return;
        }

        // Change the active page.
        switch (id) {
          case R.id.first_radio_button: {
            view_pager.setCurrentItem(FIRST_PAGE);
          }
          break;
          case R.id.second_radio_button: {
            view_pager.setCurrentItem(SECOND_PAGE);
          }
          break;
          case R.id.third_radio_button: {
            view_pager.setCurrentItem(THIRD_PAGE);
          }
          break;
        }
      });
    }
  }

  @Override
  public void onBackPressed() {
    if (!mPagerAdapter.back()) {
      super.onBackPressed();
    }
  }

  /**
   * Serves the fragments when paging.
   */
  private class PagerAdapter extends FragmentPagerAdapter {

    public PagerAdapter(ViewPager container) {
      super(TabsActivity.this.getSupportFragmentManager());

      mContainer = container;
      mFragmentManager = TabsActivity.this.getSupportFragmentManager();

      // Prepare "empty" list of fragments.
      mFragments = new ArrayList<Fragment>(){};
      mBackFragments = new ArrayList<Fragment>(){};
      for (int i = 0; i < PAGE_COUNT; i++) {
        mFragments.add(null);
        mBackFragments.add(null);
      }
    }

    /**
     * Replaces the view pager fragment at specified position.
     */
    public void replace(int position, Fragment fragment) {
      // Get currently active fragment.
      Fragment old_fragment = mFragments.get(position);
      if (old_fragment == null) {
        return;
      }

      // Replace the fragment using transaction and in underlaying array list.
      // NOTE .addToBackStack(null) doesn't work
      this.startUpdate(mContainer);
      mFragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
        .remove(old_fragment).add(mContainer.getId(), fragment)
        .commit();
      mFragments.set(position, fragment);
      this.notifyDataSetChanged();
      this.finishUpdate(mContainer);
    }

    /**
     * Replaces the fragment at specified position and stores the current fragment to back stack
     * so it can be restored by #back().
     */
    public void start(int position, Fragment fragment) {
      // Remember current fragment.
      mBackFragments.set(position, mFragments.get(position));

      // Replace the displayed fragment.
      this.replace(position, fragment);
    }

    /**
     * Replaces the current fragment by fragment stored in back stack. Does nothing and returns
     * false if no fragment is back-stacked.
     */
    public boolean back() {
      int position = mContainer.getCurrentItem();
      Fragment fragment = mBackFragments.get(position);
      if (fragment == null) {
        // Nothing to go back.
        return false;
      }

      // Restore the remembered fragment and remove it from back fragments.
      this.replace(position, fragment);
      mBackFragments.set(position, null);
      return true;
    }

    /**
     * Returns fragment of a page at specified position.
     */
    @Override
    public Fragment getItem(int position) {
      // If fragment not yet initialized, create its instance.
      if (mFragments.get(position) == null) {
        switch (position) {
          case FIRST_PAGE: {
            mFragments.set(FIRST_PAGE, new DefaultFirstFragment());
          }
          break;
          case SECOND_PAGE: {
            mFragments.set(SECOND_PAGE, new DefaultSecondFragment());
          }
          break;
          case THIRD_PAGE: {
            mFragments.set(THIRD_PAGE, new DefaultThirdFragment());
          }
          break;
        }
      }

      // Return fragment instance at requested position.
      return mFragments.get(position);
    }

    /**
     * Custom item ID resolution. Needed for proper page fragment caching.
     * @see FragmentPagerAdapter#getItemId(int).
     */
    @Override
    public long getItemId(int position) {
      // Fragments from second level page hierarchy have their ID raised above 100. This is
      // important to FragmentPagerAdapter because it is caching fragments to FragmentManager with
      // this item ID key.
      Fragment item = mFragments.get(position);
      if (item != null) {
        if ((item instanceof NewFirstFragment) || (item instanceof NewSecondFragment) ||
          (item instanceof NewThirdFragment)) {
          return 100 + position;
        }
      }

      return position;
    }

    /**
     * Returns number of pages.
     */
    @Override
    public int getCount() {
      return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object)
    {
      int position = POSITION_UNCHANGED;
      if ((object instanceof DefaultFirstFragment) || (object instanceof NewFirstFragment)) {
        if (object.getClass() != mFragments.get(FIRST_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultSecondragment) || (object instanceof NewSecondFragment)) {
        if (object.getClass() != mFragments.get(SECOND_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultThirdFragment) || (object instanceof NewThirdFragment)) {
        if (object.getClass() != mFragments.get(THIRD_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      return position;
    }

    private ViewPager mContainer;
    private FragmentManager mFragmentManager;

    /**
     * List of page fragments.
     */
    private List<Fragment> mFragments;

    /**
     * List of page fragments to return to in onBack();
     */
    private List<Fragment> mBackFragments;
  }

  /**
   * Tab fragments adapter.
   */
  private PagerAdapter mPagerAdapter;
}

Hmm, jest jeszcze jeden problem z tym kodem. Gdy chcę rozpocząć działanie od fragmentu, który został usunięty i dodany ponownie, pojawia się błąd: „Stan zapisywania niepowodzenia: aktywny NewFirstFragment {405478a0} wyczyścił indeks: -1” (śledzenie pełnego stosu: nopaste.info/547a23dfea.html ). Patrząc na źródła Androida, stwierdzam, że ma to coś wspólnego z backstackiem, ale do tej pory nie wiem, jak to naprawić. Czy ktoś ma jakiś pomysł?
Blackhex,

2
Fajnie, używając <code> mFragmentManager.beginTransaction (). Detach (old_fragment) .attach (fragment) .commit (); </code> zamiast <pre> mFragmentManager.beginTransaction (). SetTransition (FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .remove old_fragment) .add (mContainer.getId (), fragment) .commit (); </pre> w powyższym przykładzie wydaje się rozwiązać oba problemy :-).
Blackhex,

1
Jeśli używasz tej metody, upewnij się, że nie pozwalasz Androidowi na odtworzenie Aktywności podczas obracania urządzenia. W przeciwnym razie PagerAdapter zostanie ponownie utworzony i utracisz swoje mBackFragments. Spowoduje to także poważne dezorientowanie narzędzia FragmentManager. Użycie BackStack pozwala uniknąć tego problemu kosztem bardziej skomplikowanej implementacji PagerAdapter (tzn. Nie można dziedziczyć po FragmentPagerAdapter).
Norman

6

Utworzyłem ViewPager z 3 elementami i 2 elementami podrzędnymi dla indeksu 2 i 3 i tutaj chciałem zrobić ..

wprowadź opis zdjęcia tutaj

Zaimplementowałem to przy pomocy poprzednich pytań i odpowiedzi StackOverFlow i oto link.

ViewPagerChildFragments


Twój projekt nie powiedzie się, jeśli obrócisz urządzenie.
Dory,

6

Aby zastąpić fragment wewnątrz ViewPager, możesz przenieść kody źródłowe ViewPager, PagerAdapteriFragmentStatePagerAdapter klas do projektu i dodać następujący kod.

w ViewPager:

public void notifyItemChanged(Object oldItem, Object newItem) {
    if (mItems != null) {
            for (ItemInfo itemInfo : mItems) {
                        if (itemInfo.object.equals(oldItem)) {
                                itemInfo.object = newItem;
                        }
                    }
       }
       invalidate();
    }

w FragmentStatePagerAdapter:

public void replaceFragmetns(ViewPager container, Fragment oldFragment, Fragment newFragment) {
       startUpdate(container);

       // remove old fragment

       if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
       int position = getFragmentPosition(oldFragment);
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, null);
        mFragments.set(position, null);

        mCurTransaction.remove(oldFragment);

        // add new fragment

        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        mFragments.set(position, newFragment);
        mCurTransaction.add(container.getId(), newFragment);

       finishUpdate(container);

       // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
       handleGetItemInbalidated(container, oldFragment, newFragment);

       container.notifyItemChanged(oldFragment, newFragment);
    }

protected abstract void handleGetItemInbalidated(View container, Fragment oldFragment, Fragment newFragment);
protected abstract int  getFragmentPosition(Fragment fragment);

handleGetItemInvalidated()zapewnia, że ​​po następnym wywołaniu getItem()zwróci newFragment getFragmentPosition() zwraca pozycję fragmentu w adapterze.

Teraz, aby zastąpić fragmenty, zadzwoń

mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);

Jeśli interesuje Cię przykładowy projekt, zapytaj mnie o źródła.


Jak powinny wyglądać metody getFragmentPosition () i handleGetItemInbalidated ()? Nie jestem w stanie zaktualizować getItem (), aby zwrócić NewFragment .. Proszę o pomoc ..

Cześć! Projekt testowy można znaleźć pod tym linkiem files.mail.ru/eng?back=%2FEZU6G4
AndroidTeam At Mail.Ru

1
Twój projekt testowy zakończy się niepowodzeniem, jeśli obrócisz urządzenie.
seb

1
@ AndroidTeamAtMail.Ru wydaje się, że link wygasł. Czy możesz podzielić się kodem
Sunny

Możesz zobaczyć moją odpowiedź . Zastępuje fragmenty i wraca do pierwszego.
Lyd,

4

Działa świetnie z rozwiązaniem AndroidTeam, jednak stwierdziłem, że potrzebowałem możliwości cofnięcia się w podobny sposób. FrgmentTransaction.addToBackStack(null) Jednak samo dodanie tego spowoduje tylko zastąpienie fragmentu bez powiadamiania ViewPager. Połączenie dostarczonego rozwiązania z tym niewielkim ulepszeniem pozwoli ci powrócić do poprzedniego stanu, po prostu zastępując onBackPressed()metodę działania. Największą wadą jest to, że cofnie się tylko raz na raz, co może spowodować wielokrotne kliknięcia wstecz

private ArrayList<Fragment> bFragments = new ArrayList<Fragment>();
private ArrayList<Integer> bPosition = new ArrayList<Integer>();

public void replaceFragmentsWithBackOut(ViewPager container, Fragment oldFragment, Fragment newFragment) {
    startUpdate(container);

    // remove old fragment

    if (mCurTransaction == null) {
         mCurTransaction = mFragmentManager.beginTransaction();
     }
    int position = getFragmentPosition(oldFragment);
     while (mSavedState.size() <= position) {
         mSavedState.add(null);
     }

     //Add Fragment to Back List
     bFragments.add(oldFragment);

     //Add Pager Position to Back List
     bPosition.add(position);

     mSavedState.set(position, null);
     mFragments.set(position, null);

     mCurTransaction.remove(oldFragment);

     // add new fragment

     while (mFragments.size() <= position) {
         mFragments.add(null);
     }
     mFragments.set(position, newFragment);
     mCurTransaction.add(container.getId(), newFragment);

    finishUpdate(container);

    // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
    handleGetItemInvalidated(container, oldFragment, newFragment);

    container.notifyItemChanged(oldFragment, newFragment);
 }


public boolean popBackImmediate(ViewPager container){
    int bFragSize = bFragments.size();
    int bPosSize = bPosition.size();

    if(bFragSize>0 && bPosSize>0){
        if(bFragSize==bPosSize){
            int last = bFragSize-1;
            int position = bPosition.get(last);

            //Returns Fragment Currently at this position
            Fragment replacedFragment = mFragments.get(position);               
            Fragment originalFragment = bFragments.get(last);

            this.replaceFragments(container, replacedFragment, originalFragment);

            bPosition.remove(last);
            bFragments.remove(last);

            return true;
        }
    }

    return false;       
}

Mam nadzieję, że to komuś pomoże.

Również w miarę możliwości getFragmentPosition()jest getItem()odwrotnie. Wiesz, które fragmenty idą gdzieś, po prostu upewnij się, że zwrócisz prawidłową pozycję, w której się znajdzie. Oto przykład:

    @Override
    protected int getFragmentPosition(Fragment fragment) {
            if(fragment.equals(originalFragment1)){
                return 0;
            }
            if(fragment.equals(replacementFragment1)){
                return 0;
            }
            if(fragment.equals(Fragment2)){
                return 1;
            }
        return -1;
    }

3

W twojej onCreateViewmetodzie containerjest tak naprawdę ViewPagerinstancją.

Po prostu dzwonię

ViewPager vpViewPager = (ViewPager) container;
vpViewPager.setCurrentItem(1);

zmieni aktualny fragment w twoim ViewPager.


1
Od wielu godzin poszukiwań potrzebowałem tej jednej linii kodu, aby wszystkie moje karty działały poprawnie. vpViewPager.setCurrentItem(1);. Przejrzałem przykład każdej osoby, nic się nie działo za każdym razem, aż w końcu dotarłem do twojego. Dziękuję Ci.
Brandon

1
umysł oszalały ... to jest niesamowite !! Nie zdawałem sobie sprawy, że zmienną kontenera był sam podgląd. Ta odpowiedź musi zostać zbadana przed wszystkimi innymi długimi. Ponadto nie ma przekazywania zmiennych detektora do konstruktora fragmentów, co upraszcza całą implementację, gdy Android automatycznie odtwarza fragmenty przy użyciu domyślnego konstruktora no-arg po zmianie konfiguracji.
Kevin Lee

3
czy to nie zmienia się tylko w istniejącą kartę? jak to się ma do zastępowania fragmentów?
behelit

2
Zgadzam się z @behelit, to nie odpowiada na pytanie, po prostu przeniesie na ViewPagerinną stronę, nie zastąpi żadnego fragmentu.
Yoann Hercouet,

2

Oto moje stosunkowo proste rozwiązanie tego problemu. Kluczami do tego rozwiązania jest użycie FragmentStatePagerAdapterzamiast tego, FragmentPagerAdapterponieważ pierwszy usunie dla ciebie nieużywane fragmenty, a później zachowa swoje wystąpienia. Drugi to użycie POSITION_NONEw getItem (). Użyłem prostej listy do śledzenia moich fragmentów. Moim wymaganiem było zastąpienie całej listy fragmentów jednocześnie nową listą, ale poniższe można łatwo zmodyfikować, aby zastąpić poszczególne fragmenty:

public class MyFragmentAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> fragmentList = new ArrayList<Fragment>();
    private List<String> tabTitleList = new ArrayList<String>();

    public MyFragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    public void addFragments(List<Fragment> fragments, List<String> titles) {
        fragmentList.clear();
        tabTitleList.clear();
        fragmentList.addAll(fragments);
        tabTitleList.addAll(titles);
        notifyDataSetChanged();
    }

    @Override
    public int getItemPosition(Object object) {
        if (fragmentList.contains(object)) {
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int item) {
        if (item >= fragmentList.size()) {
            return null;
        }
        return fragmentList.get(item);
    }

    @Override
    public int getCount() {
        return fragmentList.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return tabTitleList.get(position);
    }
}

2

Stworzyłem również rozwiązanie, które działa ze Stacks . Jest to podejście bardziej modułowe, więc nie musisz określać każdego fragmentu i fragmentu szczegółów w swoim FragmentPagerAdapter. Jest oparty na przykładzie z ActionbarSherlock, który pochodzi, jeśli jestem bezpośrednio z aplikacji Google Demo.

/**
 * This is a helper class that implements the management of tabs and all
 * details of connecting a ViewPager with associated TabHost.  It relies on a
 * trick.  Normally a tab host has a simple API for supplying a View or
 * Intent that each tab will show.  This is not sufficient for switching
 * between pages.  So instead we make the content part of the tab host
 * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
 * view to show as the tab content.  It listens to changes in tabs, and takes
 * care of switch to the correct paged in the ViewPager whenever the selected
 * tab changes.
 * 
 * Changed to support more Layers of fragments on each Tab.
 * by sebnapi (2012)
 * 
 */
public class TabsAdapter extends FragmentPagerAdapter
        implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
    private final Context mContext;
    private final TabHost mTabHost;
    private final ViewPager mViewPager;

    private ArrayList<String> mTabTags = new ArrayList<String>();
    private HashMap<String, Stack<TabInfo>> mTabStackMap = new HashMap<String, Stack<TabInfo>>();

    static final class TabInfo {
        public final String tag;
        public final Class<?> clss;
        public Bundle args;

        TabInfo(String _tag, Class<?> _class, Bundle _args) {
            tag = _tag;
            clss = _class;
            args = _args;
        }
    }

    static class DummyTabFactory implements TabHost.TabContentFactory {
        private final Context mContext;

        public DummyTabFactory(Context context) {
            mContext = context;
        }

        @Override
        public View createTabContent(String tag) {
            View v = new View(mContext);
            v.setMinimumWidth(0);
            v.setMinimumHeight(0);
            return v;
        }
    }

    public interface SaveStateBundle{
        public Bundle onRemoveFragment(Bundle outState);
    }

    public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        mContext = activity;
        mTabHost = tabHost;
        mViewPager = pager;
        mTabHost.setOnTabChangedListener(this);
        mViewPager.setAdapter(this);
        mViewPager.setOnPageChangeListener(this);
    }

    /**
     * Add a Tab which will have Fragment Stack. Add Fragments on this Stack by using
     * addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args)
     * The Stack will hold always the default Fragment u add here.
     * 
     * DON'T ADD Tabs with same tag, it's not beeing checked and results in unexpected
     * beahvior.
     * 
     * @param tabSpec
     * @param clss
     * @param args
     */
    public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args){
        Stack<TabInfo> tabStack = new Stack<TabInfo>();

        tabSpec.setContent(new DummyTabFactory(mContext));
        mTabHost.addTab(tabSpec);
        String tag = tabSpec.getTag();
        TabInfo info = new TabInfo(tag, clss, args);

        mTabTags.add(tag);                  // to know the position of the tab tag 
        tabStack.add(info);
        mTabStackMap.put(tag, tabStack);
        notifyDataSetChanged();
    }

    /**
     * Will add the Fragment to Tab with the Tag _tag. Provide the Class of the Fragment
     * it will be instantiated by this object. Proivde _args for your Fragment.
     * 
     * @param fm
     * @param _tag
     * @param _class
     * @param _args
     */
    public void addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args){
        TabInfo info = new TabInfo(_tag, _class, _args);
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
        if(frag instanceof SaveStateBundle){
            Bundle b = new Bundle();
            ((SaveStateBundle) frag).onRemoveFragment(b);
            tabStack.peek().args = b;
        }
        tabStack.add(info);
        FragmentTransaction ft = fm.beginTransaction();
        ft.remove(frag).commit();
        notifyDataSetChanged();
    }

    /**
     * Will pop the Fragment added to the Tab with the Tag _tag
     * 
     * @param fm
     * @param _tag
     * @return
     */
    public boolean popFragment(FragmentManager fm, String _tag){
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        if(tabStack.size()>1){
            tabStack.pop();
            Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
            FragmentTransaction ft = fm.beginTransaction();
            ft.remove(frag).commit();
            notifyDataSetChanged();
            return true;
        }
        return false;
    }

    public boolean back(FragmentManager fm) {
        int position = mViewPager.getCurrentItem();
        return popFragment(fm, mTabTags.get(position));
    }

    @Override
    public int getCount() {
        return mTabStackMap.size();
    }

    @Override
    public int getItemPosition(Object object) {
        ArrayList<Class<?>> positionNoneHack = new ArrayList<Class<?>>();
        for(Stack<TabInfo> tabStack: mTabStackMap.values()){
            positionNoneHack.add(tabStack.peek().clss);
        }   // if the object class lies on top of our stacks, we return default
        if(positionNoneHack.contains(object.getClass())){
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        Stack<TabInfo> tabStack = mTabStackMap.get(mTabTags.get(position));
        TabInfo info = tabStack.peek();
        return Fragment.instantiate(mContext, info.clss.getName(), info.args);
    }

    @Override
    public void onTabChanged(String tabId) {
        int position = mTabHost.getCurrentTab();
        mViewPager.setCurrentItem(position);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
        // Unfortunately when TabHost changes the current tab, it kindly
        // also takes care of putting focus on it when not in touch mode.
        // The jerk.
        // This hack tries to prevent this from pulling focus out of our
        // ViewPager.
        TabWidget widget = mTabHost.getTabWidget();
        int oldFocusability = widget.getDescendantFocusability();
        widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        mTabHost.setCurrentTab(position);
        widget.setDescendantFocusability(oldFocusability);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

}

Dodaj to dla funkcji przycisku Wstecz w MainActivity:

@Override
public void onBackPressed() {
  if (!mTabsAdapter.back(getSupportFragmentManager())) {
    super.onBackPressed();
  }
}

Jeśli chcesz zapisać stan fragmentu, gdy zostanie on usunięty. Pozwól swojemu Fragmentowi zaimplementować interfejs SaveStateBundlew funkcji jako pakiet ze stanem zapisu. Uzyskaj pakiet po utworzeniu przez this.getArguments().

Możesz utworzyć instancję takiej karty:

mTabsAdapter.addTab(mTabHost.newTabSpec("firstTabTag").setIndicator("First Tab Title"),
                FirstFragmentActivity.FirstFragmentFragment.class, null);

działa podobnie, jeśli chcesz dodać fragment na stosie kart. Ważne : Myślę, że to nie zadziała, jeśli chcesz mieć 2 wystąpienia tej samej klasy na dwóch kartach. Zrobiłem to rozwiązanie szybko razem, więc mogę je udostępniać tylko bez doświadczenia.


Dostaję wyjątek nullpointer po naciśnięciu przycisku Wstecz, a mój zastąpiony fragment też nie jest widoczny. Więc może robię coś złego: jaki powinien być prawidłowy apel do poprawnej zmiany fragmentów?
Jeroen

Aktualizacja: Naprawiłem większość rzeczy, ale jeśli nacisnę przycisk Wstecz po dodaniu nowego fragmentu, a następnie przejdę do innej karty iz powrotem, otrzymam wyjątek nullpointer. Mam więc 2 pytania: 1: w jaki sposób mogę usunąć historię karty i upewnić się, że tylko najnowszy fragment (góra stosu) jest nadal obecny? 2: jak mogę pozbyć się wyjątku nullpointer?
Jeroen,

@Jeroen 1: możesz uzyskać odpowiedni stos z mTabStackMap, a następnie usunąć każdy obiekt TabInfo, który znajduje się pod górną krawędzią stosu, 2: bardzo trudno powiedzieć, zadaj pytanie z stacktrace, a następnie mógłbym spróbować dowiedzieć się, są na pewno nie jest to błąd w twoim kodzie? Czy dzieje się tak również, jeśli przełączysz się na zakładkę (a następnie z powrotem), która znajduje się w bezpośrednim sąsiedztwie (prawo lub lewo)?
seb

Ok, napotkałem nowy problem: jeśli obrócę ekran, stos wydaje się znowu pusty? Wygląda na to, że tracą orientację w stosunku do poprzednich pozycji ...
Jeroen,

1
@seb dzięki. Korzystałem z tego kodu, ale zagnieżdżone fragmenty są teraz możliwe, więc nie stanowią już problemu. :)
Garcon,

2

Zastąpienie fragmentów w przeglądarce jest dość zaangażowane, ale jest bardzo możliwe i może wyglądać super gładko. Po pierwsze, pozwól, aby sam przeglądarka obsługiwała usuwanie i dodawanie fragmentów. Dzieje się tak, gdy zastępujesz fragment wewnątrz SearchFragment, twoja przeglądarka zachowuje swoje widoki fragmentów. Otrzymujesz więc pustą stronę, ponieważ SearchFragment zostaje usunięty przy próbie jej zastąpienia.

Rozwiązaniem jest utworzenie detektora wewnątrz przeglądarki, który będzie obsługiwał zmiany dokonane poza nim, więc najpierw dodaj ten kod do dolnej części adaptera.

public interface nextFragmentListener {
    public void fragment0Changed(String newFragmentIdentification);
}

Następnie musisz utworzyć prywatną klasę w przeglądarce, która stanie się detektorem, gdy chcesz zmienić swój fragment. Na przykład możesz dodać coś takiego. Zauważ, że implementuje on właśnie utworzony interfejs. Tak więc za każdym razem, gdy wywołasz tę metodę, uruchomi ona kod wewnątrz klasy poniżej.

private final class fragmentChangeListener implements nextFragmentListener {


    @Override
    public void fragment0Changed(String fragment) {
        //I will explain the purpose of fragment0 in a moment
        fragment0 = fragment;
        manager.beginTransaction().remove(fragAt0).commit();

        switch (fragment){
            case "searchFragment":
                fragAt0 = SearchFragment.newInstance(listener);
                break;
            case "searchResultFragment":
                fragAt0 = Fragment_Table.newInstance(listener);
                break;
        }

        notifyDataSetChanged();
    }

Należy tu zwrócić uwagę na dwie główne rzeczy:

  1. fragAt0 jest fragmentem „elastycznym”. Może przyjmować dowolny podany typ fragmentu. Dzięki temu może stać się twoim najlepszym przyjacielem w zmianie fragmentu w pozycji 0 na pożądany fragment.
  2. Zwróć uwagę na detektory umieszczone w konstruktorze „newInstance (listener)”. Oto, jak wywołaszfrfragment0Changed (String newFragmentIdentification) `. Poniższy kod pokazuje, jak utworzyć detektor wewnątrz fragmentu.

    static nextFragmentListener listenerSearch;

        public static Fragment_Journals newInstance(nextFragmentListener listener){
                listenerSearch = listener;
                return new Fragment_Journals();
            }

Możesz nazwać zmianę wewnątrz swojego onPostExecute

private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{

    protected Void doInBackground(Void... params){
        .
        .//some more operation
        .
    }
    protected void onPostExecute(Void param){

        listenerSearch.fragment0Changed("searchResultFragment");
    }

}

Spowodowałoby to uruchomienie kodu w przeglądarce, aby przełączyć fragment w pozycji zero fragAt0 i stać się nowym searchResultFragment. Są jeszcze dwa małe elementy, które należy dodać do przeglądarki, zanim zacznie ona działać.

Jednym z nich byłaby metoda zastępowania getItem przeglądarki.

@Override
public Fragment getItem(int index) {

    switch (index) {
    case 0:
        //this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected.  

        if(fragAt0 == null){

            switch(fragment0){
            case "searchFragment":
                fragAt0 = FragmentSearch.newInstance(listener);
                break;
            case "searchResultsFragment":
                fragAt0 = FragmentSearchResults.newInstance(listener);
                break;
            }
        }
        return fragAt0;
    case 1:
        // Games fragment activity
        return new CreateFragment();

    }

Teraz bez tego ostatniego kawałka nadal byłbyś pusty. Niby kulawy, ale jest to niezbędna część viewPager. Musisz zastąpić metodę getItemPosition przeglądarki. Zwykle ta metoda zwróci POSITION_UNCHANGED, która mówi przeglądarce, aby wszystko pozostało takie samo, więc getItem nigdy nie zostanie wywołany w celu umieszczenia nowego fragmentu na stronie. Oto przykład czegoś, co możesz zrobić

public int getItemPosition(Object object)
{
    //object is the current fragment displayed at position 0.  
    if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){
        return POSITION_NONE;
    //this condition is for when you press back
    }else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){
        return POSITION_NONE;
    }
        return POSITION_UNCHANGED
}

Jak powiedziałem, kod bardzo się angażuje, ale w zasadzie musisz stworzyć niestandardowy adapter do swojej sytuacji. Rzeczy, o których wspomniałem, umożliwią zmianę fragmentu. Chyba wszystko zajmie dużo czasu, więc będę cierpliwy, ale wszystko to będzie miało sens. Warto poświęcić trochę czasu, ponieważ może stworzyć naprawdę zgrabną aplikację.

Oto model użytkowy do obsługi przycisku Wstecz. Umieszczasz to w swojej MainActivity

 public void onBackPressed() {
    if(mViewPager.getCurrentItem() == 0) {
        if(pagerAdapter.getItem(0) instanceof FragmentSearchResults){
            ((Fragment_Table) pagerAdapter.getItem(0)).backPressed();
        }else if (pagerAdapter.getItem(0) instanceof FragmentSearch) {
            finish();
        }
    }

Będziesz musiał utworzyć metodę o nazwie backPressed () wewnątrz FragmentSearchResults, która wywołuje fragment0changed. To w połączeniu z kodem, który pokazałem wcześniej, poradzi sobie z naciśnięciem przycisku Wstecz. Życzę powodzenia z kodem do zmiany przeglądarki. To wymaga dużo pracy i, o ile się przekonałem, nie ma żadnych szybkich adaptacji. Tak jak powiedziałem, w zasadzie tworzysz niestandardowy adapter przeglądarki i pozwalasz mu obsłużyć wszystkie niezbędne zmiany za pomocą detektorów


Dziękuję za to rozwiązanie. Ale czy miałeś jakieś problemy po obróceniu ekranu? Moja aplikacja ulega awarii, gdy obracam ekran i próbuję ponownie załadować inny fragment na tej samej karcie.
AnteGemini,

Minęło trochę czasu, odkąd stworzyłem program dla Androida, ale domyślam się, że istnieje pewna ilość danych, których już nie ma na twoją aplikację. Po obróceniu cała aktywność uruchamia się ponownie i może powodować awarie, jeśli spróbujesz załadować poprzedni stan bez wszystkich potrzebnych danych (powiedzmy z pakietu onPause, onResume)
user3331142

2

To jest mój sposób na osiągnięcie tego.

Przede wszystkim dodaj kartę Root_fragmentwewnętrzną viewPager, w której chcesz wdrożyć fragmentzdarzenie kliknięcia przycisku . Przykład;

@Override
public Fragment getItem(int position) {
  if(position==0)
      return RootTabFragment.newInstance();
  else
      return SecondPagerFragment.newInstance();
}

Przede wszystkim RootTabFragmentnależy uwzględnić FragmentLayoutzmiany fragmentów.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/root_frame"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
</FrameLayout>

Następnie w środku RootTabFragment onCreateViewzaimplementuj fragmentChangedla swojegoFirstPagerFragment

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, FirstPagerFragment.newInstance()).commit();

Następnie zaimplementuj onClickzdarzenie dla przycisku wewnątrz FirstPagerFragmenti ponownie dokonaj zmiany fragmentu.

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, NextFragment.newInstance()).commit();

Mam nadzieję, że to ci pomoże.


1

Znalazłem proste rozwiązanie, które działa dobrze, nawet jeśli chcesz dodać nowe fragmenty na środku lub zastąpić aktualny fragment. W moim rozwiązaniu powinieneś zastąpićgetItemId() który powinien zwracać unikalny identyfikator dla każdego fragmentu. Domyślnie nie pozycjonuj.

Jest to:

public class DynamicPagerAdapter extends FragmentPagerAdapter {

private ArrayList<Page> mPages = new ArrayList<Page>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

public DynamicPagerAdapter(FragmentManager fm) {
    super(fm);
}

public void replacePage(int position, Page page) {
    mPages.set(position, page);
    notifyDataSetChanged();
}

public void setPages(ArrayList<Page> pages) {
    mPages = pages;
    notifyDataSetChanged();
}

@Override
public Fragment getItem(int position) {
    if (mPages.get(position).mPageType == PageType.FIRST) {
        return FirstFragment.newInstance(mPages.get(position));
    } else {
        return SecondFragment.newInstance(mPages.get(position));
    }
}

@Override
public int getCount() {
    return mPages.size();
}

@Override
public long getItemId(int position) {
    // return unique id
    return mPages.get(position).getId();
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment fragment = (Fragment) super.instantiateItem(container, position);
    while (mFragments.size() <= position) {
        mFragments.add(null);
    }
    mFragments.set(position, fragment);
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    mFragments.set(position, null);
}

@Override
public int getItemPosition(Object object) {
    PagerFragment pagerFragment = (PagerFragment) object;
    Page page = pagerFragment.getPage();
    int position = mFragments.indexOf(pagerFragment);
    if (page.equals(mPages.get(position))) {
        return POSITION_UNCHANGED;
    } else {
        return POSITION_NONE;
    }
}
}

Wskazówka: W tym przykładzie FirstFragmenti SecondFragmentrozszerza klasa abstrakcyjna PageFragment, która ma metody getPage().


1

Robię coś podobnego do wize, ale w mojej odpowiedzi możesz zmieniać między dwoma fragmentami, kiedy tylko chcesz. A z odpowiedzią na duże problemy mam pewne problemy przy zmianie orientacji ekranu i takie rzeczy. To wygląda jak PagerAdapter:

    public class MyAdapter extends FragmentPagerAdapter
{
    static final int NUM_ITEMS = 2;
    private final FragmentManager mFragmentManager;
    private Fragment mFragmentAtPos0;
     private Map<Integer, String> mFragmentTags;
     private boolean isNextFragment=false;

    public MyAdapter(FragmentManager fm)
    {
        super(fm);
        mFragmentManager = fm;
         mFragmentTags = new HashMap<Integer, String>();
    }

    @Override
    public Fragment getItem(int position)
    {
        if (position == 0)
        {


            if (isPager) {
                mFragmentAtPos0 = new FirstPageFragment();
            } else {
                mFragmentAtPos0 = new NextFragment();
            }
            return mFragmentAtPos0;
        }
        else
            return SecondPageFragment.newInstance();
    }

    @Override
    public int getCount()
    {
        return NUM_ITEMS;
    }


 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object obj = super.instantiateItem(container, position);
        if (obj instanceof Fragment) {
            // record the fragment tag here.
            Fragment f = (Fragment) obj;
            String tag = f.getTag();
            mFragmentTags.put(position, tag);
        }
        return obj;
    }


    public void onChange(boolean isNextFragment) {

        if (mFragmentAtPos0 == null)
            mFragmentAtPos0 = getFragment(0);
        if (mFragmentAtPos0 != null)
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();


        if (!isNextFragment) {
            mFragmentAtFlashcards = new FirstPageFragment();
        } else {
            mFragmentAtFlashcards = new NextFragment();
        }

        notifyDataSetChanged();


    }


    @Override
    public int getItemPosition(Object object)
    {
        if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
         if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }


    public Fragment getFragment(int position) {
        String tag = mFragmentTags.get(position);
        if (tag == null)
            return null;
        return mFragmentManager.findFragmentByTag(tag);
    }
}

Listener zaimplementowałem w działaniu kontenera adaptera, aby umieścić go we fragmencie podczas dołączania, jest to działanie:

    public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {

//...

  @Override
    public void onChange(boolean isNextFragment) {
        if (pagerAdapter != null)
            pagerAdapter.onChange(isNextFragment);


    }

//...
}

Następnie we fragmencie umieszczenie detektora po dołączeniu wywołania:

public class FirstPageFragment extends Fragment{


private ChangeFragmentListener changeFragmentListener;


//...
 @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        changeFragmentListener = ((PagerContainerActivity) activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        changeFragmentListener = null;
    }
//...
//in the on click to change the fragment
changeFragmentListener.onChange(true);
//...
}

I wreszcie słuchacz:

public interface changeFragmentListener {

    void onChange(boolean isNextFragment);

}

1

Postępowałem zgodnie z odpowiedziami @wize i @mdelolmo i dostałem rozwiązanie. Dzięki Tony. Ale nieco dostroiłem te rozwiązania, aby poprawić zużycie pamięci.

Problemy, które zaobserwowałem:

Zapisują instancję, Fragmentktórej zastąpiono. W moim przypadku jest to Fragment, który trzyma MapViewi uważam, że jest kosztowny. Tak więc utrzymuję FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)zamiast Fragmentsiebie.

Oto moja implementacja.

  public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {

    private SwitchFragListener mSwitchFragListener;
    private Switch mToggle;
    private int pagerAdapterPosChanged = POSITION_UNCHANGED;
    private static final int TOGGLE_ENABLE_POS = 2;


    public DemoCollectionPagerAdapter(FragmentManager fm, Switch toggle) {
        super(fm);
        mToggle = toggle;

        mSwitchFragListener = new SwitchFragListener();
        mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mSwitchFragListener.onSwitchToNextFragment();
            }
        });
    }

    @Override
    public Fragment getItem(int i) {
        switch (i)
        {
            case TOGGLE_ENABLE_POS:
                if(mToggle.isChecked())
                {
                    return TabReplaceFragment.getInstance();
                }else
                {
                    return DemoTab2Fragment.getInstance(i);
                }

            default:
                return DemoTabFragment.getInstance(i);
        }
    }

    @Override
    public int getCount() {
        return 5;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "Tab " + (position + 1);
    }

    @Override
    public int getItemPosition(Object object) {

        //  This check make sures getItem() is called only for the required Fragment
        if (object instanceof TabReplaceFragment
                ||  object instanceof DemoTab2Fragment)
            return pagerAdapterPosChanged;

        return POSITION_UNCHANGED;
    }

    /**
     * Switch fragments Interface implementation
     */
    private final class SwitchFragListener implements
            SwitchFragInterface {

        SwitchFragListener() {}

        public void onSwitchToNextFragment() {

            pagerAdapterPosChanged = POSITION_NONE;
            notifyDataSetChanged();
        }
    }

    /**
     * Interface to switch frags
     */
    private interface SwitchFragInterface{
        void onSwitchToNextFragment();
    }
}

Link do wersji demo tutaj .. https://youtu.be/l_62uhKkLyM

Do celów demonstracyjnych wykorzystano 2 fragmenty TabReplaceFragmenti DemoTab2Fragmentpozycję 2. We wszystkich innych przypadkach używam DemoTabFragmentinstancji.

Wyjaśnienie:

Przechodzę Switchz działania do DemoCollectionPagerAdapter. W oparciu o stan tego przełącznika wyświetlimy poprawny fragment. Kiedy kontrola przełącznik zmienia się, dzwonię w SwitchFragListener„s onSwitchToNextFragmentmetody, gdzie mam zmianę wartości pagerAdapterPosChangedzmiennej do POSITION_NONE. Sprawdź więcej o POSITION_NONE . Spowoduje to unieważnienie getItem i mam logikę, aby utworzyć tam odpowiedni fragment. Przepraszamy, jeśli wyjaśnienie jest nieco niechlujne.

Jeszcze raz wielkie podziękowania dla @wize i @mdelolmo za oryginalny pomysł.

Mam nadzieję, że to jest pomocne. :)

Daj mi znać, jeśli ta implementacja ma jakieś wady. To będzie bardzo pomocne dla mojego projektu.


0

po badaniach znalazłem rozwiązanie z krótkim kodem. najpierw stwórz publiczną instancję na fragmencie i po prostu usuń swój fragment na onSaveInstanceState, jeśli fragment nie jest odtwarzany po zmianie orientacji.

 @Override
public void onSaveInstanceState(Bundle outState) {
    if (null != mCalFragment) {
        FragmentTransaction bt = getChildFragmentManager().beginTransaction();
        bt.remove(mFragment);
        bt.commit();
    }
    super.onSaveInstanceState(outState);
}
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.