Substituir fragmento dentro de um ViewPager


268

Estou tentando usar o fragmento ViewPagerusando o FragmentPagerAdapter. O que estou procurando alcançar é substituir um fragmento, posicionado na primeira página doViewPager , por outro.

O pager é composto por duas páginas. O primeiro é o FirstPagerFragment, o segundo é o SecondPagerFragment. Clicando em um botão da primeira página. Eu gostaria de substituir oFirstPagerFragment com o NextFragment.

Aqui está o meu código.

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

        }
    }
}

... e aqui os arquivos 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>

Agora o problema ... em qual ID devo usar

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

?

Se eu usar R.id.first_fragment_root_id , a substituição funcionará, mas o Hierarchy Viewer mostra um comportamento estranho, como abaixo.

No início, a situação é

após a substituição, a situação é

Como você pode ver, há algo errado, espero encontrar o mesmo estado mostrado na primeira imagem depois de substituir o fragmento.


Você gerou esse diagrama de layout de alguma forma ou manualmente?
precisa saber é o seguinte

@ Macarrão: eu preciso fazer o mesmo. Sua pergunta gerou um longo tópico com muitas respostas diferentes. O que funcionou melhor no final? Muito Obrigado!
NARB

1
@JeroenVannevel que você pode descobri-lo já, mas que diagrama de layout é developer.android.com/tools/performance/hierarchy-viewer/...
Ankit Popli

tem algum problema precisa de sua ajuda stackoverflow.com/questions/40149039/...
albert

Respostas:


162

Existe outra solução que não precisa modificar o código fonte de ViewPagere FragmentStatePagerAdaptere funciona com a FragmentPagerAdapterclasse base usada pelo autor.

Eu gostaria de começar respondendo à pergunta do autor sobre qual ID ele deveria usar; é o ID do contêiner, ou seja, o ID do próprio pager da visualização. No entanto, como você provavelmente se notou, usar esse ID no seu código não faz nada acontecer. Vou explicar o porquê:

Primeiro de tudo, para fazer o ViewPagerrepovoamento das páginas, você precisa chamar notifyDataSetChanged()que reside na classe base do seu adaptador.

Segundo, ViewPagerusa o getItemPosition()método abstrato para verificar quais páginas devem ser destruídas e quais devem ser mantidas. A implementação padrão dessa função sempre retorna POSITION_UNCHANGED, o que faz ViewPagercom que todas as páginas atuais sejam mantidas e, consequentemente, não anexando sua nova página. Portanto, para fazer a substituição do fragmento funcionar, ele getItemPosition()precisa ser substituído no seu adaptador e deve retornarPOSITION_NONE quando chamado com um fragmento antigo, para ser oculto, como argumento.

Isso também significa que seu adaptador sempre precisa estar ciente de qual fragmento deve ser exibido na posição 0 FirstPageFragmentou NextFragment. Uma maneira de fazer isso é fornecer um ouvinte ao criar FirstPageFragment, que será chamado na hora de trocar fragmentos. Eu acho que isso é uma coisa boa, deixar seu adaptador de fragmentos lidar com todos os switches e chamadas de fragmentos para ViewPagere FragmentManager.

Terceiro, FragmentPagerAdapterarmazena em cache os fragmentos usados ​​por um nome derivado da posição; portanto, se houver um fragmento na posição 0, ele não será substituído, mesmo que a classe seja nova. Existem duas soluções, mas a mais simples é usar a remove()função de FragmentTransaction, que também removerá sua tag.

Isso foi muito texto, aqui está o código que deve funcionar no seu caso:

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

Espero que isso ajude alguém!


24
funciona para mim também, mas como implementar o botão voltar para mostrar o primeiro fragmento?
User1159819

6
Ótima explicação, mas você pode postar o código fonte completo disso, especificamente as classes FirstPageFragment e SecondPageFragment.
Georgiecasey

6
Como você adiciona o FirstPageFragmentListener ao pacote em newInstance ()?
miguel

4
Você pode adicionar o código onde lida com os FirstPageFragment.newInstance()parâmetros do ouvinte?
MiguelHincapieC

6
Isso só funciona para mim se eu chamar commitNow () em vez de commit () ao remover o fragmento. Não sei por que meu caso de uso é diferente, mas espero que isso poupe algum tempo a alguém.
Ian Lovejoy

37

Desde 13 de novembro de 2012, a substituição de fragmentos em um ViewPager parece ter se tornado muito mais fácil. O Google lançou o Android 4.2 com suporte para fragmentos aninhados e também é suportado na nova Biblioteca de Suporte do Android v11, portanto, isso funcionará desde a versão 1.6.

É muito semelhante à maneira normal de substituir um fragmento, exceto pelo uso de getChildFragmentManager. Parece funcionar, exceto que o backstack do fragmento aninhado não é exibido quando o usuário clica no botão Voltar. De acordo com a solução nessa pergunta vinculada, você precisa chamar manualmente popBackStackImmediate () no gerenciador filho do fragmento. Portanto, você precisa substituir onBackPressed () da atividade do ViewPager, onde você obterá o fragmento atual do ViewPager e chamará getChildFragmentManager (). PopBackStackImmediate () nele.

Obter o fragmento atualmente sendo exibido também é um pouco invasivo, usei essa solução suja "android: switcher: VIEWPAGER_ID: INDEX", mas você também pode acompanhar todos os fragmentos do ViewPager, conforme explicado na segunda solução nesta página .

Então, aqui está o meu código para um ViewPager com 4 ListViews com uma exibição detalhada mostrada no ViewPager quando o usuário clica em uma linha e com o botão Voltar funcionando. Tentei incluir apenas o código relevante por questões de brevidade. Portanto, deixe um comentário se quiser que o aplicativo completo seja carregado no GitHub.

HomeActivity.java

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

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

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

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

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

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

 }
}

ListProductsFragment.java

public class ListProductsFragment extends SherlockFragment {
private ListView list;

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

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

7
Será possível fazer o upload do seu código no github? obrigado.
Gautham

4
@georgiecasey: baixei a API 17, Android 4.2, que me permitiu começar a usar getChildFragmentManager (). Mas devo estar faltando algo em sua solução, pois agora estou sobrepondo fragmentos antigos e novos na tela. Nota: estou tentando usar sua solução em conjunto com o exemplo de código do Google TabsAdapter em developer.android.com/reference/android/support/v4/view/… . Obrigado por todas as sugestões e / ou código de referência.
Gcl1

27
A que R.id.products_list_linear se refere? Por favor, vincule seu código como foi oferecido.
howettl

1
@howettl as referências de ID ao membro do fragmento externo. A chave para resolver esse problema é usar fragmentos aninhados (dessa forma, você não precisa manipular o adaptador do viewpager).
Indrek Kõue

2
Isso já chegou ao github?
Baruch

32

Com base na resposta do @wize, que achei útil e elegante, consegui o que queria parcialmente, porque queria que a capacidade de voltar ao primeiro fragmento fosse substituída. Eu consegui modificar um pouco o código dele.

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

Para realizar a substituição, basta definir um campo estático, do tipo CalendarPageFragmentListenere inicializado através dos newInstancemétodos dos fragmentos correspondentes e chamar FirstFragment.pageListener.onSwitchToNextFragment()ou NextFragment.pageListener.onSwitchToNextFragment()respectivamente.


Como você executa a substituição? não é possível inicializar o Ouvinte sem substituir o método onSwitchToNext.
Leandros

1
você não perde o estado salvo em mFragmentAtPos0 se você girar o dispositivo?
seb

@seb, eu realmente melhorei esta solução para salvar a mFragmentAtPos0referência ao salvar o estado da atividade. Não é a solução mais elegante, mas funciona.
Mdelolmo 14/10/12

Você pode ver minha resposta . Ele substitui os fragmentos e retorna ao primeiro.
Lyd 4/09/13

@mdelolmo Você poderia dar uma olhada nesta pergunta sobre o ViewPager? Obrigado stackoverflow.com/questions/27937250/…
Hoa Vu

21

Eu implementei uma solução para:

  • Substituição dinâmica de fragmentos dentro da guia
  • Manutenção do histórico por guia
  • Trabalhando com mudanças de orientação

Os truques para conseguir isso são os seguintes:

  • Use o método notifyDataSetChanged () para aplicar a substituição de fragmento
  • Use o gerenciador de fragmentos apenas para back-stage e não para substituição de fragmentos
  • Manter o histórico usando o padrão de lembrança (pilha)

O código do adaptador é o seguinte:

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

Na primeira vez em que você adiciona todas as guias, precisamos chamar o método createHistory (), para criar o histórico inicial

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

Toda vez que você deseja substituir um fragmento em uma guia específica, você chama: replace (posição final int, classe fragmentClass final, args finais do pacote)

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

Ao pressionar novamente, é necessário chamar o método 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();
}

A solução funciona com barra de ação sherlock e com gesto de furto.


é ótimo! você poderia colocá-lo no github?
Nicramus

Esta implementação é a mais limpa de todas as fornecidas aqui e soluciona todos os problemas. Deve ser a resposta aceita.
Davidviper

Alguém teve problemas com esta implementação? Eu estou tendo 2 deles. 1. a primeira volta não se registra - tenho que clicar duas vezes para que ela se registre. 2. Depois que eu clicar novamente (duas vezes) na guia um e eu vá para a aba b, quando eu voltar para a guia um isso só mostra o conteúdo do guia b
yaronbic

7

tl; dr: use um fragmento de host responsável por substituir o conteúdo hospedado e acompanhe um histórico de navegação anterior (como em um navegador).

Como seu caso de uso consiste em uma quantidade fixa de guias, minha solução funciona bem: a idéia é preencher o ViewPager com instâncias de uma classe personalizada HostFragment, capaz de substituir o conteúdo hospedado e manter seu próprio histórico de navegação. Para substituir o fragmento hospedado, você faz uma chamada para o método 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();
    }
}

Tudo o que esse método faz é substituir o layout do quadro pelo id R.id.hosted_fragmentpelo fragmento fornecido ao método.

Confira meu tutorial sobre este tópico para obter mais detalhes e um exemplo de trabalho completo no GitHub!


Embora isso pareça desajeitado, acho que é a melhor maneira (em termos de estrutura e facilidade de manutenção) de fazer isso.
precisa saber é o seguinte

Eu acabo dando uma cabeçada em um NPE. : / Não é possível encontrar a visão de que está a ser substituído
Fariz Fakkel

6

Algumas das soluções apresentadas me ajudaram muito a resolver parcialmente o problema, mas ainda há uma coisa importante faltando nas soluções que produziu exceções inesperadas e conteúdo de páginas em preto, em vez de fragmentar o conteúdo em alguns casos.

O problema é que a classe FragmentPagerAdapter está usando o ID do item para armazenar fragmentos em cache no FragmentManager . Por esse motivo, é necessário substituir também o método getItemId (int position) para que ele retorne, por exemplo, a posição para as páginas de nível superior e mais de 100 posições para as páginas de detalhes. Caso contrário, o fragmento de nível superior criado anteriormente seria retornado do cache em vez do fragmento de nível de detalhe.

Além disso, estou compartilhando aqui um exemplo completo de como implementar a atividade de guias com páginas do Fragment usando o ViewPager e botões de guias usando o RadioGroup que permite a substituição de páginas de nível superior por páginas detalhadas e também suporta o botão Voltar. Essa implementação suporta apenas um nível de empilhamento retroativo (lista de itens - detalhes do item), mas a implementação de empilhamento retroativo de vários níveis é simples. Este exemplo funciona muito bem em casos normais, exceto se estiver lançando uma NullPointerException no caso quando você alternar para, por exemplo, segunda página, alterar o fragmento da primeira página (enquanto não estiver visível) e retornar à primeira página. Vou postar uma solução para esse problema assim que descobrir:

public class TabsActivity extends FragmentActivity {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      return position;
    }

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

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

    private ViewPager mContainer;
    private FragmentManager mFragmentManager;

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

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

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

Hmm, há outro problema com esse código. Quando quero iniciar uma atividade do fragmento que foi removido e adicionado novamente, recebo um erro: "Falha ao salvar o estado: o NewFirstFragment ativo {405478a0} limpou o índice: -1" (rastreamento completo da pilha: nopaste.info/547a23dfea.html ) Olhando para as fontes do Android, descobri que ele tem algo a ver com o backstack, mas ainda não sei como corrigi-lo. Alguém tem uma pista?
Blackhex

2
Legal, usando <code> mFragmentManager.beginTransaction (). Desanexar (fragmento antigo). Anexar (fragmento) .commit (); </code> em vez de <pre> mFragmentManager.beginTransaction (). SetTransition (FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .remove ( old_fragment) .add (mContainer.getId (), fragmento) .commit (); </pre> no exemplo acima, parece corrigir os dois problemas :-).
Blackhex

1
Se você usar esse método, verifique se não está permitindo que o Android recrie sua atividade ao girar o dispositivo. Caso contrário, o PagerAdapter será recriado e você perderá seus mBackFragments. Isso também confundirá seriamente o FragmentManager. Usando o BackStack evita este problema, à custa de uma implementação mais complicada de PagerAdapter (ou seja, você não pode herdar de FragmentPagerAdapter.)
Norman

6

Eu criei um ViewPager com 3 elementos e 2 subelementos para o índice 2 e 3 e aqui o que eu queria fazer ..

insira a descrição da imagem aqui

Eu implementei isso com a ajuda de perguntas e respostas anteriores do StackOverFlow e aqui está o link.

ViewPagerChildFragments


Seu projeto falhará, se você girar o dispositivo.
Dory

6

Para substituir um fragmento dentro de um ViewPagervocê pode mover os códigos-fonte de ViewPager, PagerAdaptere FragmentStatePagerAdapterclasses em seu projeto e adicione o seguinte código.

em ViewPager:

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

no 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()garante que após a próxima chamada getItem()retorne newFragment getFragmentPosition()retorne a posição do fragmento no seu adaptador.

Agora, para substituir fragmentos, chame

mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);

Se você está interessado em um exemplo de projeto, peça-me as fontes.


Como devem ser os métodos getFragmentPosition () e handleGetItemInbalidated ()? Não consigo atualizar o getItem () para retornar o NewFragment .. Por favor, ajude ..

Oi! Por favor, encontre o projeto de teste neste link files.mail.ru/eng?back=%2FEZU6G4
AndroidTeam At Mail.Ru

1
Seu projeto de teste falhará se você girar o dispositivo.
seb

1
@ AndroidTeamAtMail.Ru o link parece expired.Will que quiser compartilhar o código
Ensolarado

Você pode ver minha resposta . Ele substitui os fragmentos e retorna ao primeiro.
Lyd

4

Funciona muito bem com a solução do AndroidTeam, no entanto, descobri que precisava ter a capacidade de voltar da mesma forma. FrgmentTransaction.addToBackStack(null) Mas apenas adicionar isso fará com que o Fragmento seja substituído apenas sem notificar o ViewPager. A combinação da solução fornecida com esse aprimoramento menor permitirá que você retorne ao estado anterior simplesmente substituindo o onBackPressed()método da atividade . O maior inconveniente é que ele retornará apenas um de cada vez, o que pode resultar em vários cliques

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

Espero que isso ajude alguém.

Também no que diz respeito getFragmentPosition(), é praticamente o getItem()contrário. Você sabe quais fragmentos vão para onde, apenas certifique-se de retornar a posição correta em que estará. Aqui está um exemplo:

    @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

No seu onCreateViewmétodo, containeré realmente uma ViewPagerinstância.

Então, apenas ligando

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

mudará o fragmento atual no seu ViewPager.


1
Após horas de pesquisa, era essa linha de código que eu precisava para que todas as minhas guias funcionassem corretamente. vpViewPager.setCurrentItem(1);. Eu passei pelo exemplo de cada pessoa, sem nada acontecendo toda vez, até que finalmente cheguei à sua. Obrigado.
Brandon

1
mente soprada ... isso é incrível! Não percebi que a variável container era o próprio viewpager. Esta resposta deve ser explorada primeiro, antes de todas as outras longas. Além disso, não há transferência de variáveis ​​de ouvinte para o construtor de fragmentos, o que simplifica toda a implementação quando o Android recria automaticamente fragmentos usando o construtor padrão no-arg quando a configuração é alterada.
Kevin Lee

3
isso não está mudando apenas para uma guia existente? como isso está relacionado à substituição de fragmentos?
Behelit

2
Concorde com @behelit, isso não responde à pergunta, apenas move a ViewPagerpágina para outra página, não substitui nenhum fragmento.
Yoann Hercouet

2

Aqui está minha solução relativamente simples para esse problema. As chaves desta solução são usar em FragmentStatePagerAdaptervez de, FragmentPagerAdapterpois o primeiro removerá fragmentos não utilizados para você, enquanto o último ainda retém suas instâncias. O segundo é o uso de POSITION_NONEem getItem (). Usei uma lista simples para acompanhar meus fragmentos. Meu requisito era substituir a lista inteira de fragmentos de uma vez por uma nova lista, mas o abaixo poderia ser facilmente modificado para substituir fragmentos individuais:

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

Também fiz uma solução, que está trabalhando com o Stacks . É uma abordagem mais modular , para que você não precise especificar cada fragmento e detalhe no seu FragmentPagerAdapter. Ele é baseado no exemplo do ActionbarSherlock, que deriva se eu estiver no aplicativo de demonstração do 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) {
    }

}

Adicione isso para a funcionalidade do botão voltar em sua MainActivity:

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

Se você quiser salvar o estado do fragmento quando ele for removido. Deixe seu Fragment implementar a interface SaveStateBundleretornando na função um pacote com seu estado de salvamento. Obter o pacote após a instanciação por this.getArguments().

Você pode instanciar uma guia como esta:

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

funciona semelhante se você quiser adicionar um fragmento em cima de uma pilha de guias. Importante : Eu acho que não funcionará se você quiser ter 2 instâncias da mesma classe em cima de duas guias. Fiz essa solução rapidamente juntos, para que eu possa compartilhá-la apenas sem fornecer nenhuma experiência com ela.


Eu recebo uma exceção nullpointer ao pressionar o botão voltar e meu fragmento substituído também não é visível. Então, talvez eu esteja fazendo algo errado: qual deve ser a chamada correta para alterar os fragmentos corretamente?
Jeroen #

Atualização: eu consertei a maioria das coisas corrigidas, no entanto, se eu pressionar um botão voltar depois de adicionar um novo fragmento e depois ir para outra guia e voltar, recebo uma exceção nullpointer. Portanto, tenho duas perguntas: 1: como posso remover o histórico de uma guia e garantir que apenas o fragmento mais recente (parte superior da pilha te) ainda esteja presente? 2: como posso me livrar da exceção nullpointer?
Jeroen

@Jeroen 1: você pode obter a pilha certa do mTabStackMap e remover todos os objetos TabInfo que estão sob a parte superior da pilha, 2: muito difícil dizer, faça uma pergunta com o stacktrace, então eu poderia tentar descobrir, são Você tem certeza de que não é uma falha no seu código? Isso acontece também se você alternar para uma guia (e depois voltar) que fica na vizinhança direta (direita ou esquerda)?
seb

Ok, encontrei um novo problema: se eu girar minha tela, a pilha parece estar vazia novamente? Parece a perder o controle dos itens anteriores ...
Jeroen

1
@seb obrigado. Eu estava usando esse código, mas fragmentos aninhados agora são possíveis, não sendo mais um problema. :)
Garcon

2

A substituição de fragmentos em um viewpager está bastante envolvida, mas é muito possível e pode parecer muito lisa. Primeiro, você precisa deixar o próprio viewpager lidar com a remoção e adição dos fragmentos. O que está acontecendo é que, quando você substitui o fragmento dentro do SearchFragment, seu viewpager mantém suas visualizações de fragmento. Então você acaba com uma página em branco porque o SearchFragment é removido quando você tenta substituí-lo.

A solução é criar um ouvinte dentro do seu viewpager que manipulará as alterações feitas fora dele; portanto, primeiro adicione esse código à parte inferior do seu adaptador.

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

Então você precisa criar uma classe privada no seu viewpager que se torne um ouvinte para quando você desejar alterar seu fragmento. Por exemplo, você pode adicionar algo como isto. Observe que ele implementa a interface que acabou de ser criada. Portanto, sempre que você chamar esse método, ele executará o código dentro da classe abaixo.

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

Há duas coisas principais a serem destacadas aqui:

  1. fragAt0 é um fragmento "flexível". Pode assumir qualquer tipo de fragmento que você der. Isso permite que ele se torne seu melhor amigo, alterando o fragmento na posição 0 para o fragmento que você deseja.
  2. Observe os ouvintes que são colocados no construtor 'newInstance (listener). É assim que você chama callfragment0Changed (String newFragmentIdentification) `. O código a seguir mostra como você cria o ouvinte dentro do seu fragmento.

    estático nextFragmentListener listenerSearch;

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

Você pode chamar a alteração dentro de seu 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");
    }

}

Isso acionaria o código dentro do seu viewpager para alternar seu fragmento na posição zero fragAt0 para se tornar um novo searchResultFragment. Há mais duas peças pequenas que você precisaria adicionar ao viewpager antes que ele se tornasse funcional.

Um seria no método de substituição getItem do 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();

    }

Agora, sem esta peça final, você ainda obteria uma página em branco. Tipo de coxo, mas é uma parte essencial do viewPager. Você deve substituir o método getItemPosition do viewpager. Normalmente, esse método retornará POSITION_UNCHANGED, que informa ao viewpager para manter tudo igual e, portanto, o getItem nunca será chamado para colocar o novo fragmento na página. Aqui está um exemplo de algo que você poderia fazer

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
}

Como eu disse, o código fica muito envolvido, mas você basicamente precisa criar um adaptador personalizado para sua situação. As coisas que mencionei tornarão possível alterar o fragmento. Provavelmente levará muito tempo para absorver tudo, então eu seria paciente, mas tudo fará sentido. Vale totalmente a pena dedicar um tempo, pois pode criar um aplicativo realmente elegante.

Aqui está a pepita para manipular o botão Voltar. Você coloca isso dentro da sua 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();
        }
    }

Você precisará criar um método chamado backPressed () dentro de FragmentSearchResults que chama fragment0changed. Isso em conjunto com o código que eu mostrei antes, irá lidar com o pressionamento do botão Voltar. Boa sorte com seu código para alterar o viewpager. É preciso muito trabalho e, até onde eu descobri, não há adaptações rápidas. Como eu disse, você está basicamente criando um adaptador de viewpager personalizado e permitindo que ele lide com todas as alterações necessárias usando ouvintes


Obrigado por esta solução. Mas você teve algum problema depois de girar a tela. Meu aplicativo falha quando eu giro a tela e tentando recarregar outro fragmento na mesma guia.
AnteGemini

Já faz um tempo desde que desenvolvi o desenvolvedor do Android, mas meu palpite é que há um dado que não existe mais no qual seu aplicativo conta. Quando você gira, toda a atividade é reiniciado e pode causar falhas se você tentar recarregar o seu estado anterior sem todos os dados que você precisa (dizer a partir do pacote em onPause, onResume)
user3331142

2

Esta é a minha maneira de conseguir isso.

Primeiro, adicione a guia Root_fragmentinterna viewPagerna qual você deseja implementar o fragmentevento de clique no botão . Exemplo;

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

Primeiro de tudo, RootTabFragmentdeve ser incluído FragmentLayoutpara alteração de fragmento.

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

Em seguida, RootTabFragment onCreateViewimplemente fragmentChangepara o seuFirstPagerFragment

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

Depois disso, implemente o onClickevento para o seu botão dentro FirstPagerFragmente faça a alteração do fragmento assim novamente.

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

Espero que isso ajude você cara.


1

Encontrei uma solução simples, que funciona bem, mesmo que você queira adicionar novos fragmentos no meio ou substituir o fragmento atual. Na minha solução, você deve substituirgetItemId() que deve retornar um ID exclusivo para cada fragmento. Não posicionar como padrão.

Aqui está:

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

Aviso: Neste exemplo FirstFragmente SecondFragmentestende-se classe abstracta PageFragment, que tem método getPage().


1

Estou fazendo algo parecido com o wize, mas na minha resposta você pode alternar entre os dois fragmentos sempre que quiser. E com a resposta inteligente, tenho alguns problemas ao mudar a orientação da tela e coisas assim. Este é o PagerAdapter se parece com:

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

O ouvinte que eu implementei na atividade do contêiner do adaptador para colocá-lo no fragmento ao anexá-lo, esta é a atividade:

    public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {

//...

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


    }

//...
}

Em seguida, no fragmento, coloque o ouvinte ao anexar uma chamada:

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

E finalmente o ouvinte:

public interface changeFragmentListener {

    void onChange(boolean isNextFragment);

}

1

Segui as respostas de @wize e @mdelolmo e obtive a solução. Graças Toneladas. Mas ajustei essas soluções um pouco para melhorar o consumo de memória.

Problemas que observei:

Eles salvam a instância da Fragmentqual é substituída. No meu caso, é um fragmento que se mantém MapViewe eu achei caro. Então, eu estou mantendo o FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)invés de Fragmentsi mesmo.

Aqui está a minha implementação.

  public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {

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


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

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

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

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

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

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

    @Override
    public int getItemPosition(Object object) {

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

        return POSITION_UNCHANGED;
    }

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

        SwitchFragListener() {}

        public void onSwitchToNextFragment() {

            pagerAdapterPosChanged = POSITION_NONE;
            notifyDataSetChanged();
        }
    }

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

Link de demonstração aqui .. https://youtu.be/l_62uhKkLyM

Para fins de demonstração, usei 2 fragmentos TabReplaceFragmente DemoTab2Fragmentna posição dois. Em todos os outros casos, estou usando DemoTabFragmentinstâncias.

Explicação:

Estou passando Switchde Activity para o DemoCollectionPagerAdapter. Com base no estado dessa opção, exibiremos o fragmento correto. Quando a verificação do interruptor é alterada, estou ligando para oSwitchFragListener 's onSwitchToNextFragmentmétodo, onde eu estou mudando o valor da pagerAdapterPosChangedvariável para POSITION_NONE. Confira mais sobre POSITION_NONE . Isso invalidará o getItem e eu tenho lógicas para instanciar o fragmento certo por lá. Desculpe, se a explicação é um pouco confusa.

Mais uma vez, muito obrigado a @wize e @mdelolmo pela ideia original.

Espero que isso seja útil. :)

Deixe-me saber se esta implementação tem alguma falha. Isso será muito útil para o meu projeto.


0

Após a pesquisa, encontrei a solução com código curto. Antes de tudo, crie uma instância pública no fragmento e remova-o no onSaveInstanceState se o fragmento não recriar na mudança de orientação.

 @Override
public void onSaveInstanceState(Bundle outState) {
    if (null != mCalFragment) {
        FragmentTransaction bt = getChildFragmentManager().beginTransaction();
        bt.remove(mFragment);
        bt.commit();
    }
    super.onSaveInstanceState(outState);
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.