Atualizar dados no ListFragment como parte do ViewPager

142

Estou usando o ViewPager de compatibilidade com a v4 no Android. My FragmentActivity possui um monte de dados que devem ser exibidos de maneiras diferentes em páginas diferentes no meu ViewPager. Até agora, tenho apenas 3 instâncias do mesmo ListFragment, mas no futuro terei 3 instâncias de ListFragments diferentes. O ViewPager está em uma tela vertical do telefone, as listas não estão lado a lado.

Agora, um botão no ListFragment inicia uma atividade de página inteira separada (via FragmentActivity), que retorna e FragmentActivity modifica os dados, salva-os e tenta atualizar todas as visualizações no ViewPager. É aqui, onde estou preso.

public class ProgressMainActivity extends FragmentActivity
{
    MyAdapter mAdapter;
    ViewPager mPager;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    ...
        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager) findViewById(R.id.viewpager);
        mPager.setAdapter(mAdapter);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        ...
        updateFragments();
        ...
    }
    public void updateFragments()
    {
        //Attempt 1:
        //mAdapter.notifyDataSetChanged();
        //mPager.setAdapter(mAdapter);

        //Attempt 2:
        //HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentById(mAdapter.fragId[0]);
        //fragment.updateDisplay();
    }

    public static class MyAdapter extends FragmentPagerAdapter implements
         TitleProvider
    {
      int[] fragId = {0,0,0,0,0};
      public MyAdapter(FragmentManager fm)
      {
         super(fm);
      }
      @Override
      public String getTitle(int position){
         return titles[position];
      }
      @Override
      public int getCount(){
         return titles.length;
      }

      @Override
      public Fragment getItem(int position)
      {

         Fragment frag = HomeListFragment.newInstance(position);
         //Attempt 2:
         //fragId[position] = frag.getId();
         return frag;
      }

      @Override
      public int getItemPosition(Object object) {
         return POSITION_NONE; //To make notifyDataSetChanged() do something
     }
   }

    public class HomeListFragment extends ListFragment
    {
    ...
        public static HomeListFragment newInstance(int num)
        {
            HomeListFragment f = new HomeListFragment();
            ...
            return f;
        }
   ...

Agora, como você pode ver, minha primeira tentativa foi notificar DataSetChanged em todo o FragmentPagerAdapter, e isso mostrou atualizar os dados algumas vezes, mas em outras eu recebi uma IllegalStateException: Não é possível executar esta ação após onSaveInstanceState.

Minha segunda tentativa envolveu tentar chamar uma função de atualização em meu ListFragment, mas getId em getItem retornou 0. Conforme os documentos que eu tentei

adquirindo uma referência ao Fragmento de FragmentManager, usando findFragmentById () ou findFragmentByTag ()

mas não sei a etiqueta ou o id dos meus fragmentos! Eu tenho um andróide: id = "@ + id / viewpager" para o ViewPager e um andróide: id = "@ android: id / list" para o meu ListView no layout ListFragment, mas não acho que sejam úteis.

Portanto, como posso: a) atualizar o ViewPager inteiro com segurança de uma só vez (o ideal é retornar o usuário à página em que ele estava antes) - tudo bem que o usuário veja a exibição mudar. Ou, de preferência, b) chame uma função em cada ListFragment afetado para atualizar o ListView manualmente.

Qualquer ajuda seria aceita com gratidão!

barkside
fonte

Respostas:

58

Tente gravar a tag sempre que um Fragement for instanciado.

public class MPagerAdapter extends FragmentPagerAdapter {
    private Map<Integer, String> mFragmentTags;
    private FragmentManager mFragmentManager;

    public MPagerAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
        mFragmentTags = new HashMap<Integer, String>();
    }

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

    @Override
    public Fragment getItem(int position) {
        return Fragment.instantiate(mContext, AFragment.class.getName(), null);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object obj = super.instantiateItem(container, position);
        if (obj instanceof Fragment) {
            // record the fragment tag here.
            Fragment f = (Fragment) obj;
            String tag = f.getTag();
            mFragmentTags.put(position, tag);
        }
        return obj;
    }

    public Fragment getFragment(int position) {
        String tag = mFragmentTags.get(position);
        if (tag == null)
            return null;
        return mFragmentManager.findFragmentByTag(tag);
    }
}
faylon
fonte
Parece bom, mas não funciona na minha turma. Isso funciona com você? Eu ainda pego nullpor fragmento.
sandalone
1
Funciona para mim, usei em vários projetos. Você pode tentar ler o código-fonte do FragmentPagerAdapter e imprimir alguns logs para depuração.
Faylon
Graças funciona como um encanto, mesmo depois de girar screens.Even eu era capaz de alterar o conteúdo do FragmentA usando MyActivity de FragmentB, usando esta solução.
Mehdi Fanai 03/04
5
Ele funciona para FragmentPagerAdapermas não para FragmentStatePagerAdaper( Fragment.getTag()sempre voltar null.
Motor Bai
1
Eu tentei esta solução, criei um método para atualizar os dados do fragmento. Mas agora não consigo ver os pontos de vista dos fragmentos. Eu posso ver que os dados estão se fragmentando, mas as visualizações não são visíveis. Qualquer ajuda?
Arun Badole 18/05/19
256

A resposta de Barkside funciona, FragmentPagerAdaptermas não funciona FragmentStatePagerAdapter, porque não define tags nos fragmentos para os quais passa FragmentManager.

Com FragmentStatePagerAdapterparece que nós podemos obter, utilizando a sua instantiateItem(ViewGroup container, int position)chamada. Retorna a referência ao fragmento na posição position. Se FragmentStatePagerAdapterjá possui referência ao fragmento em questão, instantiateItemapenas retorna a referência a esse fragmento e não chama getItem()para instancia-lo novamente.

Então, suponha, atualmente estou vendo o fragmento 50 e quero acessar o fragmento 49. Como eles estão próximos, há uma boa chance de o # 49 já ser instanciado. Assim,

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)
Pēteris Caune
fonte
5
Eu simplificaria isso apenas fazendo "a" um PagerAdapter (PagerAdapter a = pager.getAdapter ();). Ou mesmo MyFragment f49 = (MyFragment) pager.getAdapter (). InstantiateItem (pager, 49); instantiateItem () é de PagerAdapter, para que você não tem que typecast-lo para chamar instantiateItem ()
Dandre Allison
12
... e caso alguém esteja se perguntando, o ViewPager mantém o controle do índice do fragmento visualizado (ViewPager.getCurrentItem ()), que é o 49 no exemplo acima ... mas eu tenho que dizer que eu Fico surpreso que a API do ViewPager não retorne diretamente uma referência ao fragmento real.
mblackwell8
6
Com FragmentStatePagerAdapter, usar o código acima com a.instantiateItem(viewPager, viewPager.getCurrentItem());(para se livrar dos 49) como proposto por @ mblackwell8 funciona perfeitamente.
Cedric Simon
1
Espere, então eu tenho que passar o ViewPager para cada fragmento que ele contém, apenas para poder fazer coisas com as visualizações no fragmento? Atualmente, tudo o que faço no onCreate na página atual acontece na próxima página. Isso parece extremamente sombrio quanto a vazamentos.
GDV
é importante mencionar que todas as chamadas para instantiateItemdevem ser cercadas por startUpdate/ finishUpdatecalls. detalhes estão na minha resposta a uma pergunta semelhante: stackoverflow.com/questions/14035090/...
morgwai
154

OK, acho que encontrei uma maneira de executar a solicitação b) em minha própria pergunta, para compartilhar em benefício dos outros. A tag de fragmentos dentro de um ViewPager está no formulário "android:switcher:VIEWPAGER_ID:INDEX", onde VIEWPAGER_IDestá o R.id.viewpagerlayout XML, e INDEX é a posição no viewpager. Portanto, se a posição for conhecida (por exemplo, 0), posso executar updateFragments():

      HomeListFragment fragment = 
          (HomeListFragment) getSupportFragmentManager().findFragmentByTag(
                       "android:switcher:"+R.id.viewpager+":0");
      if(fragment != null)  // could be null if not instantiated yet
      {
         if(fragment.getView() != null) 
         {
            // no need to call if fragment's onDestroyView() 
            //has since been called.
            fragment.updateDisplay(); // do what updates are required
         }
      }

Não tenho idéia se essa é uma maneira válida de fazê-lo, mas funcionará até que algo melhor seja sugerido.

barkside
fonte
4
Entrei apenas para marcar com +1 sua resposta e pergunta.
SuitUp
5
como isso não está na documentação oficial, eu não o usaria. E se eles mudarem a tag em uma versão futura?
Thierry-Dimitri Roy
4
O FragmentPagerAdapter vem com um método estático makeFragmentNameusado para gerar o Tag que você poderia usar para uma abordagem um pouco menos hacky: HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
dgmltn
8
@dgmltn, makeFragmentNameé o private staticmétodo, então você não pode ter acesso a ele.
StenaviN
1
Doce mãe de Jesus, isso funciona! Vou manter meu dedo cruzado e usá-lo.
Bogdan Alexandru
35

Se você me perguntar, a segunda solução na página abaixo, acompanhando todas as páginas de fragmentos "ativas", é melhor: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

A resposta do barkside é muito hacky para mim.

você acompanha todas as páginas de fragmentos "ativas". Nesse caso, você acompanha as páginas do fragmento no FragmentStatePagerAdapter, usado pelo ViewPager.

private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();

public Fragment getItem(int index) {
    Fragment myFragment = MyFragment.newInstance();
    mPageReferences.put(index, myFragment);
    return myFragment;
}

Para evitar manter uma referência a páginas de fragmento "inativas", precisamos implementar o método destroyItem (...) do FragmentStatePagerAdapter:

public void destroyItem(View container, int position, Object object) {
    super.destroyItem(container, position, object);
    mPageReferences.remove(position);
}

... e quando você precisar acessar a página visível no momento, ligue para:

int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);

... onde o método getFragment (int) do MyAdapter se parece com o seguinte:

public MyFragment getFragment(int key) {
    return mPageReferences.get(key);
}

"

Frank
fonte
@Frank segunda solução na ligação é simples ... Obrigado .. :)!
theflash
3
Melhor se você usar um SparseArray em vez de um mapa
GaRRaPeTa
atualizei a minha resposta para que ele usa um SparseArray, se você segmentar <11, use um SparseArrayCompat vez porque a classe que atualizado na versão 11.
Frank
2
Isso interromperá os eventos do ciclo de vida. Veja stackoverflow.com/a/24662049/236743
Dori
13

Ok, depois de testar o método pelo @barkside acima, não consegui fazê-lo funcionar com meu aplicativo. Lembrei-me de que o aplicativo IOSched2012 também usa um viewpager, e foi aí que encontrei minha solução. Ele não usa nenhum ID ou Tags de fragmento, pois eles não são armazenados de viewpagermaneira facilmente acessível.

Aqui estão as partes importantes dos aplicativos iOS HomeActivity. Preste atenção especial ao comentário , pois é a chave:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // Since the pager fragments don't have known tags or IDs, the only way to persist the
    // reference is to use putFragment/getFragment. Remember, we're not persisting the exact
    // Fragment instance. This mechanism simply gives us a way to persist access to the
    // 'current' fragment instance for the given fragment (which changes across orientation
    // changes).
    //
    // The outcome of all this is that the "Refresh" menu button refreshes the stream across
    // orientation changes.
    if (mSocialStreamFragment != null) {
        getSupportFragmentManager().putFragment(outState, "stream_fragment",
                mSocialStreamFragment);
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    if (mSocialStreamFragment == null) {
        mSocialStreamFragment = (SocialStreamFragment) getSupportFragmentManager()
                .getFragment(savedInstanceState, "stream_fragment");
    }
}

E armazene instâncias suas Fragmentsda seguinte FragmentPagerAdaptermaneira:

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

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return (mMyScheduleFragment = new MyScheduleFragment());

            case 1:
                return (mExploreFragment = new ExploreFragment());

            case 2:
                return (mSocialStreamFragment = new SocialStreamFragment());
        }
        return null;
    }

Além disso, lembre-se de proteger suas Fragmentchamadas da seguinte maneira:

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }
Ryan R
fonte
Ryan, isso significa que você está substituindo as funções que o ViewPager usa para 'manter' suas instâncias de fragmento e definindo-as para uma variável pública que é acessível? Por que na instância de restauração você possui if (mSocialStreamFragment == null) {?
Thomas Clowes
1
esta é uma boa resposta e +1 para o aplicativo io12 não é certo se isso funcionará durante as alterações do ciclo de vida, devido ao fato de getItem()não ser chamado depois que o pai for recriado devido a uma alteração no ciclo de vida. Consulte stackoverflow.com/a/24662049/236743 . No exemplo presente é parcialmente protegida contra por um fragmento, mas não os outros dois
Dori
2

Você pode copiar FragmentPagerAdaptere modificar algum código fonte, adicionar getTag()método

por exemplo

public abstract class AppFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;

private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;

public AppFragmentPagerAdapter(FragmentManager fm) {
    mFragmentManager = fm;
}


public abstract Fragment getItem(int position);

@Override
public void startUpdate(ViewGroup container) {
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    final long itemId = getItemId(position);


    String name = getTag(position);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);

        mCurTransaction.add(container.getId(), fragment,
                getTag(position));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment) object).getView());
    mCurTransaction.detach((Fragment) object);
}

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            mCurrentPrimaryItem.setUserVisibleHint(false);
        }
        if (fragment != null) {
            fragment.setMenuVisibility(true);
            fragment.setUserVisibleHint(true);
        }
        mCurrentPrimaryItem = fragment;
    }
}

@Override
public void finishUpdate(ViewGroup container) {
    if (mCurTransaction != null) {
        mCurTransaction.commitAllowingStateLoss();
        mCurTransaction = null;
        mFragmentManager.executePendingTransactions();
    }
}

@Override
public boolean isViewFromObject(View view, Object object) {
    return ((Fragment) object).getView() == view;
}

@Override
public Parcelable saveState() {
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}


public long getItemId(int position) {
    return position;
}

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

protected abstract String getTag(int position);
}

depois estenda-o, substitua esse método abstrato, não precisa ter medo da alteração do grupo Android

FragmentPageAdapter código fonte no futuro

 class TimeLinePagerAdapter extends AppFragmentPagerAdapter {


    List<Fragment> list = new ArrayList<Fragment>();


    public TimeLinePagerAdapter(FragmentManager fm) {
        super(fm);
        list.add(new FriendsTimeLineFragment());
        list.add(new MentionsTimeLineFragment());
        list.add(new CommentsTimeLineFragment());
    }


    public Fragment getItem(int position) {
        return list.get(position);
    }

    @Override
    protected String getTag(int position) {
        List<String> tagList = new ArrayList<String>();
        tagList.add(FriendsTimeLineFragment.class.getName());
        tagList.add(MentionsTimeLineFragment.class.getName());
        tagList.add(CommentsTimeLineFragment.class.getName());
        return tagList.get(position);
    }


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


}
Jiang Qi
fonte
1

Também funciona sem problemas:

em algum lugar no layout do fragmento de página:

<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="@+id/fragment_reference">
     <View android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/>
</FrameLayout>

no onCreateView () do fragmento:

...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;

e método para retornar Fragment do ViewPager, se existir:

public Fragment getFragment(int pageIndex) {        
        View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
        if (w == null) return null;
        View r = (View) w.getParent();
        return (Fragment) r.getTag();
}
Hox
fonte
1

Como alternativa, você pode substituir o setPrimaryItemmétodo da seguinte FragmentPagerAdaptermaneira:

public void setPrimaryItem(ViewGroup container, int position, Object object) { 
    if (mCurrentFragment != object) {
        mCurrentFragment = (Fragment) object; //Keep reference to object
        ((MyInterface)mCurrentFragment).viewDidAppear();//Or call a method on the fragment
    }

    super.setPrimaryItem(container, position, object);
}

public Fragment getCurrentFragment(){
    return mCurrentFragment;
}
Vlad Spreys
fonte
0

Quero dar a minha abordagem, caso possa ajudar mais alguém:

Este é o meu adaptador de pager:

 public class CustomPagerAdapter extends FragmentStatePagerAdapter{
    private Fragment[] fragments;

    public CustomPagerAdapter(FragmentManager fm) {
        super(fm);
        fragments = new Fragment[]{
                new FragmentA(),
                new FragmentB()
        };
    }

    @Override
    public Fragment getItem(int arg0) {
        return fragments[arg0];
    }

    @Override
    public int getCount() {
        return fragments.length;
    }

}

Na minha atividade eu tenho:

public class MainActivity {
        private ViewPager view_pager;
        private CustomPagerAdapter adapter;


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

        adapter = new CustomPagerAdapter(getSupportFragmentManager());
        view_pager = (ViewPager) findViewById(R.id.pager);
        view_pager.setAdapter(adapter);
        view_pager.setOnPageChangeListener(this);
          ...
         }

}

Então, para obter o fragmento atual, o que faço é:

int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);
kiduxa
fonte
1
isso vai quebrar depois do ciclo de vida / métodos de fragmentos de actividade
DORI
0

Esta é a minha solução, pois não preciso acompanhar minhas guias e preciso atualizá-las de qualquer maneira.

    Bundle b = new Bundle();
    b.putInt(Constants.SharedPreferenceKeys.NUM_QUERY_DAYS,numQueryDays);
    for(android.support.v4.app.Fragment f:getSupportFragmentManager().getFragments()){
        if(f instanceof HomeTermFragment){
            ((HomeTermFragment) f).restartLoader(b);
        }
    }
abhi08638
fonte