Заменить фрагмент внутри ViewPager

268

Я пытаюсь использовать фрагмент с ViewPagerиспользованием FragmentPagerAdapter. Чего я хочу добиться, так это заменить фрагмент, расположенный на первой странице ViewPager, другим.

Пейджер состоит из двух страниц. Первый - FirstPagerFragmentвторой SecondPagerFragment. Нажав на кнопку первой страницы. Я хотел бы заменить FirstPagerFragmentна NextFragment.

Ниже приведен мой код.

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, замена работает, но Hierarchy Viewer показывает странное поведение, как показано ниже.

В начале ситуация

после замены ситуация

Как вы видите, что-то не так, я ожидаю найти то же состояние, что и на первом рисунке, после замены фрагмента.

лапша
источник
Вы как-то сгенерировали эту схему расположения или сделали это вручную?
Йерун Ванневел
@Noodles: мне нужно сделать то же самое. Ваш вопрос породил длинную ветку с множеством разных ответов. Что сработало лучше всего в конце? Большое спасибо!
Нарб
1
@JeroenVannevel Вы, возможно, уже поняли это, но эта схема компоновки - developer.android.com/tools/performance/hierarchy-viewer/…
Анкит Попли,
возникли проблемы, нужна ваша помощь stackoverflow.com/questions/40149039/…
Альберт

Ответы:

162

Есть еще одно решение, которое не требует модификации исходного кода ViewPagerи FragmentStatePagerAdapter, и оно работает с FragmentPagerAdapterбазовым классом, используемым автором.

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

Прежде всего, чтобы сделать 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();
}

Надеюсь, это кому-нибудь поможет!

Wize
источник
24
у меня тоже работает, но как реализовать кнопку назад, чтобы показать первый фрагмент?
user1159819
6
Отличное объяснение, но не могли бы вы опубликовать полный исходный код этого, в частности классов FirstPageFragment и SecondPageFragment.
Георгиевская
6
Как добавить FirstPageFragmentListener в Bundle в newInstance ()?
Мигель
4
Можете ли вы добавить код, где вы обрабатываете с параметром FirstPageFragment.newInstance()слушателя?
MiguelHincapieC
6
Это работает только для меня, если я вызываю commitNow () вместо commit () при удалении фрагмента. Не уверен, почему мой вариант использования отличается, но, надеюсь, это сэкономит кому-то время.
Ян Лавджой,
37

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

Это очень похоже на обычный способ замены фрагмента, за исключением того, что вы используете getChildFragmentManager. Кажется, это работает, за исключением того, что вложенный фрагмент backstack не появляется, когда пользователь нажимает кнопку возврата. В соответствии с решением в этом связанном вопросе вам нужно вручную вызвать popBackStackImmediate () для дочернего менеджера фрагмента. Поэтому вам нужно переопределить onBackPressed () действия ViewPager, где вы получите текущий фрагмент ViewPager и вызовете getChildFragmentManager (). PopBackStackImmediate ().

Получение фрагмента, отображаемого в данный момент, также немного странно, я использовал это грязное решение «android: switcher: VIEWPAGER_ID: INDEX», но вы также можете самостоятельно отслеживать все фрагменты ViewPager, как описано во втором решении на этой странице .

Итак, вот мой код для ViewPager с 4 ListViews с подробным представлением, отображаемым в 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;
}
}
georgiecasey
источник
7
Удастся ли вам загрузить свой код на github? Спасибо.
Gautham
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 ссылки на внешний элемент фрагмента. Ключом к решению этой проблемы является использование вложенных фрагментов (таким образом, вам не нужно манипулировать адаптером viewpager).
Индрек Кюэ
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()соответственно.

mdelolmo
источник
Как вы выполняете замену? невозможно инициализировать прослушиватель без переопределения метода onSwitchToNext.
Леандрос
1
Не потеряете ли вы состояние, сохраненное в mFragmentAtPos0, если повернуть устройство?
СЕБ
@seb, я действительно улучшил это решение, чтобы сохранить mFragmentAtPos0ссылку при сохранении состояния активности. Это не самое элегантное решение, но работает.
mdelolmo
Вы можете увидеть мой ответ . Он заменяет фрагменты и возвращается к первому.
Лид
@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++;
    }
}

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

/* 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 необходимо вызвать метод 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();
}

Решение работает с панелью действий Шерлока и жестом смахивания.

mspapant
источник
Здорово! не могли бы вы положить его на GitHub?
Никрам
Эта реализация является самой чистой из всех представленных здесь и решает все проблемы. Это должен быть принятый ответ.
dazedviper
У кого-нибудь были проблемы с этой реализацией? У меня есть 2 из них. 1. первый бэк не регистрируется - я должен дважды щелкнуть назад, чтобы зарегистрироваться. 2. После того, как я щелкнул (дважды) на вкладке a и перешел на вкладку b, когда я вернулся на вкладку a, он просто показывает содержимое вкладки b
yaronbic
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();
    }
}

Все, что делает этот метод, - это заменяет макет кадра идентификатором R.id.hosted_fragmentна фрагмент, предоставленный методу.

Проверьте мой учебник по этой теме для получения дополнительной информации и полного рабочего примера на GitHub!

marktani
источник
Хотя это выглядит неуклюже, я думаю, что на самом деле это лучший (с точки зрения структуры и простоты обслуживания) способ сделать это.
Сира Лам
Я заканчиваю тем, что наносил удар NPE. : / Не могу найти вид, который должен быть заменен
Фариз Факкель
6

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

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

Кроме того, я делюсь здесь полным примером того, как реализовать действия, подобные вкладкам, на страницах фрагментов с помощью 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;
}
Blackhex
источник
Хм, есть еще одна проблема с этим кодом. Когда я хочу запустить действие из фрагмента, который был удален и добавлен снова, я получаю сообщение об ошибке: «Состояние сохранения ошибки: активный 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);

Если вы заинтересованы в примере проекта, спросите меня об источниках.

AndroidTeam At Mail.Ru
источник
Какими должны быть методы getFragmentPosition () и handleGetItemInbalidated ()? Я не могу обновить getItem () для возврата NewFragment .. Пожалуйста, помогите ..
Здравствуй! Пожалуйста, найдите тестовый проект по этой ссылке files.mail.ru/eng?back=%2FEZU6G4
AndroidTeam At Mail.Ru
1
Ваш тестовый проект потерпит неудачу, если вы поверните устройство.
СЕБ
1
@ AndroidTeamAtMail.Ru срок действия ссылки истек. Пожалуйста, поделитесь кодом
Sunny
Вы можете увидеть мой ответ . Он заменяет фрагменты и возвращается к первому.
Лид
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 автоматически воссоздает фрагменты, используя конструктор по умолчанию без аргументов при изменении конфигурации.
Кевин Ли
3
это не просто переход на существующую вкладку? как это связано с заменой фрагментов?
behelit
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

Я также сделал решение, которое работает со стеками . Это более модульный подход, поэтому вам не нужно указывать каждый фрагмент и фрагмент в вашем FragmentPagerAdapter. Он построен поверх примера из ActionbarSherlock, который выводится, если я прав из демонстрационного приложения Google.

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

работает аналогично, если вы хотите добавить фрагмент поверх стека вкладок. Важно : я думаю, это не сработает, если вы захотите иметь 2 экземпляра одного класса поверх двух вкладок. Я быстро разработал это решение, поэтому могу поделиться им, не предоставляя никакого опыта.

СЕБ
источник
Я получаю исключение nullpointer при нажатии кнопки назад, и мой замененный фрагмент также не виден. Так что, возможно, я делаю что-то не так: какой должен быть правильный вызов для правильного изменения фрагментов?
Йерун
Обновление: большинство вещей исправлено, однако, если я нажимаю кнопку «Назад» после добавления нового фрагмента, а затем перехожу на другую вкладку и возвращаюсь, я получаю исключение nullpointer. Итак, у меня есть 2 вопроса: 1: как я могу удалить историю вкладки и убедиться, что по-прежнему присутствует только последний фрагмент (вершина стека)? 2: как я могу избавиться от исключения nullpointer?
Йерун
@Jeroen 1: вы можете получить правильный стек из mTabStackMap, а затем удалить каждый объект TabInfo, который находится под вершиной стека, 2: очень трудно сказать, задайте вопрос с помощью трассировки стека, затем я могу попытаться выяснить, Вы уверены, что это не ошибка в вашем коде? Это происходит также, если вы переключаетесь на вкладку (а затем обратно), которая находится в непосредственной близости (справа или слева)?
СЕБ
Хорошо, я столкнулся с новой проблемой: если я поверну экран, то стек снова окажется пустым? Кажется, потерял след предыдущих пунктов ...
Jeroen
1
@seb спасибо. Я использовал этот код, но вложенные фрагменты теперь возможны, поэтому больше не проблема. :)
Гаркон
2

Замена фрагментов в окне просмотра довольно сложна, но очень возможна и может выглядеть супер гладко. Во-первых, вам нужно позволить самому viewpager обрабатывать удаление и добавление фрагментов. Что происходит, когда вы заменяете фрагмент внутри SearchFragment, ваш viewpager сохраняет свои представления фрагментов. Таким образом, вы получаете пустую страницу, потому что SearchFragment удаляется, когда вы пытаетесь заменить ее.

Решение состоит в том, чтобы создать слушатель внутри вашего viewpager, который будет обрабатывать изменения, сделанные вне него, поэтому сначала добавьте этот код в нижнюю часть вашего адаптера.

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

Затем вам нужно создать закрытый класс в вашем viewpager, который становится слушателем, когда вы хотите изменить свой фрагмент. Например, вы можете добавить что-то вроде этого. Обратите внимание, что он реализует интерфейс, который был только что создан. Поэтому всякий раз, когда вы вызываете этот метод, он запускает код внутри класса ниже.

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 (слушатель). Вот как вы будете вызывать fragment0Changed (String newFragmentIdentification) `. Следующий код показывает, как вы создаете слушателя внутри вашего фрагмента.

    static 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");
    }

}

Это приведет к тому, что код внутри вашего viewpager переключит ваш фрагмент в нулевую позицию fragAt0, чтобы стать новым searchResultFragment. Есть еще две маленькие части, которые вам нужно добавить в пейджер, прежде чем он станет функциональным.

Один из них будет в методе переопределения getItem viewpager.

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

    }

Теперь без этой последней части вы все равно получите пустую страницу. Вид хромает, но это неотъемлемая часть viewPager. Вы должны переопределить метод getItemPosition окна просмотра. Обычно этот метод возвращает POSITION_UNCHANGED, который сообщает ViewPager о том, что все должно быть одинаково, и поэтому 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();
        }
    }

Вам нужно будет создать метод с именем backPressed () внутри FragmentSearchResults, который будет вызывать Frag0знак. Это в сочетании с кодом, который я показал ранее, будет обрабатывать нажатие кнопки назад. Удачи вам в вашем коде для изменения виджета. Требуется много работы, и, насколько я обнаружил, нет никаких быстрых адаптаций. Как я уже сказал, вы в основном создаете собственный адаптер viewpager и позволяете ему обрабатывать все необходимые изменения с помощью слушателей

user3331142
источник
Спасибо за это решение. Но были ли у вас проблемы после поворота экрана. Мое приложение падает, когда я поворачиваю экран и пытаюсь перезагрузить другой фрагмент в той же вкладке.
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, но в моем ответе вы можете переключаться между двумя фрагментами, когда захотите. И с мудрым ответом у меня есть некоторые проблемы при изменении ориентации экрана и тому подобное. Это выглядит как 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);

}
Jachumbelechao к Mantekilla
источник
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);
}
JosephM
источник