Fragmentos parecem muito bons para a separação da lógica da interface do usuário em alguns módulos. Mas junto com o ViewPager
seu 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 ViewPager
com 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 ViewPager
fragmentos. O código (você encontrará abaixo) diz que toda vez que a atividade é criada, tento criar um novo ViewPager
adaptador 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();
...
}
fonte
Respostas:
Quando
FragmentPagerAdapter
adiciona 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 eleFragmentManager.findFragmentByTag()
, em vez de criar um novo. Tudo isso é gratuito ao usarFragmentPagerAdapter
oe é por isso que é comum ter seu código de inicialização do fragmento dentro dogetItem(int)
método.Mesmo se não estivéssemos usando um
FragmentPagerAdapter
, não é uma boa ideia criar um novo fragmento toda vezActivity.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:
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 usandoFragmentStatePagerAdapter
). 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
putFragment
ser 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 queoffscreenPageLimit
seja 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).
fonte
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 ...FragmentPageAdapter.instantiateItem(ViewGroup, int)
melhor queFragmentPageAdapter.instantiateItem(View, int)
.onDetach()
, ou algo mais?Quero oferecer uma solução que expande
antonyt
's resposta maravilhosa e menção de substituirFragmentPageAdapter.instantiateItem(View, int)
para salvar referências ao criadoFragments
para que você possa fazer o trabalho deles mais tarde. Isso também deve funcionar comFragmentStatePagerAdapter
; veja notas para detalhes.Aqui está um exemplo simples de como obter uma referência ao
Fragments
retornado porFragmentPagerAdapter
que não depende dotags
conjunto interno noFragments
. A chave é substituirinstantiateItem()
e salvar referências lá em vez de emgetItem()
.ou se você preferir trabalhar com as
tags
variáveis / referências dos membros da classe,Fragments
também pode pegar otags
conjuntoFragmentPagerAdapter
da mesma maneira: NOTA: isso não se aplica,FragmentStatePagerAdapter
pois não definetags
ao criar o seuFragments
.Observe que esse método NÃO depende da imitação do
tag
conjunto internoFragmentPagerAdapter
e usa APIs apropriadas para recuperá-los. Dessa forma, mesmo que astag
alterações nas versões futuras doSupportLibrary
aplicativo ainda estejam seguras.Não se esqueça que, dependendo do design do seu
Activity
, o queFragments
você está tentando trabalhar ainda pode ou não existir, então você deve levar isso em consideração,null
verificando antes de usar suas referências.Além disso, se você estiver trabalhando com isso
FragmentStatePagerAdapter
, não deseja manter referências rígidas,Fragments
porque você pode ter muitas delas e referências rígidas as manteriam desnecessariamente na memória. Em vez disso, salve asFragment
referências nasWeakReference
variáveis em vez das padrão. Como isso:fonte
FragmetPagerAdapter
onCreate de Atividade em cada rotação da tela. É errado isso porque ele pode ignorar a reutilização de fragmentos já adicionadas noFragmentPagerAdapter
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étodoinstantiateItem()
é chamado. A super implementação parainstantiateItem()
reconectar fragmentos após a rotação (conforme necessário), em vez de instanciar novas instâncias!Fragment createdFragment = (Fragment) super.instantiateItem..
na primeira solução.Encontrei outra solução relativamente fácil para sua pergunta.
Como você pode ver no código-fonte FragmentPagerAdapter , os fragmentos gerenciados pela
FragmentPagerAdapter
loja naFragmentManager
tag abaixo gerada usando:O
viewId
é ocontainer.getId()
, acontainer
é a suaViewPager
instância. Aindex
é a posição do fragmento. Portanto, você pode salvar o ID do objeto emoutState
:Se você deseja se comunicar com esse fragmento, poderá obter if from
FragmentManager
, como:fonte
tag
, considere tentar minha resposta .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
Portanto, nesta atividade, declaro um membro privado para rastrear as páginas abertas
Isso é atualizado sempre que onSaveInstanceState é chamado e restaurado em onCreate
... então, uma vez armazenado, pode ser recuperado ...
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
uma construção idêntica (como mostrado acima em MainActivity)
e essa sincronização (conforme usada acima em onSaveInstanceState) é suportada especificamente pelos métodos
E finalmente, na classe de fragmentos
para que tudo isso funcionasse, houve duas mudanças, primeiro
e adicionando isso ao onCreate para que os fragmentos não sejam destruídos
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.
fonte
Minha solução é muito rude, mas funciona: sendo meus fragmentos criados dinamicamente a partir de dados retidos, simplesmente removo todos os fragmentos da
PageAdapter
chamada anteriorsuper.onSaveInstanceState()
e os recrio na criação da atividade: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 depoisonSaveInstanceState
Aqui o código no adaptador da página:
Eu salvo a página atual e a restauro somente
onCreate()
depois que os fragmentos foram criados.fonte
O que é isso
BasePagerAdapter
? Você deve usar um dos adaptadores de pager padrão -FragmentPagerAdapter
ouFragmentStatePagerAdapter
, 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
ViewPager
pode ser encontrado aquiÉ verdade que o gerenciamento de fragmentos em um pager de exibição entre instâncias de atividade é um pouco complicado, porque
FragmentManager
a 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ódigoFragmentPagerAdapter
ouFragmentStatePagerAdapter
ver como isso é feito.fonte
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).fonte
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
mFragments
ser uma lista de fragmentos mantidos pela Atividade)Todo o problema desse segmento é obter uma referência dos fragmentos "antigos", então eu uso esse código no onCreate da Activity.
É 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.
fonte
Para obter os fragmentos após a mudança de orientação, use o .getTag ().
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:
Então, basta criar um MyPageArrayList com os fragmentos:
e adicione-os ao viewPager:
Depois disso, você pode obter orientação após alterar o fragmento correto usando sua classe:
fonte
adicionar:
antes da sua aula.
não funciona, faça algo assim:
fonte