android - with - Actualizar datos en ListFragment como parte de ViewPager
viewpager not showing fragment (10)
Estoy usando la compatibilidad con v4 ViewPager en Android. My FragmentActivity tiene una gran cantidad de datos que se mostrarán de diferentes maneras en diferentes páginas en mi ViewPager. Hasta ahora solo tengo 3 instancias del mismo ListFragment, pero en el futuro tendré 3 instancias de ListFragments diferentes. El ViewPager está en una pantalla vertical del teléfono, las listas no están una al lado de la otra.
Ahora, un botón en ListFragment inicia una actividad de página completa separada (a través de FragmentActivity), que regresa y FragmentActivity modifica los datos, los guarda y luego intenta actualizar todas las vistas en su ViewPager. Está aquí, donde estoy atrapado.
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;
}
...
Ahora, como pueden ver, mi primer intento fue notificarDataSetChanged en todo el FragmentPagerAdapter, y esto mostró la actualización de los datos algunas veces, pero otros obtuve una IllegalStateException: no puedo realizar esta acción después de onSaveInstanceState.
Mi segundo intento involed tratando de llamar a una función de actualización en mi ListFragment, pero getId en getItem devuelto 0. Según los documentos que probé por
adquiriendo una referencia al Fragment de FragmentManager, usando findFragmentById () o findFragmentByTag ()
¡pero no sé la etiqueta o id de mis Fragmentos! Tengo un android: id = "@ + id / viewpager" para ViewPager, y un android: id = "@ android: id / list" para mi ListView en el diseño ListFragment, pero no creo que estos sean útiles.
Entonces, ¿cómo puedo: a) actualizar todo el ViewPager de una sola vez (lo ideal es devolver al usuario a la página en la que estaba antes) - está bien que el usuario vea el cambio en la vista. O preferiblemente, b) llame a una función en cada ListFragment afectado para actualizar el ListView manualmente.
¡Cualquier ayuda sería aceptada con gratitud!
Alternativamente, puede anular el método setPrimaryItem
de FragmentPagerAdapter
manera:
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;
}
De acuerdo, creo que he encontrado una manera de realizar la solicitud b) en mi propia pregunta, así que la compartiré en beneficio de los demás. La etiqueta de fragmentos dentro de un ViewPager tiene el formato "android:switcher:VIEWPAGER_ID:INDEX"
, donde VIEWPAGER_ID
es el R.id.viewpager
en el diseño XML, e INDEX es la posición en el viewpager. Entonces, si la posición es conocida (por ejemplo, 0), puedo realizarme en 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
}
}
No tengo idea si esta es una forma válida de hacerlo, pero funcionará hasta que se sugiera algo mejor.
De acuerdo, después de probar el método de @barkside anterior, no pude hacer que funcione con mi aplicación. Entonces recordé que la aplicación IOSched2012 también usa un viewpager
, y ahí es donde encontré mi solución. No utiliza ningún identificador de fragmentos o etiquetas ya que estos no son almacenados por viewpager
de una manera fácilmente accesible.
Aquí están las partes importantes de las aplicaciones IOSched HomeActivity. Presta especial atención al comentario , ya que ahí está la clave:
@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");
}
}
Y almacene instancias de usted Fragments
en el FragmentPagerAdapter
como sigue:
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;
}
Además, recuerde guardar sus llamadas al Fragment
así:
if (mSocialStreamFragment != null) {
mSocialStreamFragment.refresh();
}
Esta es mi solución ya que no necesito hacer un seguimiento de mis pestañas y necesito actualizarlas de todos modos.
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);
}
}
Intente grabar la etiqueta cada vez que se crea una instancia de Fragement.
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);
}
}
La respuesta de Barkside funciona con FragmentPagerAdapter
pero no funciona con FragmentStatePagerAdapter
, porque no establece etiquetas en los fragmentos que pasa a FragmentManager
.
Con FragmentStatePagerAdapter
parece que podemos salir adelante, utilizando su instantiateItem(ViewGroup container, int position)
. Devuelve la referencia al fragmento en la posición de position
. Si FragmentStatePagerAdapter
ya contiene referencia al fragmento en cuestión, instantiateItem
simplemente devuelve referencia a ese fragmento, y no llama a getItem()
para instanciarlo de nuevo.
Entonces, supongamos, actualmente estoy viendo el fragmento # 50 y quiero acceder al fragmento # 49. Dado que están cerca, hay una buena posibilidad de que el # 49 ya esté instanciado. Asi que,
ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)
Puede copiar FragmentPagerAdapter
y modificar el código fuente, agregar el método getTag()
por ejemplo
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);
}
luego, pruébelo, anule este método abstracto, no tenga que temer a Android Group change
Código fuente FragmentPageAdapter
en el 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();
}
}
Quiero dar mi enfoque en caso de que pueda ayudar a alguien más:
Este es mi adaptador de buscapersonas:
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;
}
}
En mi actividad tengo:
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);
...
}
}
Luego, para obtener el fragmento actual, lo que hago es:
int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);
Si me preguntas, la segunda solución en la página de abajo, haciendo un seguimiento de todas las páginas de fragmentos "activos", es mejor: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part-ii.html
La respuesta de Barkside es demasiado hacky para mí.
realiza un seguimiento de todas las páginas de fragmentos "activos". En este caso, realiza un seguimiento de las páginas de fragmentos en FragmentStatePagerAdapter, que es utilizado por 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 mantener una referencia a las páginas de fragmentos "inactivos", debemos implementar el método destroyItem (...) de FragmentStatePagerAdapter:
public void destroyItem(View container, int position, Object object) {
super.destroyItem(container, position, object);
mPageReferences.remove(position);
}
... y cuando necesita acceder a la página visible actualmente, luego llama:
int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);
... donde el método getFragment (int) de MyAdapter se ve así:
public MyFragment getFragment(int key) {
return mPageReferences.get(key);
}
"
También funciona sin problemas:
en algún lugar del diseño del 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>
en el fragmento onCreateView ():
...
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;
y método para devolver Fragment from ViewPager, si existe:
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();
}