ViewPager e fragmentos - qual é a maneira correta de armazenar o estado do fragmento?

492

Fragmentos parecem muito bons para a separação da lógica da interface do usuário em alguns módulos. Mas junto com o ViewPagerseu ciclo de vida ainda é nebuloso para mim. Portanto, os pensamentos do Guru são extremamente necessários!

Editar

Veja a solução burra abaixo ;-)

Escopo

Atividade principal ViewPagercom fragmentos. Esses fragmentos podem implementar uma lógica um pouco diferente para outras atividades (subdomínios), para que os dados dos fragmentos sejam preenchidos por meio de uma interface de retorno de chamada dentro da atividade. E tudo funciona bem no primeiro lançamento, mas! ...

Problema

Quando a atividade é recriada (por exemplo, na mudança de orientação), o mesmo ocorre com os ViewPagerfragmentos. O código (você encontrará abaixo) diz que toda vez que a atividade é criada, tento criar um novo ViewPageradaptador de fragmentos igual aos fragmentos (talvez esse seja o problema), mas o FragmentManager já possui todos esses fragmentos armazenados em algum lugar (onde?) E inicia o mecanismo de recreação para aqueles. Portanto, o mecanismo de recreação chama onAttach, onCreateView, etc. do fragmento "antigo", com minha interface de retorno de chamada, para iniciar dados por meio do método implementado da Activity. Mas esse método aponta para o fragmento recém-criado, criado pelo método onCreate da Activity.

Questão

Talvez eu esteja usando padrões errados, mas mesmo o livro do Android 3 Pro não tem muito a ver. Então, por favor , me dê um soco de um a dois e aponte como fazê-lo da maneira certa. Muito Obrigado!

Código

Atividade principal

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

Auxiliar de BasePagerActivity aka

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

Adaptador

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

Fragmento

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

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

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

Solução

A solução idiota é salvar os fragmentos dentro de onSaveInstanceState (da atividade do host) com putFragment e colocá-los dentro de onCreate via getFragment. Mas ainda tenho uma sensação estranha de que as coisas não devem funcionar assim ... Veja o código abaixo:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}
Oleksii Malovanyi
fonte
1
Gostaria de saber agora: devo usar uma abordagem muito diferente ou tentar salvar fragmentos da atividade principal (Painel) via onSavedInstancestate para usá-los em onCreate (). Existe uma maneira adequada de salvar esses fragmentos e obtê-los do bundle no onCreate? Eles não parecem ser parcelable ...
Oleksii Malovanyi
2
A segunda abordagem funciona - consulte "Suluição". Mas parece ser um pedaço feio de código, não é?
Oleksii Malovanyi
1
Para o esforço de limpeza da tag Android (detalhes aqui: meta.stackexchange.com/questions/100529/… ), você se importaria de postar sua solução como resposta e marcá-la como selecionada? Dessa forma, ele não vai aparecer como uma pergunta sem resposta :)
Alexander Lucas
1
Sim, acho que está tudo bem. Esperado para smth melhor que a minha ...
Oleksii Malovanyi
1
A solução idiota funciona? Isso me dá uma exceção de ponteiro nulo ..
Zhen Liu

Respostas:

451

Quando FragmentPagerAdapteradiciona um fragmento ao FragmentManager, ele usa uma tag especial com base na posição específica em que o fragmento será colocado. FragmentPagerAdapter.getItem(int position)é chamado apenas quando um fragmento para essa posição não existe. Após a rotação, o Android notará que ele já criou / salvou um fragmento para essa posição específica e, portanto, simplesmente tenta se reconectar com ele FragmentManager.findFragmentByTag(), em vez de criar um novo. Tudo isso é gratuito ao usar FragmentPagerAdapteroe é por isso que é comum ter seu código de inicialização do fragmento dentro do getItem(int)método.

Mesmo se não estivéssemos usando um FragmentPagerAdapter, não é uma boa ideia criar um novo fragmento toda vez Activity.onCreate(Bundle). Como você notou, quando um fragmento é adicionado ao FragmentManager, ele é recriado após a rotação e não é necessário adicioná-lo novamente. Fazer isso é uma causa comum de erros ao trabalhar com fragmentos.

Uma abordagem usual ao trabalhar com fragmentos é a seguinte:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

Ao usar a FragmentPagerAdapter, abandonamos o gerenciamento de fragmentos no adaptador e não precisamos executar as etapas acima. Por padrão, ele apenas pré-carregará um fragmento na frente e atrás da posição atual (embora não os destrua a menos que você esteja usando FragmentStatePagerAdapter). Isso é controlado pelo ViewPager.setOffscreenPageLimit (int) . Por esse motivo, não é garantido que a chamada direta de métodos nos fragmentos fora do adaptador seja válida, pois eles podem nem estar vivos.

Para resumir uma longa história, sua solução a putFragmentser usada para obter uma referência posteriormente não é tão louca e nem tão diferente da maneira normal de usar fragmentos (acima). É difícil obter uma referência caso contrário, porque o fragmento é adicionado pelo adaptador, e não você pessoalmente. Apenas certifique-se de que offscreenPageLimitseja alto o suficiente para carregar os fragmentos desejados o tempo todo, desde que você confie na presença. Isso ignora os recursos preguiçosos de carregamento do ViewPager, mas parece ser o que você deseja para o seu aplicativo.

Outra abordagem é substituir FragmentPageAdapter.instantiateItem(View, int)e salvar uma referência ao fragmento retornado da super chamada antes de retorná-lo (ele tem a lógica de encontrar o fragmento, se já estiver presente).

Para uma imagem mais completa, dê uma olhada em algumas das fontes do FragmentPagerAdapter (curto) e do ViewPager (longo).

antonyt
fonte
7
Adorei a última parte. Tinha um cache para os fragmentos e movia o put na lógica de cache dentro do FragmentPageAdapter.instantiateItem(View, int). Finalmente reparado um bug de longa duração que só aparece na rotação mudança / config e estava me deixando louca ...
Carlos Sobrinho
2
Isso corrigiu um problema que eu estava tendo na alteração de rotação. Talvez eu simplesmente não consiga enxergar o olhar, mas isso está documentado? ou seja, usando a tag para recuperar de um estado anterior? Talvez seja uma resposta óbvia, mas sou bastante novo no desenvolvimento do Android.
1
@ Antidepressivo industrial É o ID do contêiner (por exemplo, um FrameLayout) ao qual o fragmento deve ser adicionado.
antonyt
1
btw, para mim foi FragmentPageAdapter.instantiateItem(ViewGroup, int)melhor que FragmentPageAdapter.instantiateItem(View, int).
Exibir nome
1
Btw, você sabe qual (se houver) método de ciclo de vida do fragmento é chamado quando o fragmento é retirado da tela? É onDetach(), ou algo mais?
ygesher 19/07/2014
36

Quero oferecer uma solução que expande antonyt's resposta maravilhosa e menção de substituir FragmentPageAdapter.instantiateItem(View, int)para salvar referências ao criado Fragmentspara que você possa fazer o trabalho deles mais tarde. Isso também deve funcionar com FragmentStatePagerAdapter; veja notas para detalhes.


Aqui está um exemplo simples de como obter uma referência ao Fragmentsretornado por FragmentPagerAdapterque não depende do tagsconjunto interno no Fragments. A chave é substituir instantiateItem()e salvar referências lá em vez de em getItem().

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

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

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

ou se você preferir trabalhar com as tagsvariáveis ​​/ referências dos membros da classe, Fragmentstambém pode pegar o tagsconjunto FragmentPagerAdapterda mesma maneira: NOTA: isso não se aplica, FragmentStatePagerAdapterpois não define tagsao criar o seu Fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

Observe que esse método NÃO depende da imitação do tagconjunto interno FragmentPagerAdaptere usa APIs apropriadas para recuperá-los. Dessa forma, mesmo que as tagalterações nas versões futuras do SupportLibraryaplicativo ainda estejam seguras.


Não se esqueça que, dependendo do design do seu Activity, o que Fragmentsvocê está tentando trabalhar ainda pode ou não existir, então você deve levar isso em consideração, nullverificando antes de usar suas referências.

Além disso, se você estiver trabalhando com isso FragmentStatePagerAdapter, não deseja manter referências rígidas, Fragmentsporque você pode ter muitas delas e referências rígidas as manteriam desnecessariamente na memória. Em vez disso, salve as Fragmentreferências nas WeakReferencevariáveis em vez das padrão. Como isso:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}
Tony Chan
fonte
com falta de memória, o Android pode pedir ao FragmentManager para destruir fragmentos não utilizados, certo? Se eu estiver certo, sua segunda versão funcionará, a primeira poderá falhar. (Não sei se as versões Android atuais fazer isso, mas parece que temos que esperar isso.)
Moritz Ambos
1
Que tal criar um FragmetPagerAdapteronCreate de Atividade em cada rotação da tela. É errado isso porque ele pode ignorar a reutilização de fragmentos já adicionadas noFragmentPagerAdapter
Bahman
Substituir instantiateItem()é o caminho a percorrer; isso me ajudou a lidar com as rotações da tela e a recuperar minhas instâncias existentes do Fragment depois que a Atividade e o Adaptador foram reiniciados; Deixei-me comentários no código como lembrete: Após a rotação, getItem()NÃO é chamado; somente esse método instantiateItem()é chamado. A super implementação para instantiateItem()reconectar fragmentos após a rotação (conforme necessário), em vez de instanciar novas instâncias!
HelloImKevo
Estou recebendo ponteiro nulo Fragment createdFragment = (Fragment) super.instantiateItem..na primeira solução.
Alexs
Após pesquisar todas as várias iterações duplicadas / variantes desse problema, essa foi a melhor solução. (Cross-referenciado de outro Q com um título mais relevante, por isso agradeço por isso!)
MandisaW
18

Encontrei outra solução relativamente fácil para sua pergunta.

Como você pode ver no código-fonte FragmentPagerAdapter , os fragmentos gerenciados pela FragmentPagerAdapterloja na FragmentManagertag abaixo gerada usando:

String tag="android:switcher:" + viewId + ":" + index;

O viewIdé o container.getId(), a containeré a sua ViewPagerinstância. A indexé a posição do fragmento. Portanto, você pode salvar o ID do objeto em outState:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

Se você deseja se comunicar com esse fragmento, poderá obter if from FragmentManager, como:

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")
user2110066
fonte
21
hmmm, não acho que esse seja um bom caminho, pois você está respondendo a uma convenção de nomenclatura interna de tags que nunca será a mesma para sempre.
Dori
1
Para aqueles que procuram uma solução que não depende do interno tag, considere tentar minha resposta .
Tony Chan
16

Quero oferecer uma solução alternativa para talvez um caso um pouco diferente, já que muitas das minhas pesquisas por respostas continuaram me levando a esse segmento.

Meu caso - estou criando / adicionando páginas dinamicamente e deslizando-as para um ViewPager, mas, quando rotacionadas (onConfigurationChange), acabo com uma nova página porque é claro que o OnCreate é chamado novamente. Mas quero manter a referência a todas as páginas que foram criadas antes da rotação.

Problema - não tenho identificadores exclusivos para cada fragmento que criei, portanto, a única maneira de fazer referência era, de alguma forma, armazenar referências em uma Matriz para serem restauradas após a alteração na rotação / configuração.

Solução alternativa - O conceito principal era que a Atividade (que exibe os Fragmentos) também gerencie a matriz de referências aos Fragmentos existentes, pois essa atividade pode utilizar Bundles no onSaveInstanceState

public class MainActivity extends FragmentActivity

Portanto, nesta atividade, declaro um membro privado para rastrear as páginas abertas

private List<Fragment> retainedPages = new ArrayList<Fragment>();

Isso é atualizado sempre que onSaveInstanceState é chamado e restaurado em onCreate

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

... então, uma vez armazenado, pode ser recuperado ...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

Essas foram as alterações necessárias na atividade principal e, portanto, eu precisava dos membros e métodos dentro do meu FragmentPagerAdapter para que isso funcionasse, portanto, dentro

public class ViewPagerAdapter extends FragmentPagerAdapter

uma construção idêntica (como mostrado acima em MainActivity)

private List<Fragment> _pages = new ArrayList<Fragment>();

e essa sincronização (conforme usada acima em onSaveInstanceState) é suportada especificamente pelos métodos

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

E finalmente, na classe de fragmentos

public class CustomFragment extends Fragment

para que tudo isso funcionasse, houve duas mudanças, primeiro

public class CustomFragment extends Fragment implements Serializable

e adicionando isso ao onCreate para que os fragmentos não sejam destruídos

setRetainInstance(true);

Ainda estou no processo de entender o ciclo de vida dos fragmentos e do Android, portanto, ressalte aqui que pode haver redundâncias / ineficiências nesse método. Mas funciona para mim e espero que possa ser útil para outras pessoas com casos semelhantes aos meus.

Frank Yin
fonte
2
+1 para setRetainInstance (true) - a magia negra que eu estava procurando!
declinação 29/03
Foi ainda mais fácil usando o FragmentStatePagerAdapter (v13). Que tratam para você com a restauração e liberação do estado.
A Butcher
Descrição clara e solução muito agradável. Obrigado!
Bruno Bieri
8

Minha solução é muito rude, mas funciona: sendo meus fragmentos criados dinamicamente a partir de dados retidos, simplesmente removo todos os fragmentos da PageAdapterchamada anterior super.onSaveInstanceState()e os recrio na criação da atividade:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

Você não pode removê-los onDestroy(), caso contrário, você recebe esta exceção:

java.lang.IllegalStateException: Não é possível executar esta ação depois onSaveInstanceState

Aqui o código no adaptador da página:

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

Eu salvo a página atual e a restauro somente onCreate()depois que os fragmentos foram criados.

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  
Sir John
fonte
Não sei por que, mas notifyDataSetChanged (); causa falha no aplicativo
Malachiasz 15/10
4

O que é isso BasePagerAdapter? Você deve usar um dos adaptadores de pager padrão - FragmentPagerAdapterou FragmentStatePagerAdapter, dependendo se deseja Fragmentos que não são mais necessários peloViewPager para o mesmo sejam mantidos ao redor (o primeiro) ou tenham seu estado salvo (o último) e recriados se necessário novamente.

O código de exemplo para uso ViewPagerpode ser encontrado aqui

É verdade que o gerenciamento de fragmentos em um pager de exibição entre instâncias de atividade é um pouco complicado, porque FragmentManagera estrutura cuida de salvar o estado e restaurar quaisquer fragmentos ativos que o pager criou. Tudo isso realmente significa é que o adaptador ao inicializar precisa garantir que ele se reconecte com os fragmentos restaurados existentes. Você pode olhar para o código FragmentPagerAdapterou FragmentStatePagerAdapterver como isso é feito.

hackbod
fonte
1
O código BasePagerAdapter está disponível na minha pergunta. Como se pode ver, ele simplesmente estende o FragmentPagerAdapter com a finalidade de implementar o TitleProvider . Então, tudo já está funcionando com a abordagem sugerida para desenvolvedores do Android.
Oleksii Malovanyi 4/11
Eu não estava ciente de "FragmentStatePagerAdapter". Você me salvou literalmente horas. Obrigado.
Knossos
2

Se alguém tiver problemas com seu FragmentStatePagerAdapter, não restaurando adequadamente o estado de seus fragmentos ... ou seja, novos fragmentos estão sendo criados pelo FragmentStatePagerAdapter, em vez de restaurá-los do estado ...

Certifique-se de ligar ViewPager.setOffscreenPageLimit()ANTES de ligarViewPager.setAdapter(fragmentStatePagerAdapter)

Ao ligar ViewPager.setOffscreenPageLimit()... o ViewPager procurará imediatamente seu adaptador e tentará obter seus fragmentos. Isso pode acontecer antes que o ViewPager tenha a chance de restaurar os fragmentos do savedInstanceState (criando assim novos fragmentos que não podem ser reinicializados no SavedInstanceState porque são novos).

dell116
fonte
0

Eu vim com esta solução simples e elegante. Ele pressupõe que a atividade é responsável pela criação dos Fragmentos, e o Adaptador apenas os serve.

Este é o código do adaptador (nada de estranho aqui, exceto pelo fato de mFragmentsser uma lista de fragmentos mantidos pela Atividade)

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

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

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

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

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

Todo o problema desse segmento é obter uma referência dos fragmentos "antigos", então eu uso esse código no onCreate da Activity.

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

É claro que você pode ajustar ainda mais esse código, se necessário, por exemplo, certificando-se de que os fragmentos sejam instâncias de uma classe específica.

Merlevede
fonte
0

Para obter os fragmentos após a mudança de orientação, use o .getTag ().

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

Para um pouco mais de manipulação, escrevi minha própria ArrayList para o meu PageAdapter obter o fragmento por viewPagerId e FragmentClass em qualquer posição:

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

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

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


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

Então, basta criar um MyPageArrayList com os fragmentos:

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

e adicione-os ao viewPager:

    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

Depois disso, você pode obter orientação após alterar o fragmento correto usando sua classe:

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));
Scrounger
fonte
-30

adicionar:

   @SuppressLint("ValidFragment")

antes da sua aula.

não funciona, faça algo assim:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
andro_stackoverflow
fonte