Заменить фрагмент внутри ViewPager - PullRequest
256 голосов
/ 11 октября 2011

Я пытаюсь использовать фрагмент с 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 показывает странное поведение, как показано ниже.

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

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

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

Ответы [ 18 ]

153 голосов
/ 03 февраля 2012

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

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

Прежде всего, чтобы ViewPager снова заполнил страницы, вам нужно вызвать notifyDataSetChanged(), который находится в базовом классе вашего адаптера.

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

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

В-третьих, FragmentPagerAdapter кэширует используемые фрагменты по имени, которое является производнымс позиции, поэтому, если в позиции 0 был фрагмент, он не будет заменен, даже если класс новый.Есть два решения, но самое простое - использовать функцию remove() FragmentTransaction, которая также удалит его тег.

Это было много текста, вот код, который должен работать в вашемcase:

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

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

37 голосов
/ 18 декабря 2012

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

Это очень похоже на обычный способ замены фрагмента, за исключением того, что вы используете getChildFragmentManager. Кажется, это работает, за исключением того, что вложенный фрагмент не вставлен обратно , когда пользователь нажимает кнопку возврата. Согласно решению в этом связанном вопросе, вам нужно вручную вызвать 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;
}
}
32 голосов
/ 28 августа 2012

Основываясь на ответе @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() соответственно.

21 голосов
/ 24 июля 2013

Я реализовал решение для:

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

Уловки для достижения этой цели:

  • Используйте метод 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, конечный фрагмент класса Class, конечные аргументы 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 ():

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

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

7 голосов
/ 03 января 2016

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!

6 голосов
/ 23 сентября 2014

Я создал ViewPager с 3 элементами и 2 подэлементами для индекса 2 и 3, и вот что я хотел сделать ..

enter image description here

Я реализовал это с помощьюсправка из предыдущих вопросов и ответы от StackOverFlow и вот ссылка.

ViewPagerChildFragments

6 голосов
/ 15 августа 2012

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

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

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

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;
}
6 голосов
/ 16 ноября 2011

Чтобы заменить фрагмент внутри 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);

Если вас интересует пример проекта, спросите у меня источники.

4 голосов
/ 01 декабря 2011

Прекрасно работает с решением 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;       
}

Надеюсь, это кому-то поможет.в значительной степени 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;
    }
2 голосов
/ 05 декабря 2014

В вашем onCreateView методе container на самом деле является ViewPager экземпляром.

Итак, просто позвонив

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

изменит текущий фрагмент в вашем ViewPager.

...