ViewPager no vuelve a dibujar el contenido, permanece / se vuelve en blanco

86

Estamos sufriendo un problema muy extraño con ViewPager aquí. Incrustamos listas en cada página de ViewPager y activamos notifyDataSetChanged tanto en el adaptador de lista como en el adaptador de buscapersonas al actualizar los datos de lista.

Lo que observamos es que, en ocasiones, la página no actualiza su árbol de vistas, es decir, permanece en blanco o, en ocasiones, incluso desaparece al paginarse. Al desplazarse varias veces hacia adelante y hacia atrás, el contenido reaparecerá repentinamente. Parece que a Android le falta una actualización de vista aquí. También noté que al depurar con el visor de jerarquía, seleccionar una vista siempre hará que vuelva a aparecer, aparentemente porque el visor de jerarquía obliga a la vista seleccionada a volver a dibujarse.

Sin embargo, no pude hacer que esto funcione programáticamente; invalidar la vista de lista, o incluso todo el paginador de vista, no tuvo ningún efecto.

Esto es con la biblioteca compatibilidad-v4_r7. También traté de usar la última revisión, ya que pretende solucionar muchos problemas relacionados con la visualización del buscapersonas, pero empeoró las cosas (por ejemplo, los gestos se interrumpieron para que a veces ya no me dejaran pasar por todas las páginas).

¿Alguien más se encuentra con estos problemas también o tiene una idea de lo que podría estar causando esto?

Matías
fuente

Respuestas:

44

Finalmente logramos encontrar una solución. Aparentemente, nuestra implementación tuvo dos problemas:

  1. nuestro adaptador no eliminó la vista en destroyItem().
  2. estábamos almacenando vistas en caché para que tuviéramos que inflar nuestro diseño solo una vez, y, como no estábamos eliminando la vista destroyItem(), no la estábamos agregando, instantiateItem()sino que simplemente devolvíamos la vista en caché correspondiente a la posición actual.

No he examinado demasiado a fondo el código fuente del ViewPager, y no es exactamente explícito que tengas que hacer eso, pero los documentos dicen:

destroyItem ()
Elimina una página para la posición dada. El adaptador es responsable de eliminar la vista de su contenedor, aunque solo debe asegurarse de que esto se haga para cuando regrese de finishUpdate (ViewGroup).

y:

Un PagerAdapter muy simple puede optar por usar las vistas de página como objetos clave, devolviéndolas desde instantiateItem (ViewGroup, int) después de la creación y agregándolas al ViewGroup principal. Una implementación de destroyItem (ViewGroup, int, Object) coincidente eliminaría la Vista del ViewGroup principal e isViewFromObject (View, Object) podría implementarse como vista de retorno == objeto ;.

Entonces, mi conclusión es que se ViewPagerbasa en su adaptador subyacente para agregar / eliminar explícitamente sus hijos en instantiateItem()/ destroyItem(). Es decir, si su adaptador es una subclase de PagerAdapter, su subclase debe implementar esta lógica.

Nota al margen: tenga en cuenta esto si usa listas dentro ViewPager.

futtetennista
fuente
2
Tengo el mismo problema pero con FragmentPagerAdapter, que maneja onDestroy por mí. Ninguna de las otras soluciones aquí funciona tampoco.
Greg Ennis
14
Lo que también podría ser el problema, es que alguien está usando getFragmentManger en lugar de GetChildFragmentManager
Boy
@Boy, ¿qué es GetChildFragmentManager?
fuego en el hoyo
1
@Boy Wooooow ... Estuve tirando de mi cabello durante dos días enteros tratando de averiguar por qué no podía actualizar nada. ¡GRACIAS! (realmente, realmente deberían poner un aviso en la documentación de Google para usar el administrador de fragmentos de niños, que no es que necesite un administrador diferente).
user0721090601
@futtetennista Estoy haciendo lo mismo ... frente a este problema ... ¿puedes comprobar ... stackoverflow.com/questions/61727835/…
AskQ
55

Si ViewPagerse establece dentro de un Fragmento con a FragmentPagerAdapter, utilícelo en getChildFragmentManager()lugar de getSupportFragmentManager()como parámetro para inicializar su FragmentPagerAdapter.

mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

En vez de

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
esclavo de ojos
fuente
2
Buena captura, parece resolver mi problema al usar un FragmentPagerAdapter
Tobliug
1
Gracias, esto funcionó. Estaba tratando de forzar getItem()un FragmentPagerAdapterque estaba anidado en un Fragmento recreado.
kosiara - Bartosz Kosarzycki
wow este trabajo como un encanto. Supongo que el problema se debe a inflar el visor en un fragmento
Thecarisma
Es en momentos como este. Desearía que tuviéramos aplausos como Medium para poder darte más de 1000 aplausos por esto. Buen trabajo, esta debería ser la respuesta aceptada
Codelicious
21

Tuve exactamente el mismo problema, pero en realidad destruí la vista en destroyItem (pensé). Sin embargo, el problema fue que lo destruí usando viewPager.removeViewAt(index);insted ofviewPager.removeView((View) object);

Incorrecto:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeViewAt(position);
}

Derecho:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeView((View) object);
}
Ringen
fuente
Gracias por la pista. He estado investigando el problema durante bastante tiempo.
Moritz
2
Esto funcionó para mí, pero ¿sabes por qué el primero es un problema?
HannahMitt
@HannahMitt removeViewAt eliminará cualquier vista que se encuentre en esa posición particular dentro de la lista actual de niños de ViewPager, y las páginas se pueden agregar a ViewPager en cualquier orden (por lo que la página 0 puede estar en el ViewPager en el índice 1 o lo que sea). removeView iterará a través de la lista de hijos y eliminará exactamente el objeto especificado.
2
No funciona para mí: no se puede convertir la vista en un fragmento. El objeto aquí es un fragmento.
FRK
viewpager debe ser estático?
Kanagalingam
10

ViewPager intenta hacer cosas inteligentes en torno a la reutilización de elementos, pero requiere que devuelva nuevas posiciones de elementos cuando las cosas han cambiado. Intente agregar esto a su PagerAdapter:

public int getItemPosition (Object object) { return POSITION_NONE; }

Básicamente, le dice a ViewPager que todo ha cambiado (y lo obliga a volver a crear una instancia de todo). Eso es lo único en lo que se me ocurre la cabeza.

Chris Banes
fuente
1
Sí, he leído sobre esta opción en este hilo: stackoverflow.com/questions/7263291/… - sin embargo, esto parece el enfoque de mazo. ¿Seguramente debe haber una forma más elegante? Me pregunto si está relacionado con el uso de listas como páginas de buscapersonas.
Matthias
Es un enfoque un poco mazo, pero puedes trabajar desde eso. es decir, devolver un valor correcto del método.
Chris Banes
@ChrisBanes Como dijiste llamando return POSITION_NONE;, forces it to re-instantiate everythingpero en mi caso no quiero volver a crear una instancia de todo , entonces ¿sería posible eliminar viewsin borrar las cosas existentes?
Házmelo
usted es el sabor de la vida
mask8
2

Probé demasiadas soluciones pero viewPager.post()funcionó inesperadamente

 mAdapter = new NewsVPAdapter(getContext(), articles);
    viewPager.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdapter(mAdapter);
        }
    });
Zeero0
fuente
1
Dios mío. ¿Por qué nadie votó a favor de esto? Obtuve un comportamiento extraño cuando reingreso un fragmento y el visor dentro no se renderiza, ¡y retrasarlo un poco como esto realmente resolvió el problema!
Fugogugo
0

La biblioteca de soporte de Android tiene una actividad de demostración que incluye un ViewPager con un ListView en cada página. Probablemente debería echarle un vistazo y ver qué hace.

En Eclipse (con Android Dev Tools r20):

  1. Seleccione New > Android Sample Project
  2. Seleccione su nivel de API de destino (sugiero el más nuevo disponible)
  3. Seleccione Support4Demos
  4. Haga clic derecho en el proyecto y seleccione Android Tools > Add Support Library
  5. Ejecute la aplicación y seleccione Fragmenty luegoPager

El código para esto está en src/com.example.android.supportv4.app/FragmentPagerSupport.java. ¡Buena suerte!

Sparky
fuente
gracias - ¡echaré un vistazo a eso! Quizás detecte algo que nos estamos equivocando.
Matthias
0

Me encontré con esto y tuve problemas muy similares. Incluso lo pregunté en stack overflow.

Para mí, en el padre del padre de mi vista, alguien subclasificó LinearLayouty anuló requestLayout()sin llamar super.requestLayout(). Esto impidió onMeasurey onLayoutde ser llamado en mi ViewPager (aunque hierarchyviewer llamadas de forma manual estos). Si no se miden, aparecerán en blanco en ViewPager.

Así que revisa tus vistas de contenido. Asegúrese de que tengan una subclase de View y no anulen ciegamente requestLayout ni nada similar.

Tim O'Brien
fuente
0

Tuve el mismo problema, que tiene algo que ver con ListView(porque mi vista vacía se muestra bien si la lista está vacía). Solo llamé requestLayout()a la problemática ListView. ¡Ahora dibuja bien!

Oleg Vaskevich
fuente
0

Me encontré con este mismo problema cuando usaba ViewPager y FragmentStatePagerAdapter. Intenté usar un controlador con un retraso de 3 segundos para llamar a invalidate () y requestLayout () pero no funcionó. Lo que funcionó fue restablecer el color de fondo de viewPager de la siguiente manera:

MyFragment.java

    private Handler mHandler;
    private Runnable mBugUpdater;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = new ViewPager(getActivity());
        //...Create your adapter and set it here...

        mHandler = new Handler();
        mBugUpdater = new Runnable(){
            @Override
            public void run() {
                mVp.setBackgroundColor(mItem.getBackgroundColor());
                mHandler = null;
                mBugUpdater = null;
            }           
        };
        mHandler.postDelayed(mBugUpdater,50);

        return rootView;
    }

    @Override
    public void onPause() {
        if(mHandler != null){
            //Remove the callback if it hasn't triggered yet
            mHandler.removeCallbacks(mBugUpdater);
            mHandler = null;
            mBugUpdater = null;
        }
        super.onPause();
     }
Chris Sprague
fuente
0

Tuve un problema con los mismos síntomas, pero una causa diferente que resultó ser un error tonto de mi parte. Pensé en agregarlo aquí en caso de que ayude a alguien.

Tenía un ViewPager usando FragmentStatePagerAdapter que solía tener dos fragmentos, pero luego agregué un tercero. Sin embargo, olvidé que el límite predeterminado de páginas fuera de la pantalla es 1, por lo que, cuando cambiaba al nuevo tercer fragmento, el primero se destruía y luego se recreaba después de volver a cambiar. El problema era que mi actividad se encargaba de notificar a estos fragmentos para inicializar su estado de IU. Esto pasó a funcionar cuando la actividad y los ciclos de vida de los fragmentos eran los mismos, pero para solucionarlo tuve que cambiar los fragmentos para inicializar su propia IU durante su ciclo de vida de inicio. Al final, también terminé cambiando setOffscreenPageLimit a 2 para que los tres fragmentos se mantuvieran vivos en todo momento (seguro en este caso, ya que no consumían mucha memoria).

dfinn
fuente
-1

Tuve un problema similar. Guardo en caché las vistas porque solo necesito 3 vistas en formato ViewPager. Cuando me deslizo hacia adelante, todo está bien, pero cuando empiezo a deslizarme hacia atrás ocurre un error, dice que "mi vista ya tiene un padre". La solución es eliminar los elementos innecesarios manualmente.

@Override
    public Object instantiateItem(ViewGroup container, int position) {
        int localPos = position % SIZE;
        TouchImageView view;
        if (touchImageViews[localPos] != null) {
            view = touchImageViews[localPos];
        } else {
            view = new TouchImageView(container.getContext());
            view.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            touchImageViews[localPos] = view;
        }
        view.setImageDrawable(mDataModel.getPhoto(position));
        Log.i(IRViewPagerAdpt.class.toString(), "Add view " + view.toString() + " at pos: " + position + " " + localPos);
        if (view.getParent() == null) {
        ((ViewPager) container).addView(view);
    }
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object view) {
        //      ((ViewPager) container).removeView((View) view);
        Log.i(IRViewPagerAdpt.class.toString(), "remove view " + view.toString() + " at pos: " + position);
    }

..................

private static final int SIZE = 3;
private TouchImageView[] touchImageViews = new TouchImageView[SIZE];
Roman Nazarevych
fuente
-1

Para mí, el problema fue volver a la actividad después de que se eliminó el proceso de la aplicación. Estoy usando un adaptador de localizador de vista personalizado modificado de las fuentes de Android. El localizador de vista está integrado directamente en la actividad.

Vocación viewPager.setCurrentItem(position, true);

(con animación) después de configurar los datos y notifyDataSetChanged () parece funcionar, pero si el parámetro se establece en falso, no lo hace y el fragmento está en blanco. Este es un caso marginal que puede ser de ayuda para alguien.

Hombre malo
fuente