Estou tentando usar o fragmento ViewPager
usando 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.
fonte
Respostas:
Existe outra solução que não precisa modificar o código fonte de
ViewPager
eFragmentStatePagerAdapter
e funciona com aFragmentPagerAdapter
classe 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
ViewPager
repovoamento das páginas, você precisa chamarnotifyDataSetChanged()
que reside na classe base do seu adaptador.Segundo,
ViewPager
usa ogetItemPosition()
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 retornaPOSITION_UNCHANGED
, o que fazViewPager
com 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, elegetItemPosition()
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
FirstPageFragment
ouNextFragment
. Uma maneira de fazer isso é fornecer um ouvinte ao criarFirstPageFragment
, 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 paraViewPager
eFragmentManager
.Terceiro,
FragmentPagerAdapter
armazena 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 aremove()
função deFragmentTransaction
, que também removerá sua tag.Isso foi muito texto, aqui está o código que deve funcionar no seu caso:
Espero que isso ajude alguém!
fonte
FirstPageFragment.newInstance()
parâmetros do ouvinte?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
ListProductsFragment.java
fonte
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:
Para realizar a substituição, basta definir um campo estático, do tipo
CalendarPageFragmentListener
e inicializado através dosnewInstance
métodos dos fragmentos correspondentes e chamarFirstFragment.pageListener.onSwitchToNextFragment()
ouNextFragment.pageListener.onSwitchToNextFragment()
respectivamente.fonte
mFragmentAtPos0
referência ao salvar o estado da atividade. Não é a solução mais elegante, mas funciona.Eu implementei uma solução para:
Os truques para conseguir isso são os seguintes:
O código do adaptador é o seguinte:
Na primeira vez em que você adiciona todas as guias, precisamos chamar o método createHistory (), para criar o histórico inicial
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)
Ao pressionar novamente, é necessário chamar o método back ():
A solução funciona com barra de ação sherlock e com gesto de furto.
fonte
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étodohostfragment.replaceFragment()
:Tudo o que esse método faz é substituir o layout do quadro pelo id
R.id.hosted_fragment
pelo fragmento fornecido ao método.Confira meu tutorial sobre este tópico para obter mais detalhes e um exemplo de trabalho completo no GitHub!
fonte
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:
fonte
Eu criei um ViewPager com 3 elementos e 2 subelementos para o índice 2 e 3 e aqui o que eu queria fazer ..
Eu implementei isso com a ajuda de perguntas e respostas anteriores do StackOverFlow e aqui está o link.
ViewPagerChildFragments
fonte
Para substituir um fragmento dentro de um
ViewPager
você pode mover os códigos-fonte deViewPager
,PagerAdapter
eFragmentStatePagerAdapter
classes em seu projeto e adicione o seguinte código.em
ViewPager
:no FragmentStatePagerAdapter:
handleGetItemInvalidated()
garante que após a próxima chamadagetItem()
retorne newFragmentgetFragmentPosition()
retorne a posição do fragmento no seu adaptador.Agora, para substituir fragmentos, chame
Se você está interessado em um exemplo de projeto, peça-me as fontes.
fonte
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 oonBackPressed()
método da atividade . O maior inconveniente é que ele retornará apenas um de cada vez, o que pode resultar em vários cliquesEspero que isso ajude alguém.
Também no que diz respeito
getFragmentPosition()
, é praticamente ogetItem()
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:fonte
No seu
onCreateView
método,container
é realmente umaViewPager
instância.Então, apenas ligando
mudará o fragmento atual no seu
ViewPager
.fonte
vpViewPager.setCurrentItem(1);
. Eu passei pelo exemplo de cada pessoa, sem nada acontecendo toda vez, até que finalmente cheguei à sua. Obrigado.ViewPager
página para outra página, não substitui nenhum fragmento.Aqui está minha solução relativamente simples para esse problema. As chaves desta solução são usar em
FragmentStatePagerAdapter
vez de,FragmentPagerAdapter
pois o primeiro removerá fragmentos não utilizados para você, enquanto o último ainda retém suas instâncias. O segundo é o uso dePOSITION_NONE
em 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:fonte
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.Adicione isso para a funcionalidade do botão voltar em sua MainActivity:
Se você quiser salvar o estado do fragmento quando ele for removido. Deixe seu Fragment implementar a interface
SaveStateBundle
retornando na função um pacote com seu estado de salvamento. Obter o pacote após a instanciação porthis.getArguments()
.Você pode instanciar uma guia como esta:
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.
fonte
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.
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.
Há duas coisas principais a serem destacadas aqui:
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;
Você pode chamar a alteração dentro de seu
onPostExecute
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.
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
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
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
fonte
Esta é a minha maneira de conseguir isso.
Primeiro, adicione a guia
Root_fragment
internaviewPager
na qual você deseja implementar ofragment
evento de clique no botão . Exemplo;Primeiro de tudo,
RootTabFragment
deve ser incluídoFragmentLayout
para alteração de fragmento.Em seguida,
RootTabFragment
onCreateView
implementefragmentChange
para o seuFirstPagerFragment
Depois disso, implemente o
onClick
evento para o seu botão dentroFirstPagerFragment
e faça a alteração do fragmento assim novamente.Espero que isso ajude você cara.
fonte
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 substituir
getItemId()
que deve retornar um ID exclusivo para cada fragmento. Não posicionar como padrão.Aqui está:
Aviso: Neste exemplo
FirstFragment
eSecondFragment
estende-se classe abstracta PageFragment, que tem métodogetPage()
.fonte
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:
O ouvinte que eu implementei na atividade do contêiner do adaptador para colocá-lo no fragmento ao anexá-lo, esta é a atividade:
Em seguida, no fragmento, coloque o ouvinte ao anexar uma chamada:
E finalmente o ouvinte:
fonte
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
Fragment
qual é substituída. No meu caso, é um fragmento que se mantémMapView
e eu achei caro. Então, eu estou mantendo oFragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)
invés deFragment
si mesmo.Aqui está a minha implementação.
Link de demonstração aqui .. https://youtu.be/l_62uhKkLyM
Para fins de demonstração, usei 2 fragmentos
TabReplaceFragment
eDemoTab2Fragment
na posição dois. Em todos os outros casos, estou usandoDemoTabFragment
instâncias.Explicação:
Estou passando
Switch
de Activity para oDemoCollectionPagerAdapter
. Com base no estado dessa opção, exibiremos o fragmento correto. Quando a verificação do interruptor é alterada, estou ligando para oSwitchFragListener
'sonSwitchToNextFragment
método, onde eu estou mudando o valor dapagerAdapterPosChanged
variável paraPOSITION_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.
fonte
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.
fonte