Estoy tratando de usar Fragment con un ViewPager
uso de FragmentPagerAdapter
. Lo que estoy buscando lograr es reemplazar un fragmento, posicionado en la primera página del ViewPager
, con otro.
El buscapersonas se compone de dos páginas. El primero es el FirstPagerFragment
, el segundo es el SecondPagerFragment
. Al hacer clic en un botón de la primera página. Me gustaría reemplazar el FirstPagerFragment
con el NextFragment.
Ahí está mi código a continuación.
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);
}
}
}
... y aquí los archivos 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>
Ahora el problema ... qué ID debo usar en
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
?
Si lo uso R.id.first_fragment_root_id
, el reemplazo funciona, pero Hierarchy Viewer muestra un comportamiento extraño, como se muestra a continuación.
Al principio la situación es
después del reemplazo la situación es
Como puede ver, hay algo mal, espero encontrar el mismo estado que se muestra en la primera imagen después de reemplazar el fragmento.
fuente
Respuestas:
Hay otra solución que no necesita modificar el código fuente de
ViewPager
yFragmentStatePagerAdapter
, y funciona con laFragmentPagerAdapter
clase base utilizada por el autor.Me gustaría comenzar respondiendo a la pregunta del autor sobre qué identificación debe usar; es la ID del contenedor, es decir, la ID del propio buscapersonas. Sin embargo, como probablemente te hayas dado cuenta, usar esa ID en tu código no hace que suceda nada. Explicaré por qué:
En primer lugar, para
ViewPager
repoblar las páginas, debe llamar anotifyDataSetChanged()
eso reside en la clase base de su adaptador.En segundo lugar,
ViewPager
utiliza elgetItemPosition()
método abstracto para verificar qué páginas deben destruirse y cuáles deben conservarse. La implementación predeterminada de esta función siempre regresaPOSITION_UNCHANGED
, lo que haceViewPager
que se mantengan todas las páginas actuales y, en consecuencia, no se adjunte su nueva página. Por lo tanto, para que el reemplazo de fragmentos funcione,getItemPosition()
debe anularse en su adaptador y debe regresarPOSITION_NONE
cuando se lo llama con un fragmento antiguo, oculto, como argumento.Esto también significa que su adaptador siempre debe ser consciente de qué fragmento debe mostrarse en la posición 0,
FirstPageFragment
oNextFragment
. Una forma de hacerlo es proporcionar un oyente al crearFirstPageFragment
, que se llamará cuando sea el momento de cambiar fragmentos. Sin embargo, creo que es bueno dejar que su adaptador de fragmentos maneje todos los conmutadores de fragmentos y llamadas aViewPager
yFragmentManager
.Tercero,
FragmentPagerAdapter
almacena en caché los fragmentos usados por un nombre derivado de la posición, por lo que si había un fragmento en la posición 0, no se reemplazará aunque la clase sea nueva. Hay dos soluciones, pero la más simple es usar laremove()
función deFragmentTransaction
, que también eliminará su etiqueta.Eso fue mucho texto, aquí hay un código que debería funcionar en su caso:
Espero que esto ayude a cualquiera!
fuente
FirstPageFragment.newInstance()
parámetro oyente?A partir del 13 de noviembre de 2012, la reparación de fragmentos en un ViewPager parece haberse vuelto mucho más fácil. Google lanzó Android 4.2 con soporte para fragmentos anidados, y también es compatible con la nueva Biblioteca de soporte de Android v11, por lo que esto funcionará desde 1.6
Es muy similar a la forma normal de reemplazar un fragmento, excepto que usa getChildFragmentManager. Parece funcionar, excepto que la pila de fragmentos anidados no aparece cuando el usuario hace clic en el botón Atrás. Según la solución en esa pregunta vinculada, debe llamar manualmente a popBackStackImmediate () en el administrador secundario del fragmento. Por lo tanto, debe anular onBackPressed () de la actividad ViewPager donde obtendrá el fragmento actual de ViewPager y llamará a getChildFragmentManager (). PopBackStackImmediate () en él.
Obtener el Fragmento que se muestra actualmente también es un poco extraño, utilicé esta sucia solución "android: switcher: VIEWPAGER_ID: INDEX", pero también puede realizar un seguimiento de todos los fragmentos del ViewPager usted mismo, como se explica en la segunda solución en esta página .
Así que aquí está mi código para un ViewPager con 4 ListViews con una vista detallada que se muestra en ViewPager cuando el usuario hace clic en una fila y con el botón Atrás funcionando. Traté de incluir solo el código relevante por razones de brevedad, así que deje un comentario si desea que la aplicación completa se cargue en GitHub.
HomeActivity.java
ListProductsFragment.java
fuente
Basado en la respuesta de @wize, que encontré útil y elegante, pude lograr lo que quería parcialmente, porque quería que la capacidad volviera al primer Fragmento una vez reemplazado. Lo logré modificando un poco su código.
Este sería el FragmentPagerAdapter:
Para realizar el reemplazo, simplemente defina un campo estático, del tipo
CalendarPageFragmentListener
e inicializado a través de losnewInstance
métodos de los fragmentos correspondientes y llameFirstFragment.pageListener.onSwitchToNextFragment()
oNextFragment.pageListener.onSwitchToNextFragment()
respiretevely.fuente
mFragmentAtPos0
referencia al guardar el estado de la actividad. No es la solución más elegante, pero funciona.He implementado una solución para:
Los trucos para lograr esto son los siguientes:
El código del adaptador es el siguiente:
La primera vez que agrega todas las pestañas, debemos llamar al método createHistory (), para crear el historial inicial
Cada vez que desee reemplazar un fragmento a una pestaña específica, llame a: replace (posición int final, clase final fragmentClass, args de paquete final)
Al presionar hacia atrás, debe llamar al método back ():
La solución funciona con la barra de acción Sherlock y con un gesto de deslizar.
fuente
tl; dr: utilice un fragmento de host que sea responsable de reemplazar su contenido alojado y realice un seguimiento del historial de navegación hacia atrás (como en un navegador).
Como su caso de uso consiste en una cantidad fija de pestañas, mi solución funciona bien: la idea es llenar ViewPager con instancias de una clase personalizada
HostFragment
, que pueda reemplazar su contenido alojado y mantenga su propio historial de navegación hacia atrás. Para reemplazar el fragmento alojado, realice una llamada al métodohostfragment.replaceFragment()
:Todo lo que hace el método es reemplazar el diseño del marco con la identificación
R.id.hosted_fragment
con el fragmento proporcionado al método.¡Consulte mi tutorial sobre este tema para obtener más detalles y un ejemplo de trabajo completo en GitHub!
fuente
Algunas de las soluciones presentadas me ayudaron mucho a resolver parcialmente el problema, pero todavía falta una cosa importante en las soluciones que ha producido excepciones inesperadas y contenido de página negra en lugar de contenido fragmentado en algunos casos.
El caso es que la clase FragmentPagerAdapter está utilizando la ID del elemento para almacenar fragmentos almacenados en caché en FragmentManager . Por esta razón, debe anular también el método getItemId (int position) para que devuelva, por ejemplo, posición para páginas de nivel superior y 100 + posición para páginas de detalles. De lo contrario, el fragmento de nivel superior creado anteriormente se devolvería de la memoria caché en lugar del fragmento de nivel de detalle.
Además, estoy compartiendo aquí un ejemplo completo de cómo implementar una actividad similar a pestañas con páginas Fragment usando ViewPager y botones de pestañas usando RadioGroup que permite el reemplazo de páginas de nivel superior con páginas detalladas y también admite el botón de retroceso. Esta implementación admite solo un nivel de apilamiento posterior (lista de elementos - detalles del elemento) pero la implementación de apilamiento posterior de varios niveles es sencilla. Este ejemplo funciona bastante bien en casos normales, excepto que está lanzando una NullPointerException en caso de que cambie, por ejemplo, a la segunda página, cambie el fragmento de la primera página (aunque no sea visible) y regrese a la primera página. Publicaré una solución a este problema una vez que lo resuelva:
fuente
He creado un ViewPager con 3 elementos y 2 subelementos para el índice 2 y 3 y aquí lo que quería hacer.
He implementado esto con la ayuda de preguntas y respuestas anteriores de StackOverFlow y aquí está el enlace.
ViewPagerChildFragments
fuente
Para reemplazar un fragmento dentro de una
ViewPager
que se puede mover códigos fuente deViewPager
,PagerAdapter
yFragmentStatePagerAdapter
clases en su proyecto y añadir siguiente código.en
ViewPager
:en FragmentStatePagerAdapter:
handleGetItemInvalidated()
asegura que después de la próxima llamadagetItem()
devuelva newFragmentgetFragmentPosition()
devuelve la posición del fragmento en su adaptador.Ahora, para reemplazar fragmentos llame
Si está interesado en un proyecto de ejemplo, solicite las fuentes.
fuente
Funciona muy bien con la solución de AndroidTeam, sin embargo, descubrí que necesitaba la capacidad de retroceder de manera muy similar,
FrgmentTransaction.addToBackStack(null)
pero simplemente agregar esto solo hará que el Fragmento sea reemplazado sin notificar al ViewPager. La combinación de la solución provista con esta mejora menor le permitirá volver al estado anterior simplemente anulando elonBackPressed()
método de la actividad . El mayor inconveniente es que solo retrocederá uno a la vez, lo que puede generar múltiples clics de retrocesoEspero que esto ayude a alguien.
Además, en lo que respecta,
getFragmentPosition()
es más o menosgetItem()
a la inversa. Usted sabe qué fragmentos van a dónde, solo asegúrese de devolver la posición correcta en la que estará. Aquí hay un ejemplo:fuente
En su
onCreateView
método, encontainer
realidad es unaViewPager
instancia.Entonces, solo llamando
cambiará el fragmento actual en su
ViewPager
.fuente
vpViewPager.setCurrentItem(1);
. Revisé el ejemplo de cada persona, sin que pasara nada cada vez hasta que finalmente llegara al tuyo. Gracias.ViewPager
página a otra, no reemplazará ningún fragmento.Aquí está mi solución relativamente simple para este problema. Las claves de esta solución son usarlas en
FragmentStatePagerAdapter
lugar de usarlas,FragmentPagerAdapter
ya que la primera eliminará los fragmentos no utilizados por usted mientras que la segunda aún conserva sus instancias. El segundo es el uso dePOSITION_NONE
in getItem (). He usado una lista simple para hacer un seguimiento de mis fragmentos. Mi requisito era reemplazar toda la lista de fragmentos a la vez con una nueva lista, pero lo siguiente podría modificarse fácilmente para reemplazar fragmentos individuales:fuente
También hice una solución, que está trabajando con Stacks . Es un enfoque más modular , por lo que no tiene que especificar cada Fragmento y Fragmento de detalle en su
FragmentPagerAdapter
. Se basa en el ejemplo de ActionbarSherlock que se deriva si estoy en la aplicación de demostración de Google.Agregue esto para la funcionalidad del botón de retroceso en su actividad principal:
Si desea guardar el estado del fragmento cuando se elimina. Deje que su Fragment implemente la interfaz y
SaveStateBundle
devuelva en la función un paquete con su estado de guardado. Obtenga el paquete después de la creación de instanciasthis.getArguments()
.Puede crear una instancia de una pestaña como esta:
funciona de manera similar si desea agregar un Fragmento en la parte superior de una Pila de fichas. Importante : creo que no funcionará si desea tener 2 instancias de la misma clase en la parte superior de dos pestañas. Hicimos esta solución rápidamente juntos, por lo que solo puedo compartirla sin proporcionar ninguna experiencia con ella.
fuente
Reemplazar fragmentos en un visor es bastante complicado, pero es muy posible y puede verse muy elegante. Primero, debe dejar que el visor mismo se encargue de eliminar y agregar los fragmentos. Lo que sucede es que cuando reemplaza el fragmento dentro de SearchFragment, su visor retiene sus vistas de fragmentos. Entonces terminas con una página en blanco porque el SearchFragment se elimina cuando intentas reemplazarlo.
La solución es crear un oyente dentro de su visor que maneje los cambios realizados fuera de él, así que primero agregue este código a la parte inferior de su adaptador.
Luego, debe crear una clase privada en su visor que se convierta en un oyente para cuando desee cambiar su fragmento. Por ejemplo, podría agregar algo como esto. Tenga en cuenta que implementa la interfaz que acaba de crear. Entonces, cada vez que llame a este método, ejecutará el código dentro de la clase a continuación.
Hay dos cosas principales que señalar aquí:
Observe los oyentes que se colocan en el constructor 'newInstance (oyente). Así es como llamarás a fragment0Changed (String newFragmentIdentification) `. El siguiente código muestra cómo se crea el oyente dentro de su fragmento.
static nextFragmentListener listenerSearch;
Podrías llamar al cambio dentro de tu
onPostExecute
Esto activaría el código dentro de su visor para cambiar su fragmento en la posición cero fragAt0 para convertirse en un nuevo searchResultFragment. Hay dos piezas más pequeñas que necesitará agregar al visor antes de que sea funcional.
Uno estaría en el método de anulación getItem del visor.
Ahora, sin esta pieza final, todavía obtendría una página en blanco. Un poco cojo, pero es una parte esencial de viewPager. Debe anular el método getItemPosition del visor. Normalmente, este método devolverá POSITION_UNCHANGED, que le indica al visor que mantenga todo igual y, por lo tanto, getItem nunca será llamado para colocar el nuevo fragmento en la página. Aquí hay un ejemplo de algo que podrías hacer
Como dije, el código se involucra mucho, pero básicamente tienes que crear un adaptador personalizado para tu situación. Las cosas que mencioné permitirán cambiar el fragmento. Probablemente tomará mucho tiempo empapar todo para que sea paciente, pero todo tendrá sentido. Vale la pena tomarse el tiempo porque puede hacer una aplicación realmente elegante.
Aquí está la pepita para manejar el botón Atrás. Pones esto dentro de tu actividad principal
Deberá crear un método llamado backPressed () dentro de FragmentSearchResults que llame a fragment0changed. Esto junto con el código que mostré antes manejará presionar el botón Atrás. Buena suerte con tu código para cambiar el visor. Requiere mucho trabajo y, por lo que he encontrado, no hay adaptaciones rápidas. Como dije, básicamente estás creando un adaptador de visor personalizado y permitiéndote manejar todos los cambios necesarios usando escuchas
fuente
Esta es mi forma de lograr eso.
En primer lugar, agregue la pestaña
Root_fragment
interiorviewPager
en la que desea implementar elfragment
evento de clic de botón . Ejemplo;En primer lugar,
RootTabFragment
debe incluirseFragmentLayout
para el cambio de fragmentos.Luego, adentro
RootTabFragment
onCreateView
, implementefragmentChange
para suFirstPagerFragment
Después de eso, implemente el
onClick
evento para su botón dentroFirstPagerFragment
y haga un cambio de fragmento como ese nuevamente.Espero que esto te ayude, chico.
fuente
Encontré una solución simple, que funciona bien incluso si desea agregar nuevos fragmentos en el medio o reemplazar el fragmento actual. En mi solución, debería anular lo
getItemId()
que debería devolver una identificación única para cada fragmento. No se posiciona como por defecto.Ahí está:
Aviso: en este ejemplo
FirstFragment
ySecondFragment
extiende la clase abstracta PageFragment, que tiene métodogetPage()
.fuente
Estoy haciendo algo similar a Wize, pero en mi respuesta puedes cambiar entre los dos fragmentos cuando quieras. Y con la respuesta wize tengo algunos problemas al cambiar la orientación de la pantalla y cosas así. Así se ve el PagerAdapter:
El oyente que implementé en la actividad del contenedor del adaptador para ponerlo en el fragmento al adjuntarlo, esta es la actividad:
Luego, en el fragmento, coloca al oyente cuando adjunta un llamado:
Y finalmente el oyente:
fuente
Seguí las respuestas de @wize y @mdelolmo y obtuve la solución. Gracias toneladas. Pero, ajusté un poco estas soluciones para mejorar el consumo de memoria.
Problemas que observé:
Guardan la instancia de la
Fragment
cual se reemplaza. En mi caso, es un Fragmento que tieneMapView
y pensé que era costoso. Por lo tanto, estoy manteniendo elFragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)
lugar deFragment
sí mismo.Aquí está mi implementación.
Enlace de demostración aquí .. https://youtu.be/l_62uhKkLyM
Para fines de demostración, usé 2 fragmentos
TabReplaceFragment
yDemoTab2Fragment
en la posición dos. En todos los demás casos estoy usandoDemoTabFragment
instancias.Explicación:
Estoy pasando
Switch
de Actividad aDemoCollectionPagerAdapter
. Según el estado de este interruptor, mostraremos el fragmento correcto. Cuando se cambia la verificación del interruptor, estoy llamando al métodoSwitchFragListener
'sonSwitchToNextFragment
, donde estoy cambiando el valor de lapagerAdapterPosChanged
variable aPOSITION_NONE
. Vea más sobre POSITION_NONE . Esto invalidará getItem y tengo lógicas para instanciar el fragmento correcto allí. Lo siento, si la explicación es un poco desordenada.Una vez más, muchas gracias a @wize y @mdelolmo por la idea original.
Espero que esto sea útil. :)
Avíseme si esta implementación tiene algún defecto. Eso será de gran ayuda para mi proyecto.
fuente
Después de la investigación, encontré una solución con código corto. en primer lugar, cree una instancia pública en fragmento y simplemente elimine su fragmento en onSaveInstanceState si el fragmento no se recrea en el cambio de orientación.
fuente