Замініть фрагмент всередині ViewPager


268

Я намагаюся використовувати Fragment з ViewPagerвикористанням FragmentPagerAdapter. Що я хочу досягти - це замінити фрагмент, розміщений на першій сторінці ViewPager, на інший.

Пейджер складається з двох сторінок. Перший - це FirstPagerFragment, другий - SecondPagerFragment. Натиснувши на кнопку першої сторінки. Я хотів би замінити FirstPagerFragmentNextFragment.

Нижче є мій код.

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

        }
    }
}

... і ось файли 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>

Тепер проблема ... який ідентифікатор я повинен використовувати

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

?

Якщо я використовую R.id.first_fragment_root_id, заміна працює, але переглядач ієрархії показує дивну поведінку, як показано нижче.

На початку ситуація така

після заміни ситуація є

Як ви бачите, що щось не так, я очікую, що після того, як я заміню фрагмент, знайду такий самий стан, як на першому малюнку.


Ви якось генерували цю схему компонування чи це робили вручну?
Jeroen Vannevel

@Noodles: мені потрібно зробити те саме. Ваше запитання створило довгу нитку з безліччю різних відповідей. Що найкраще працювало в підсумку? Велике дякую!
нарб

1
@JeroenVannevel ви, можливо, це вже зрозуміли, але ця схема компонування є developer.android.com/tools/performance/hierarchy-viewer/…
Ankit Popli

Вам потрібна проблема, потрібна ваша допомога stackoverflow.com/questions/40149039/…
Альберт

Відповіді:


162

Є ще одне рішення, яке не потребує зміни вихідного коду ViewPagerта FragmentStatePagerAdapter, і воно працює зFragmentPagerAdapter базовим класом, використовуваним автором.

Я хотів би почати з відповіді на запитання автора про те, який ідентифікатор він повинен використовувати; це ідентифікатор контейнера, тобто ідентифікатор самого пейджера перегляду. Однак, як ви, напевно, самі помітили, використання цього ідентифікатора у коді нічого не спричиняє. Я поясню, чому:

Перш за все, щоб зробити ViewPagerперенаселення сторінок, вам потрібно зателефонувати, notifyDataSetChanged()що знаходиться в базовому класі вашого адаптера.

По-друге, ViewPagerвикористовує getItemPosition()абстрактний метод, щоб перевірити, які сторінки слід знищити та які слід зберігати. Виконання цієї функції за замовчуванням завжди повертається POSITION_UNCHANGED, що призводить ViewPagerдо збереження всіх поточних сторінок і, отже, не приєднання нової сторінки. Таким чином, щоб зробити роботу із заміни фрагментів, getItemPosition()потрібно змінити ваш адаптер і повернутисяPOSITION_NONE коли його викликають зі старим, приховати, фрагмент як аргумент.

Це також означає, що ваш адаптер завжди повинен знати про те, який фрагмент повинен відображатися в положенні 0, FirstPageFragmentабо NextFragment. Один із способів цього - забезпечити слухача під час створення FirstPageFragment, який буде називатися, коли настав час переключити фрагменти. Я думаю, що це добре, щоб ваш адаптер фрагментів обробляв усі фрагменти комутації та дзвінки до ViewPagerта FragmentManager.

По-третє, FragmentPagerAdapterкешує використані фрагменти ім'ям, яке походить від позиції, тому, якщо в позиції 0 був фрагмент, він не буде замінений, навіть якщо клас новий. Є два рішення, але найпростішим є використання remove()функції FragmentTransaction, яка також видалить її тег.

Це було багато тексту, ось код, який повинен працювати у вашому випадку:

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

Сподіваюся, це допоможе комусь!


24
працює і для мене, але як реалізувати кнопку "назад", щоб показати перший фрагмент?
користувач1159819

6
Чудове пояснення, але чи можете ви опублікувати повний вихідний код цього, зокрема класів FirstPageFragment та SecondPageFragment.
georgiecasey

6
Як додати FirstPageFragmentListener до групи в newInstance ()?
miguel

4
Чи можете ви додати код, де ви обробляєте FirstPageFragment.newInstance()параметр слухача?
MiguelHincapieC

6
Це працює для мене лише в тому випадку, якщо я під час видалення фрагмента я викликаю commitNow () замість commit (). Не впевнений, чому мій варіант використання відрізняється, але, сподіваюся, це заощадить когось деякий час.
Ян Лавджой

37

Станом на 13 листопада 2012 року, відновлення фрагментів у ViewPager, здається, стало набагато простіше. Google випустив Android 4.2 з підтримкою вкладених фрагментів, а також він підтримується в новій бібліотеці підтримки Android v11, так що це буде працювати повністю до 1.6

Це дуже подібне до звичайного способу заміни фрагмента, за винятком того, що ви використовуєте getChildFragmentManager. Це здається, що працює, за винятком того, що вкладений фрагмент рюкзака не вискакує, коли користувач натискає кнопку назад. Відповідно до рішення в цьому пов'язаному питанні, вам потрібно вручну викликати popBackStackImmediate () на дочірньому менеджері фрагмента. Отже, вам потрібно перекрити onBackPress () активності ViewPager, де ви отримаєте поточний фрагмент ViewPager і викликаєте на ньому getChildFragmentManager (). PopBackStackImmediate ().

Отримання фрагмента, який наразі відображається, також трохи хиткий, я використав це брудне рішення "android: switch: VIEWPAGER_ID: INDEX", але ви також можете самостійно відслідковувати всі фрагменти ViewPager, як пояснено у другому рішенні на цій сторінці .

Отже, ось мій код для ViewPager з 4 ListView з детальним поданням, показаним у ViewPager, коли користувач натискає рядок, і при натисканні кнопки "назад". Я спробував включити лише відповідний код заради стислості, тому залиште коментар, якщо ви хочете, щоб повне додаток було завантажено на 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
Чи вдасться вам завантажити код у github? Дякую.
Гаутем

4
@georgiecasey: я завантажив API 17, Android 4.2, що дозволило мені почати використовувати getChildFragmentManager (). Але мені, мабуть, щось не вистачає у вашому рішенні, оскільки я зараз перекриваю старі + нові фрагменти на екрані. Примітка. Я намагаюся використовувати ваше рішення спільно з кодом прикладу Google TabsAdapter від developer.android.com/reference/android/support/v4/view/… . Дякуємо за будь-які пропозиції та / або довідковий код.
gcl1

27
На що посилається R.id.products_list_linear? Будь ласка, посилання на ваш код, як було запропоновано.
howettl

1
@howettl посилання ID на зовнішній член фрагмента. Ключовим для вирішення цієї проблеми є використання вкладених фрагментів (таким чином вам не доведеться маніпулювати адаптером переглядача).
Індрек Кюе

2
Це коли-небудь встигало до Github?
Барух

32

На підставі відповіді @wize, яку я вважав корисною та елегантною, я міг досягти того, чого я хотів частково, тому що я хотів, щоб кабель повернувся до першого заміненого фрагмента. Я досяг цього, трохи змінивши його код.

Це буде 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();
}

Щоб виконати заміну, просто визначте статичне поле типу CalendarPageFragmentListenerта ініціалізуйте за допомогою newInstanceметодів відповідних фрагментів та зателефонуйте FirstFragment.pageListener.onSwitchToNextFragment()або NextFragment.pageListener.onSwitchToNextFragment()респікстевелі.


Як виконувати заміну? неможливо ініціалізувати Слухач, не переставляючи метод onSwitchToNext.
Леандрос

1
ви не втрачаєте стан, збережений у mFragmentAtPos0, якщо ви повертати пристрій?
seb

@seb, я фактично вдосконалив це рішення, щоб зберегти mFragmentAtPos0посилання під час збереження стану активності. Це не найелегантніше рішення, але працює.
mdelolmo

Ви можете бачити мою відповідь . Він замінює фрагменти і повертається назад до першого.
Lyd

@mdelolmo Чи можете ви поглянути на це питання про ViewPager? Дякую, stackoverflow.com/questions/27937250/…
Hoa Vu

21

Я реалізував рішення для:

  • Динамічна заміна фрагмента всередині вкладки
  • Обслуговування історії на вкладці
  • Робота зі змінами орієнтації

Підказки для досягнення цього полягають у наступному:

  • Використовуйте метод notifyDataSetChanged (), щоб застосувати заміну фрагмента
  • Використовуйте диспетчер фрагментів лише для заднього етапу, а не для заміни фрагмента
  • Ведення історії за допомогою шаблону пам’яті (стека)

Код адаптера такий:

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

Перший раз, коли ви додаєте всі вкладки, нам потрібно викликати метод createHistory (), щоб створити початкову історію

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

Кожен раз, коли ви хочете замінити фрагмент на певну вкладку, яку ви закликаєте: замінити (остаточна позиція int, остаточний фрагмент ClassClass, заключні аргументи групи)

/* 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();

При натисканні на спині потрібно викликати метод 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();
}

Рішення працює із штриховою панеллю дій та з пальцем жестом.


це чудово! ви могли б покласти його на github?
Нікрам

Ця реалізація є найбільш чистою з усіх поданих тут і стосується кожного окремого питання. Це має бути прийнята відповідь.
dazedviper

У когось були проблеми з цією реалізацією? У мене їх 2. 1. перша спинка не реєструється - мені потрібно двічі клацнути назад, щоб зареєструватися. 2. Після того, як я натискаю назад (двічі) на вкладці a і
переходжу

7

tl; dr: використовуйте фрагмент хоста, який відповідає за заміну розміщеного вмісту та відстежує історію зворотної навігації (наприклад, у веб-переглядачі).

Оскільки ваш випадок використання складається з фіксованої кількості вкладок, моє рішення працює добре: Ідея полягає в тому, щоб заповнити ViewPager екземплярами спеціального класу HostFragment, який може замінити розміщений вміст і зберігає власну історію зворотної навігації. Щоб замінити розміщений фрагмент, ви телефонуєте методу 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();
    }
}

Все, що робить цей метод - це замінити макет кадру на id R.id.hosted_fragmentна фрагмент, наданий методу.

Перевірте мій підручник з цієї теми, щоб отримати детальнішу інформацію та повний робочий приклад на GitHub!


Хоча це виглядає незграбно, я думаю, що насправді найкращий (з точки зору структури та простоти обслуговування) спосіб зробити це.
Сіра Лам

В кінцевому підсумку я голову замикаю на NPE. : / Не вдається знайти вид, який слід замінити
Фаріз Факел

6

Деякі з представлених рішень мені дуже допомогли частково вирішити проблему, але все ще є одна важлива річ, яка в деяких випадках створила несподівані винятки та вміст чорних сторінок замість вмісту фрагментів.

Вся справа в тому, що клас FragmentPagerAdapter використовує ідентифікатор елемента для зберігання кешованих фрагментів у FragmentManager . З цієї причини вам потрібно перекрити також метод getItemId (int position), щоб він повертався, наприклад, позицією для сторінок вищого рівня та позицією 100 + для сторінок деталей. В іншому випадку створений раніше фрагмент верхнього рівня буде повернуто з кеша замість фрагмента рівня деталізації.

Крім того, я ділюсь тут повним прикладом того, як реалізувати подію на вкладки із фрагментами на сторінках Fragment за допомогою ViewPager та кнопок вкладки за допомогою RadioGroup, що дозволяє замінювати сторінки верхнього рівня детальними сторінками, а також підтримує кнопку назад. Ця реалізація підтримує лише один рівень зворотного складання (список елементів - деталі деталей), але реалізація багаторівневої зворотної стеки є простою. Цей приклад працює досить добре у звичайних випадках, за винятком того, що це перекидання NullPointerException у випадку, коли ви переходите, наприклад, на другу сторінку, змінюєте фрагмент першої сторінки (поки не видно) та повертаєтесь назад на першу сторінку. Я опублікую рішення цього питання, як тільки зрозумію:

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

Хм, є ще одна проблема з цим кодом. Коли я хочу запустити діяльність з фрагменту, який було видалено та додано погоджуватись, я отримую помилку: "Стан збереження помилок: активний NewFirstFragment {405478a0} очистив індекс: -1" (повний слід стека: nopaste.info/547a23dfea.html ). Дивлячись на джерела для android, я розумію, що це має щось спільне з backstack, але я не знаю, як це виправити поки що. Хтось має підказку?
Blackhex

2
Класно, використовуючи <code> mFragmentManager.beginTransaction (). Detach (old_fragment) .attach (фрагмент) .commit (); </code> замість <pre> mFragmentManager.beginTransaction (). SetTransition (FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .remove ( old_fragment) .add (mContainer.getId (), фрагмент) .commit (); </pre> у прикладі вище, здається, виправляє обидві проблеми :-).
Blackhex

1
Якщо ви використовуєте цей метод, переконайтеся, що ви не дозволяєте Android відтворювати свою активність під час обертання пристрою. В іншому випадку PagerAdapter буде створено заново, і ви втратите свої mBackFragments. Це також серйозно заплутає FragmentManager. Використання BackStack дозволяє уникнути цієї проблеми за рахунок більш складної реалізації PagerAdapter (тобто ви не можете успадкувати від FragmentPagerAdapter.)
Норман

6

Я створив ViewPager з 3 елементами та 2 піделементами для індексів 2 і 3, і ось, що я хотів зробити.

введіть тут опис зображення

Я реалізував це за допомогою попередніх питань та відповідей від StackOverFlow, і ось посилання.

ViewPagerChildFragments


Якщо проект обертається, проект не вдасться.
Дорі

6

Для того, щоб замінити фрагмент всередині ViewPagerви можете перемістити вихідні коди ViewPager, PagerAdapterі FragmentStatePagerAdapterкласи в свій проект і додайте наступний код.

в ViewPager:

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

у 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()гарантує, що після наступного виклику getItem()цього повернення newFragment getFragmentPosition()повертає положення фрагмента у вашому адаптері.

Тепер для заміни фрагментів заклик

mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);

Якщо вас цікавить приклад проекту, запитайте мене про джерела.


Як мають виглядати методи getFragmentPosition () та handleGetItemInbalidated ()? Я не можу оновити getItem (), щоб повернути NewFragment .. Будь ласка, допоможіть ..

Привіт! Ви можете знайти пробний проект за цим посиланням files.mail.ru/eng?back=%2FEZU6G4
AndroidTeam На Mail.Ru

1
Ваш тестовий проект не вдається, якщо ви поверніть пристрій.
seb

1
@ AndroidTeamAtMail.Ru, мабуть, термін дії посилання минув. Будь ласка, поділіться кодом
Сонячний

Ви можете бачити мою відповідь . Він замінює фрагменти і повертається назад до першого.
Lyd

4

Відмінно працює з рішенням AndroidTeam, однак я виявив, що мені потрібна здатність повертатися назад, як, FrgmentTransaction.addToBackStack(null) але просто додавання цього лише призведе до заміни Фрагменту без повідомлення про ViewPager. Поєднання запропонованого рішення з цим незначним вдосконаленням дозволить вам повернутися до попереднього стану, просто перекресливши onBackPressed()метод діяльності. Найбільшим недоліком є ​​те, що він повернеться один за одним, що може призвести до декількох натискань назад

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

Сподіваюся, що це комусь допоможе.

Крім того, наскільки це getFragmentPosition()іде значно getItem()навпаки. Ви знаєте, які фрагменти куди йдуть, просто переконайтеся, що ви повернете правильне положення, в якому воно буде. Ось приклад:

    @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

У вашому onCreateViewметоді, containerнасправді єViewPager екземпляр.

Отже, просто дзвоню

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

змінить поточний фрагмент у вашому ViewPager.


1
Через години пошуку мені потрібен був цей один рядок коду, щоб усі мої вкладки справно працювали. vpViewPager.setCurrentItem(1);. Я переглянув приклад кожної людини, щоразу нічого не сталося, поки я нарешті не дійшов до вашого. Дякую.
Брендон

1
розум здутий ... це дивовижно !! Я не усвідомлював, що змінна контейнера є самим переглядачем. Цю відповідь слід вивчити спочатку перед усіма іншими тривалими. Крім того, в конструктор фрагментів немає передачі змінних слухачів, що спрощує всю реалізацію, коли Android автоматично відтворює фрагменти, використовуючи конструктор no-arg за замовчуванням при зміні конфігурації.
Кевін Лі

3
це не просто змінюється на існуючу вкладку? як це пов’язано із заміною фрагментів?
бехеліт

2
Погодьтеся з @behelit, це не відповідає на запитання, воно просто перенесе ViewPagerна іншу сторінку, вона не замінить жодного фрагмента.
Йоанна Херкует

2

Ось моє відносно просте рішення цієї проблеми. Ключі цього рішення полягають у використанні FragmentStatePagerAdapterзамість того, FragmentPagerAdapterяк перший видалить невикористані фрагменти для вас, тоді як пізніші ще зберігають їхні екземпляри. Друге - використання POSITION_NONEв getItem (). Я використовував простий Список для відстеження моїх фрагментів. Моя вимога полягала в тому, щоб весь список фрагментів одразу замінити новим списком, але наведене нижче можна було легко змінити, щоб замінити окремі фрагменти:

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

Я також прийняв рішення, яке працює з Stacks . Це більш модульний підхід, тому вам не потрібно вказувати кожен фрагмент та детальний фрагмент у вашому FragmentPagerAdapter. Він побудований на вершині прикладу від ActionbarSherlock, який отримує, якщо я працюю з програми 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) {
    }

}

Додайте це для функцій кнопок повернення у вашій MainActivity:

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

Якщо ви хочете зберегти стан фрагмента, коли він буде видалений. Нехай ваш фрагмент реалізує інтерфейс, що SaveStateBundleповертає у функцію пакет із вашим станом збереження. Отримайте пакет після інстанції за допомогою this.getArguments().

Ви можете створити інстанцію так:

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

працює аналогічно, якщо ви хочете додати фрагмент у верхній частині стека вкладок. Важливо : Я думаю, це не спрацює, якщо ви хочете мати дві екземпляри одного класу поверх двох вкладок. Я вирішив це рішення швидко разом, тому можу поділитися ним лише, не надаючи йому жодного досвіду.


Я отримую виняток з нульовим покажчиком, коли натискаю кнопку назад, і мій замінений фрагмент також не видно. Тож, можливо, я роблю щось не так: яким повинен бути правильний виклик, щоб правильно змінити фрагменти?
Jeroen

Оновлення. Більшість матеріалів виправлено, однак, якщо натиснути кнопку назад після додавання нового фрагмента, а потім перейти на іншу вкладку та назад, я отримую виняток з нульовим покажчиком. Отже, у мене є 2 питання: 1: як я можу видалити історію вкладки і переконатися, що лише останній фрагмент (у верхній частині стека) все ще присутній? 2: як я можу позбутися виключення з nullpointer?
Jeroen

@ Jeroen 1: ти можеш отримати правильний стек з mTabStackMap, а потім видалити кожен об'єкт TabInfo, що лежить під вершиною стека, 2: дуже важко сказати, задати питання з стек-трасом, то я міг би спробувати дізнатися, чи Ви впевнені, що це не збій у вашому коді? Чи трапляється це також, якщо ви переходите на вкладку (а потім назад), що знаходиться у безпосередньому сусідстві (праворуч чи ліворуч)?
seb

Гаразд, я зіткнувся з новою проблемою: якщо я обертаю екран, то, здається, стек знову порожній? Здається, вільне відстеження попередніх предметів ...
Єроен

1
@seb спасибі Я використовував цей код, але вкладені фрагменти тепер можливі, тому більше не виникає проблем. :)
Гаркон

2

Заміна фрагментів у панелі перегляду досить задіяна, але це дуже можливо і може виглядати надзвичайно гладко. По-перше, потрібно дозволити самому оглядачеві керувати видаленням та додаванням фрагментів. Що відбувається, коли ви замінюєте фрагмент всередині SearchFragment, ваш панель перегляду зберігає свої представлення фрагмента. Таким чином, ви отримуєте порожню сторінку, оскільки SearchFragment видаляється при спробі заміни.

Рішення полягає в тому, щоб створити слухача всередині свого переглядача, який буде обробляти зміни, внесені поза ним, тому спочатку додайте цей код у нижню частину адаптера.

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

Тоді вам потрібно створити приватний клас у своєму оглядачі, який стане слухачем, коли ви хочете змінити фрагмент. Наприклад, ви можете додати щось подібне. Зауважте, що він реалізує щойно створений інтерфейс. Тому щоразу, коли ви викликаєте цей метод, він запускатиме код усередині класу нижче.

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

Тут слід зазначити дві основні речі:

  1. fragAt0 - "гнучкий" фрагмент. Він може мати будь-який тип фрагмента, який ви йому надаєте. Це дозволяє йому стати вашим найкращим другом у зміні фрагмента в положенні 0 на потрібний вам фрагмент.
  2. Зверніть увагу на слухачів, які розміщені в конструкторі "newInstance (listener)". Ось як ви будете викликатиfrafragment0Changed (String newFragmentIdentification) `. Наступний код показує, як ви створюєте слухача всередині свого фрагмента.

    статичний nextFragmentListener listenerSearch;

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

Ви можете зателефонувати на зміну всередині свого 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");
    }

}

Це призведе до того, що код у вашому панелі перегляду переключить фрагмент у нульовому положенні fragAt0, щоб він став новим searchResultFragment. Є ще два невеликих шматочки, які вам потрібно буде додати до оглядача, перш ніж він стане функціональним.

Можливо, це було б методом переосмислення getItem переглядача.

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

    }

Тепер без цього останнього твору ви все одно отримаєте порожню сторінку. Вигляд кульгавого, але це важлива частина переглядуПаджер. Ви повинні перекрити метод getItemPosition проглядача. Зазвичай цей метод повертає POSITION_UNCHANGED, який вказує віконцеві зберігати все те саме, і таким чином getItem ніколи не буде викликаний, щоб розмістити новий фрагмент на сторінці. Ось приклад того, що ви могли зробити

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
}

Як я вже говорив, код дуже сильно задіяний, але вам потрібно створити спеціальний адаптер для вашої ситуації. Речі, про які я згадав, дозволять змінити фрагмент. Можливо, знадобиться багато часу, щоб все замочити, щоб я був терплячим, але це буде мати сенс. Цілком варто витратити час, оскільки це може зробити дійсно гладкий додаток.

Ось такий самородок для обробки кнопки "назад". Ви помістите це всередину своєї 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();
        }
    }

Вам потрібно буде створити метод, який називається backPress () всередині FragmentSearchResults, який викликає fragment0changed. Це в тандемі з кодом, який я показав раніше, буде працювати з натисканням кнопки "назад". Удачі зі своїм кодом, щоб змінити вікно перегляду. Це вимагає багато роботи, і, наскільки я виявив, немає швидких адаптацій. Як я вже говорив, ви в основному створюєте спеціальний адаптер переглядача і дозволяєте йому обробляти всі необхідні зміни за допомогою слухачів


Дякую за таке рішення. Але чи виникли у вас проблеми після обертання екрана. Мій додаток виходить з ладу, коли я обертаю екран і намагаюся перезавантажити ще один фрагмент на тій же вкладці.
AnteGemini

Минув деякий час, оскільки я робив Android-розробник, але я здогадуюсь, що є частина даних, на яку вже не розраховує ваш додаток. Коли ви обертаєтесь, вся активізація перезапускається і може спричинити збої, якщо ви спробуєте перезавантажити попередній стан без усіх необхідних даних (скажімо, із пакета в onPause, onResume)
user3331142

2

Це мій спосіб досягти цього.

Перш за все додайте Root_fragmentвнутрішню viewPagerвкладку, в яку потрібно реалізувати fragmentподію натискання кнопки . Приклад;

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

Перш за все, RootTabFragmentслід включити FragmentLayoutдля зміни фрагмента.

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

Потім, всередині RootTabFragment onCreateView, реалізуйте fragmentChangeдля свогоFirstPagerFragment

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

Після цього реалізуйте onClickподію для вашої кнопки всередині FirstPagerFragmentта знову зробіть зміни фрагменту.

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

Сподіваюся, це допоможе тобі хлопець.


1

Я знайшов просте рішення, яке добре працює, навіть якщо ви хочете додати нові фрагменти посередині або замінити поточний фрагмент. У моєму рішенні ви повинні перекритиgetItemId() який повинен повернути унікальний ідентифікатор для кожного фрагмента. Не зазначається за замовчуванням.

Є це:

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

Примітка. У цьому прикладі FirstFragmentі SecondFragmentрозширюється абстрактний клас PageFragment клас, який має метод getPage().


1

Я роблю щось подібне до wize, але у моїй відповіді yo може змінюватися між двома фрагментами, коли вам захочеться І з відповіддю wize у мене є деякі проблеми, коли змінюється орієнтація екрана на такі речі. Це 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);
    }
}

Слухач, який я реалізував у активності контейнера адаптера, щоб поставити його до фрагмента при приєднанні, це діяльність:

    public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {

//...

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


    }

//...
}

Потім у фрагменті, що ставить слухача, коли додає його виклик:

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);
//...
}

І нарешті слухач:

public interface changeFragmentListener {

    void onChange(boolean isNextFragment);

}

1

Я дотримувався відповідей @wize та @mdelolmo, і я отримав рішення. Спасибі Тон. Але я трохи налаштував ці рішення, щоб поліпшити споживання пам’яті.

Проблеми, які я спостерігав:

Вони зберігають екземпляр, Fragmentякий замінюється. У моєму випадку це фрагмент, який тримається, MapViewі я вважав, що це дорого. Отже, я підтримую FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)замість Fragmentсебе.

Ось моя реалізація.

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

Демонстраційне посилання тут .. https://youtu.be/l_62uhKkLyM

Для демонстраційної мети використовували 2 фрагменти TabReplaceFragmentі DemoTab2Fragmentв позиції два. У всіх інших випадках я використовую DemoTabFragmentекземпляри.

Пояснення:

Я переходжу Switchвід Діяльності до DemoCollectionPagerAdapter. На основі стану цього перемикача ми відобразимо правильний фрагмент. Коли перевірка комутатора змінюється, я викликаю метод SwitchFragListener's onSwitchToNextFragment, де я змінюю значення pagerAdapterPosChangedзмінної на POSITION_NONE. Дізнайтеся більше про POSITION_NONE . Це призведе до недійсності getItem, і я маю логіку, щоб інстанціювати потрібний фрагмент там. Вибачте, якщо пояснення трохи безладно.

Ще раз велике спасибі @wize та @mdelolmo за оригінальну ідею.

Сподіваюся, це корисно. :)

Повідомте мене, чи має ця реалізація недоліки. Це буде дуже корисно для мого проекту.


0

після дослідження я знайшов рішення з коротким кодом. Перш за все, створіть публічний екземпляр на фрагменті та просто видаліть його фрагмент на OnSaveInstanceState, якщо фрагмент не відтворить при зміні орієнтації.

 @Override
public void onSaveInstanceState(Bundle outState) {
    if (null != mCalFragment) {
        FragmentTransaction bt = getChildFragmentManager().beginTransaction();
        bt.remove(mFragment);
        bt.commit();
    }
    super.onSaveInstanceState(outState);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.