Respostas:
A resposta principal depende de um nome que está sendo gerado pela estrutura. Se isso mudar, não funcionará mais.
Que tal esta solução, substituindo instantiateItem()
e destroyItem()
sua Fragment(State)PagerAdapter
:
public class MyPagerAdapter extends FragmentStatePagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return ...;
}
@Override
public Fragment getItem(int position) {
return MyFragment.newInstance(...);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}
Isso parece funcionar para mim ao lidar com fragmentos que estão disponíveis. Fragmentos que ainda não foram instanciados retornarão nulos ao chamar getRegisteredFragment
. Mas eu tenho usado isso principalmente para obter a corrente Fragment
do ViewPager
: adapater.getRegisteredFragment(viewPager.getCurrentItem())
e isso não retornará null
.
Não conheço outras desvantagens dessa solução. Se houver, eu gostaria de saber.
MyPagerAdapter
é destruído devido ao ciclo de vida (ou seja, girar), não registerdFragments
será perdido? OActivity
/ Fragment
using MyPagerAdapter
precisará salvá-lo onSaveInstanceState
e precisará atualizá-lo com a nova FragmentManager
ref?
getItem
não é chamado novamente ao rodar (para quaisquer frags que já foram criados), pois FragmentManager
restaura os estados do Fragments
mesmo no pager. E seinstantiateItem
for chamado quando cada um Fragment
for restaurado, essa solução será de fato mais segura e mais futura do que a minha ou a resposta aceita. Vou considerar tentar isso sozinho.
Para pegar fragmentos de um ViewPager, existem muitas respostas aqui e em outros tópicos / blogs relacionados ao SO. Todo mundo que eu vi está quebrado, e eles geralmente parecem se encaixar em um dos dois tipos listados abaixo. Existem outras soluções válidas se você quiser apenas pegar o fragmento atual, como esta outra resposta neste tópico.
Se estiver usando FragmentPagerAdapter
veja abaixo. Se usar o FragmentStatePagerAdapter
seu vale a pena olhar para isso . Os índices de captura que não são os atuais em um FragmentStateAdapter não são tão úteis quanto, por natureza, serão completamente reduzidos e ficaram fora de vista / fora dos limites offScreenLimit.
Errado: mantenha sua própria lista interna de fragmentos, adicionada a quando FragmentPagerAdapter.getItem()
é chamada
SparseArray
ouMap
getItem
é chamado apenas na primeira vez em que uma página é rolada para (ou obtida se seu ViewPager.setOffscreenPageLimit(x)
> 0) no ViewPager
, se o host Activity
/Fragment
for interrompida ou reiniciada, o interno SpaseArray
será eliminado quando o FragmentPagerActivity personalizado for recriado, mas nos bastidores o ViewPagers fragmentos internos vai ser recriada e getItem
vai NÃO ser chamado para qualquer um dos índices, de modo a capacidade de obter um fragmento de índice será perdido para sempre. Você pode explicar isso salvando e restaurando essas referências de fragmentos via FragmentManager.getFragment()
e, putFragment
mas isso começa a ficar IMHO bagunçado.Errado: construa seu próprio ID de tag correspondente ao que é usado sob o capô no FragmentPagerAdapter
e use-o para recuperar os fragmentos de página doFragmentManager
ViewPager
que poderia alterar a qualquer momento ou para qualquer versão do sistema operacional.O método recriado para esta solução é
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
ViewPager.instantiateItem()
Uma abordagem semelhante à getItem()
anterior, mas que não interrompe o ciclo de vida, é a de conectar-seinstantiateItem()
invés degetItem()
como o primeiro será chamado toda vez que o índice for criado / acessado. Veja esta resposta
FragmentViewPager
Construa sua própria FragmentViewPager
classe a partir da fonte da última lib de suporte e altere o método usado internamente para gerar as tags de fragmento. Você pode substituí-lo pelo abaixo. Isso tem a vantagem de que você sabe que a criação da tag nunca será alterada e que você não depende de uma API / método particular, o que é sempre perigoso.
/**
* @param containerViewId the ViewPager this adapter is being supplied to
* @param id pass in getItemId(position) as this is whats used internally in this class
* @return the tag used for this pages fragment
*/
public static String makeFragmentName(int containerViewId, long id) {
return "android:switcher:" + containerViewId + ":" + id;
}
Então, como o documento diz, quando você quiser pegar um fragmento usado para um índice, chame algo como este método (que você pode colocar no costume FragmentPagerAdapter
ou em uma subclasse) sabendo que o resultado pode ser nulo se getItem ainda não foi chamado. essa página, ou seja, ainda não foi criada.
/**
* @return may return null if the fragment has not been instantiated yet for that position - this depends on if the fragment has been viewed
* yet OR is a sibling covered by {@link android.support.v4.view.ViewPager#setOffscreenPageLimit(int)}. Can use this to call methods on
* the current positions fragment.
*/
public @Nullable Fragment getFragmentForPosition(int position)
{
String tag = makeFragmentName(mViewPager.getId(), getItemId(position));
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
return fragment;
}
Esta é uma solução simples e resolve os problemas das outras duas soluções encontradas em todos os lugares na Web
instantiateItem()
quando você deseja acessar um fragmento que ainda não foi visitado após a ocorrência de um evento do ciclo de vida e foi visitado antes do evento do ciclo de vida. Uma pequena diferença que eu conheço, mas que levou a um bug sutil no meu programa, portanto, eu estou investigando isso.
getItemId(pos)
interiorFragmentStatePagerAdapter
Adicione os próximos métodos ao seu FragmentPagerAdapter:
public Fragment getActiveFragment(ViewPager container, int position) {
String name = makeFragmentName(container.getId(), position);
return mFragmentManager.findFragmentByTag(name);
}
private static String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
getActiveFragment (0) tem que funcionar.
Aqui está a solução implementada no ViewPager https://gist.github.com/jacek-marchwicki/d6320ba9a910c514424d . Se algo falhar, você verá um bom log de falha.
Outra solução simples:
public class MyPagerAdapter extends FragmentPagerAdapter {
private Fragment mCurrentFragment;
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
//...
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (getCurrentFragment() != object) {
mCurrentFragment = ((Fragment) object);
}
super.setPrimaryItem(container, position, object);
}
}
setPrimaryItem()
é chamado depois ViewPager.OnPageChangeListener#onPageSelected()
eu não posso usá-lo :-(
Eu sei que isso tem algumas respostas, mas talvez isso ajude alguém. Eu tenho usado uma solução relativamente simples quando eu precisava para obter um Fragment
do meu ViewPager
. No seu Activity
ou Fragment
segurando o ViewPager
, você pode usar esse código para percorrer cada Fragment
um deles.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = fragmentPagerAdapter.getItem(i);
if(viewPagerFragment != null) {
// Do something with your Fragment
// Check viewPagerFragment.isResumed() if you intend on interacting with any views.
}
}
Se você souber a sua posição Fragment
no ViewPager
, você pode simplesmente ligar getItem(knownPosition)
.
Se você não sabe a sua posição Fragment
no ViewPager
, você pode fazer com que seus filhos Fragments
implementem uma interface com um método como esse getUniqueId()
e use isso para diferenciá-los. Ou você pode percorrer tudo Fragments
e verificar o tipo de classe, comoif(viewPagerFragment instanceof FragmentClassYouWant)
!!! EDIT !!!
Eu descobri que getItem
só é chamado por FragmentPagerAdapter
quando cada um Fragment
precisa ser criado na primeira vez , depois disso, parece que o Fragments
são reciclados usando o FragmentManager
. Dessa forma, muitas implementações de FragmentPagerAdapter
criação de novos Fragment
s getItem
. Usando o meu método acima, isso significa que criaremos novos Fragment
s cada vez que getItem
for chamado à medida que passarmos por todos os itens no FragmentPagerAdapter
. Devido a isso, eu encontrei uma abordagem melhor, usando o FragmentManager
para obter cada um Fragment
(usando a resposta aceita). Esta é uma solução mais completa e está funcionando bem para mim.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
String name = makeFragmentName(mViewPager.getId(), i);
Fragment viewPagerFragment = getChildFragmentManager().findFragmentByTag(name);
// OR Fragment viewPagerFragment = getFragmentManager().findFragmentByTag(name);
if(viewPagerFragment != null) {
// Do something with your Fragment
if (viewPagerFragment.isResumed()) {
// Interact with any views/data that must be alive
}
else {
// Flag something for update later, when this viewPagerFragment
// returns to onResume
}
}
}
E você precisará deste método.
private static String makeFragmentName(int viewId, int position) {
return "android:switcher:" + viewId + ":" + position;
}
ViewPager
próprio define essas tags. Esta solução é frágil porque depende do conhecimento da convenção de nomenclatura "oculta" do ViewPager
. A resposta do Streets of Boston é uma solução muito mais abrangente, que agora uso no meu projeto (em vez dessa abordagem).
Para o meu caso, nenhuma das soluções acima funcionou.
No entanto, como estou usando o Gerenciador de fragmentos filho em um fragmento, o seguinte foi usado:
Fragment f = getChildFragmentManager().getFragments().get(viewPager.getCurrentItem());
Isso funcionará apenas se seus fragmentos no Gerenciador corresponderem ao item do viewpager.
Para obter o fragmento Visible atual do ViewPager . Estou usando esta declaração simples e está funcionando bem.
public Fragment getFragmentFromViewpager()
{
return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem())));
}
Eu lidei com isso primeiro fazendo uma lista de todos os fragmentos (List<Fragment> fragments;
) que eu iria usar e os adicionei ao pager, facilitando o manuseio do fragmento atualmente exibido.
Assim:
@Override
onCreate(){
//initialise the list of fragments
fragments = new Vector<Fragment>();
//fill up the list with out fragments
fragments.add(Fragment.instantiate(this, MainFragment.class.getName()));
fragments.add(Fragment.instantiate(this, MenuFragment.class.getName()));
fragments.add(Fragment.instantiate(this, StoresFragment.class.getName()));
fragments.add(Fragment.instantiate(this, AboutFragment.class.getName()));
fragments.add(Fragment.instantiate(this, ContactFragment.class.getName()));
//Set up the pager
pager = (ViewPager)findViewById(R.id.pager);
pager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments));
pager.setOffscreenPageLimit(4);
}
então isso pode ser chamado:
public Fragment getFragment(ViewPager pager){
Fragment theFragment = fragments.get(pager.getCurrentItem());
return theFragment;
}
então eu poderia jogá-lo em uma instrução if que só seria executada se estivesse no fragmento correto
Fragment tempFragment = getFragment();
if(tempFragment == MyFragmentNo2.class){
MyFragmentNo2 theFrag = (MyFragmentNo2) tempFragment;
//then you can do whatever with the fragment
theFrag.costomFunction();
}
mas essa é apenas a minha abordagem de hack e slash, mas funcionou para mim; eu a uso para fazer alterações relevantes no fragmento exibido no momento quando o botão Voltar é pressionado.
Isso é baseado na resposta de Steven acima. Isso retornará a instância real do fragmento que já está anexado à atividade pai.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = (Fragment) mViewPager.getAdapter().instantiateItem(mViewPager, i);
if(viewPagerFragment != null && viewPagerFragment.isAdded()) {
if (viewPagerFragment instanceof FragmentOne){
FragmentOne oneFragment = (FragmentOne) viewPagerFragment;
if (oneFragment != null){
oneFragment.update(); // your custom method
}
} else if (viewPagerFragment instanceof FragmentTwo){
FragmentTwo twoFragment = (FragmentTwo) viewPagerFragment;
if (twoFragment != null){
twoFragment.update(); // your custom method
}
}
}
}
Não consegui encontrar uma maneira simples e limpa de fazer isso. No entanto, o widget ViewPager é apenas outro ViewGroup, que hospeda seus fragmentos. O ViewPager possui esses fragmentos como filhos imediatos. Portanto, você pode iterar sobre eles (usando .getChildCount () e .getChildAt ()) e ver se a instância de fragmento que você está procurando está atualmente carregada no ViewPager e obter uma referência a ela. Por exemplo, você pode usar algum campo de ID exclusivo estático para diferenciar os fragmentos.
Observe que o ViewPager pode não ter carregado o fragmento que você está procurando, pois é um contêiner de virtualização como o ListView.
FragmentPagerAdapter é a fábrica dos fragmentos. Para encontrar um fragmento com base em sua posição, se ainda estiver na memória, use isto:
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
Código de amostra para a API de suporte da v4.
Você não precisa chamar getItem()
ou algum outro método posteriormente para obter a referência de um Fragment
host hospedado ViewPager
. Se você deseja atualizar alguns dados internos Fragment
, use esta abordagem: Atualizar o ViewPager dinamicamente?
A chave é definir novos dados dentro Adaper
e chamar, notifyDataSetChanged()
que por sua vez chamará getItemPosition()
, passando uma referência sua Fragment
e dando a você a chance de atualizá-la. Todas as outras formas exigem que você mantenha referência a si mesmo ou a algum outro truque, o que não é uma boa solução.
@Override
public int getItemPosition(Object object) {
if (object instanceof UpdateableFragment) {
((UpdateableFragment) object).update(xyzData);
}
//don't return POSITION_NONE, avoid fragment recreation.
return super.getItemPosition(object);
}
getItemPosition()
é um método no seu adaptador. Você não vai ligar diretamente. Você tem uma referência do seu adaptador, para adapter.notifyDataSetChanged()
ligar e ligar getItemPosition()
para o seu adaptador, passando a referência dos seus Fragment
s. Você pode ver uma implementação completa em ação aqui stackoverflow.com/questions/19891828/…
Fragment
mas como obter uma referência é passível de debate. Outras soluções obter referência de FragmentManager
usar uma corda semelhante a "android:switcher:"+id
ou retornando POSITION_NONE
de getItemosition()
causando todos os fragmentos para recriar-se.
Deve se estender FragmentPagerAdapter
à sua classe de adaptador do ViewPager.
Se você usar FragmentStatePagerAdapter
, não poderá encontrar o seu Fragment
pelo seuID
public static String makeFragmentName(int viewPagerId, int index) {
return "android:switcher:" + viewPagerId + ":" + index;
}
Como usar este método: -
Fragment mFragment = ((FragmentActivity) getContext()).getSupportFragmentManager().findFragmentByTag(
AppMethodUtils.makeFragmentName(mViewPager.getId(), i)
);
InterestViewFragment newFragment = (InterestViewFragment) mFragment;
Ei, eu respondi a esta pergunta aqui . Basicamente, você precisa substituir
public Object instantiateItem (container ViewGroup, int position)
método de FragmentStatePagerAdapter.
A melhor solução é usar a extensão que criamos no CodePath chamada SmartFragmentStatePagerAdapter . Seguindo esse guia, isso facilita a recuperação de fragmentos e do fragmento atualmente selecionado de um ViewPager. Ele também faz um trabalho melhor de gerenciar a memória dos fragmentos incorporados no adaptador.
A maneira mais fácil e concisa. Se todos os seus fragmentos ViewPager
forem de classes diferentes, você poderá recuperá-los e diferenciá-los da seguinte maneira:
public class MyActivity extends Activity
{
@Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment.getClass() == MyFragment.class) {
mMyFragment = (MyFragment) fragment;
}
}
}
Eu implementei isso facilmente com uma abordagem um pouco diferente.
Meu método FragmentAdapter.getItem personalizado não retornou novo MyFragment (), mas a instância de MyFragment que foi criada no construtor FragmentAdapter.
Na minha atividade, obtive o fragmento do adaptador, verifique se ele é instanceOf needed Fragment e, em seguida, projete e use os métodos necessários.
Crie um ID de recurso inteiro em /values/integers.xml
<integer name="page1">1</integer>
<integer name="page2">2</integer>
<integer name="page3">3</integer>
Em seguida, na função getItem do PagerAdapter:
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = FragmentOne.newInstance();
mViewPager.setTag(R.integer.page1,fragment);
}
else if (position == 1) {
fragment = FragmentTwo.newInstance();
mViewPager.setTag(R.integer.page2,fragment);
} else if (position == 2) {
fragment = FragmentThree.newInstance();
mViewPager.setTag(R.integer.page3,fragment);
}
return fragment;
}
Em atividade, escreva esta função para obter uma referência de fragmento:
private Fragment getFragmentByPosition(int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = (Fragment) mViewPager.getTag(R.integer.page1);
break;
case 1:
fragment = (Fragment) mViewPager.getTag(R.integer.page2);
break;
case 2:
fragment = (Fragment) mViewPager.getTag(R.integer.page3);
break;
}
return fragment;
}
Obtenha a referência do fragmento chamando a função acima e, em seguida, faça a conversão no seu fragmento personalizado:
Fragment fragment = getFragmentByPosition(position);
if (fragment != null) {
FragmentOne fragmentOne = (FragmentOne) fragment;
}
Maneira fácil de iterar sobre fragmentos no gerenciador de fragmentos. Localize o viewpager, que possui argumento de posição de seção, colocado em público estático PlaceholderFragment newInstance (int sectionNumber) .
public PlaceholderFragment getFragmentByPosition(Integer pos){
for(Fragment f:getChildFragmentManager().getFragments()){
if(f.getId()==R.id.viewpager && f.getArguments().getInt("SECTNUM") - 1 == pos) {
return (PlaceholderFragment) f;
}
}
return null;
}
Em Fragmento
public int getArgument(){
return mPage;
{
public void update(){
}
Em FragmentActivity
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for(Fragment f:fragments){
if((f instanceof PageFragment)&&(!f.isDetached())){
PageFragment pf = (PageFragment)f;
if(pf.getArgument()==pager.getCurrentItem())pf.update();
}
}
No TabLayout, existem várias guias para Fragment. você pode encontrar o fragmento por Tag usando o índice do fragmento.
Por ex. o índice para o fragmento1 é 0, portanto, no findFragmentByTag()
método, passe a tag para o Viewpager.after usando fragmentTransaction que você pode adicionar, substitua o fragmento.
String tag = "android:switcher:" + R.id.viewPager + ":" + 0;
Fragment1 f = (Fragment1) getSupportFragmentManager().findFragmentByTag(tag);
Ok para o adaptador FragmentStatePagerAdapter
, financio uma solução:
em seu FragmentActivity:
ActionBar mActionBar = getSupportActionBar();
mActionBar.addTab(mActionBar.newTab().setText("TAB1").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment1.class.getName())));
mActionBar.addTab(mActionBar.newTab().setText("TAB2").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment2.class.getName())));
mActionBar.addTab(mActionBar.newTab().setText("TAB3").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment3.class.getName())));
viewPager = (STViewPager) super.findViewById(R.id.viewpager);
mPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), mActionBar);
viewPager.setAdapter(this.mPagerAdapter);
e crie um método na sua classe FragmentActivity - para que esse método lhe dê acesso ao seu fragmento, você só precisa fornecer a posição do fragmento que deseja:
public Fragment getActiveFragment(int position) {
String name = MyPagerAdapter.makeFragmentName(position);
return getSupportFragmentManager().findFragmentByTag(name);
}
no seu adaptador:
public class MyPagerAdapter extends FragmentStatePagerAdapter {
private final ActionBar actionBar;
private final FragmentManager fragmentManager;
public MyPagerAdapter(FragmentManager fragmentManager, com.actionbarsherlock.app.ActionBarActionBar mActionBar) {super(fragmentManager);
this.actionBar = mActionBar;
this.fragmentManager = fragmentManager;
}
@Override
public Fragment getItem(int position) {
getSupportFragmentManager().beginTransaction().add(mTchatDetailsFragment, makeFragmentName(position)).commit();
return (Fragment)this.actionBar.getTabAt(position);
}
@Override
public int getCount() {
return this.actionBar.getTabCount();
}
@Override
public CharSequence getPageTitle(int position) {
return this.actionBar.getTabAt(position).getText();
}
private static String makeFragmentName(int viewId, int index) {
return "android:fragment:" + index;
}
}
Fragment yourFragment = yourviewpageradapter.getItem(int index);
index
é o lugar do fragmento no adaptador como você adicionou fragment1
primeiro, então volte a fragment1
passar index
como 0 e assim por diante
Fragment
em umWeakReference
para garantir que você não impedem que um fragmento destruída de ser lixo coletado? Apenas parece que a coisa certa a fazer ...