Actualizar datos en ListFragment como parte de ViewPager

142

Estoy usando la compatibilidad 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. ViewPager está en una pantalla de teléfono vertical, 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. Es 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 puede ver, mi primer intento fue notificar aDataSetChanged en todo el FragmentPagerAdapter, y esto demostró actualizar los datos a veces, pero otros obtuve una IllegalStateException: no se puede realizar esta acción después de onSaveInstanceState.

Mi segundo intento invocó el intento de llamar a una función de actualización en mi ListFragment, pero getId en getItem devolvió 0. Según los documentos que probé por

adquirir una referencia al Fragment de FragmentManager, usando findFragmentById () o findFragmentByTag ()

¡pero no sé la etiqueta o la identificación 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 sean útiles.

Entonces, ¿cómo puedo: a) actualizar todo el ViewPager de forma segura de una sola vez (idealmente, devolver al usuario a la página en la que estaba antes): está bien que el usuario vea el cambio de vista. O preferiblemente, b) llame a una función en cada ListFragment afectado para actualizar el ListView manualmente.

Cualquier ayuda sería gratamente aceptada!

lado de la corteza
fuente

Respuestas:

58

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);
    }
}
faylon
fuente
Esto se ve bien pero no funciona en mi clase. ¿Funciona contigo? Todavía me sale nullpor fragmento.
sandalone
1
A mí me funciona, lo usé en varios proyectos. Puede intentar leer el código fuente de FragmentPagerAdapter e imprimir algunos registros para depurar.
faylon
Gracias funciona de maravilla incluso después de rotar las pantallas. Incluso pude cambiar el contenido de FragmentA usando MyActivity de FragmentB, usando esta solución.
Mehdi Fanai
55
Funciona FragmentPagerAdaperpero falla FragmentStatePagerAdaper( Fragment.getTag()siempre regresa null.
Engine Bai
1
Probé esta solución, creé un método para actualizar los datos del fragmento. Pero ahora no puedo ver las vistas del fragmento. Puedo ver que los datos se van a fragmentar pero las vistas no son visibles. ¿Alguna ayuda?
Arun Badole
256

La respuesta de Barkside funciona con, FragmentPagerAdapterpero no funciona FragmentStatePagerAdapter, porque no establece etiquetas en los fragmentos a los que pasa FragmentManager.

Con FragmentStatePagerAdapterlo que parece que podemos pasar, utilizando su instantiateItem(ViewGroup container, int position)llamada. Devuelve la referencia al fragmento en la posición position. Si FragmentStatePagerAdapterya contiene una referencia al fragmento en cuestión, instantiateItemsolo devuelve la referencia a ese fragmento y no llama getItem()para instanciarlo nuevamente.

Entonces, supongamos que actualmente estoy mirando el fragmento # 50, y quiero acceder al fragmento # 49. Ya que están cerca, hay una buena posibilidad de que el # 49 ya esté instanciado. Entonces,

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)
Pēteris Caune
fuente
55
Lo simplificaría simplemente haciendo "a" un PagerAdapter (PagerAdapter a = pager.getAdapter ();). O incluso MyFragment f49 = (MyFragment) pager.getAdapter (). InstantiateItem (pager, 49); instantiateItem () es de PagerAdapter, por lo que no tiene que escribirlo para llamar a instantiateItem ()
Dandre Allison
12
... y en caso de que alguien se pregunte, ViewPager realiza un seguimiento del índice del fragmento visto (ViewPager.getCurrentItem ()), que es el 49 en el ejemplo anterior ... pero debo decir que yo Me sorprende que la API de ViewPager no devuelva una referencia al Fragmento real directamente.
mblackwell8
66
Con FragmentStatePagerAdapter, usar el código anterior con a.instantiateItem(viewPager, viewPager.getCurrentItem());(para deshacerse de los 49) como lo propone @ mblackwell8 funciona perfectamente.
Cedric Simon
1
Espera, ¿entonces tengo que pasar el ViewPager a cada fragmento que contiene solo para poder hacer cosas a las vistas en el fragmento? Actualmente todo lo que hago en onCreate en la página actual ocurre en la página siguiente. Esto suena extremadamente sombrío a prueba de fugas.
G_V
Es importante mencionar que cualquier llamada a instantiateItemdebe estar rodeada por startUpdate/ finishUpdatecalls. los detalles están en mi respuesta a una pregunta similar: stackoverflow.com/questions/14035090/…
morgwai
154

OK, creo que he encontrado una manera de realizar la solicitud b) en mi propia pregunta, así que compartiré para beneficio de los demás. La etiqueta de fragmentos dentro de un ViewPager está en el formulario "android:switcher:VIEWPAGER_ID:INDEX", donde VIEWPAGER_IDestá el R.id.viewpagerdiseño en XML, e INDEX es la posición en el visor. Entonces, si se conoce la posición (por ejemplo, 0), puedo realizar 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 lo hará hasta que se sugiera algo mejor.

lado de la corteza
fuente
44
Ingresé solo para hacer +1 en su respuesta y pregunta.
SuitUp
55
Como esto no está en la documentación oficial, no lo usaría. ¿Qué pasa si cambian la etiqueta en una versión futura?
Thierry-Dimitri Roy
44
FragmentPagerAdapter viene con un método estático makeFragmentNameutilizado para generar la etiqueta que podría usar en su lugar para un enfoque un poco menos hacky: HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
dgmltn
8
@dgmltn, makeFragmentNamees un private staticmétodo, por lo que no puede acceder a él.
StenaviN
1
Dulce madre de Jesús, ¡esto funciona! Mantendré mi dedo cruzado y lo usaré.
Bogdan Alexandru
35

Si me pregunta, la segunda solución en la página a continuación, hacer 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 el 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", necesitamos 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, entonces 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);
}

"

Franco
fuente
@Frank segunda solución en el enlace es simple ...! Gracias .. :)
TheFlash
3
Mejor si usa un SparseArray en lugar de un mapa
GaRRaPeTa
actualicé mi respuesta para que use un SparseArray, si apuntas a <11, usa un SparseArrayCompat en su lugar porque la clase se actualizó en la versión 11.
Frank
2
Esto se romperá en los eventos del ciclo de vida. Ver stackoverflow.com/a/24662049/236743
Dori
13

Bien, 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 ninguna ID o etiqueta de fragmento, ya que no se almacenan de viewpagermanera 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 suyas de Fragmentsla FragmentPagerAdaptermisma manera:

    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 proteger sus Fragmentllamadas así:

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }
Ryan R
fuente
Ryan, ¿Esto quiere decir que estás exagerando las funciones que utiliza ViewPager para 'mantener' tus instancias de fragmentos y establecerlas en una variable pública que sea accesible? ¿Por qué en la instancia de restauración tiene if (mSocialStreamFragment == null) {?
Thomas Clowes
1
esta es una buena respuesta y +1 para la aplicación io12 no está seguro de si esto funcionará en los cambios del ciclo de vida, aunque debido a que getItem()no se llama después de que el padre se recrea debido a un cambio en el ciclo de vida. Ver stackoverflow.com/a/24662049/236743 . En el ejemplo, esto está parcialmente protegido contra un fragmento, pero no para los otros dos
Dori
2

Puede copiar FragmentPagerAdaptery modificar algún código fuente, agregar getTag()método

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 extiéndalo, anule estos métodos abstractos, no tenga que temer el cambio de Android Group

FragmentPageAdapter código fuente 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();
    }


}
Jiang Qi
fuente
1

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 de 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();
}
Hox
fuente
1

Alternativamente, puede anular el setPrimaryItemmétodo de esta FragmentPagerAdaptermanera:

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
fuente
0

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);
Kiduxa
fuente
1
esto se romperá después de los métodos de ciclo de vida de actividad / fragmento
Dori
0

Esta es mi solución ya que no necesito hacer un seguimiento de mis pestañas y necesito actualizarlas todas 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);
        }
    }
abhi08638
fuente