ViewPager não redesenha o conteúdo, permanece / fica em branco

86

Estamos sofrendo de um problema muito estranho com o ViewPager aqui. Incorporamos listas em cada página do ViewPager e disparamos o notifigDataSetChanged no adaptador de lista e no adaptador de pager de visualização ao atualizar os dados da lista.

O que observamos é que às vezes a página não atualiza sua árvore de visualização, ou seja, permanece em branco, ou às vezes até desaparece ao ser paginada. Ao retroceder e avançar algumas vezes, o conteúdo reaparecerá repentinamente. Parece que o Android está perdendo uma atualização de visualização aqui. Também percebi que, ao depurar com o visualizador de hierarquia, selecionar uma visualização sempre fará com que ela reapareça, aparentemente porque o visualizador de hierarquia força a visualização selecionada a se redesenhar.

Eu não poderia fazer este trabalho programaticamente; invalidar a exibição de lista, ou até mesmo o pager de exibição inteiro, não teve nenhum efeito.

Isso é com a biblioteca compatibilidade-v4_r7. Também tentei usar a revisão mais recente, uma vez que afirma corrigir muitos problemas relacionados à visualização do pager, mas tornou as coisas ainda piores (por exemplo, os gestos foram interrompidos para que às vezes não me deixasse folhear todas as páginas).

Alguém mais está enfrentando esses problemas também ou você tem uma ideia do que pode estar causando isso?

Matthias
fonte

Respostas:

44

Finalmente conseguimos encontrar uma solução. Aparentemente, nossa implementação sofreu de dois problemas:

  1. nosso adaptador não removeu a visão em destroyItem().
  2. estávamos armazenando visualizações em cache para que tivéssemos que aumentar nosso layout apenas uma vez e, como não estávamos removendo a visualização destroyItem(), não estávamos adicionando, instantiateItem()mas apenas retornando a visualização em cache correspondente à posição atual.

Eu não olhei muito profundamente no código-fonte do ViewPager- e não é exatamente explícito que você tem que fazer isso - mas a documentação diz:

destroyItem ()
Remove uma página para a posição dada. O adaptador é responsável por remover a visualização de seu contêiner, embora ele só deva garantir que isso seja feito no momento em que retornar de finishUpdate (ViewGroup).

e:

Um PagerAdapter muito simples pode escolher usar as próprias visualizações da página como objetos-chave, retornando-as de instantiateItem (ViewGroup, int) após a criação e adicionando-as ao ViewGroup pai. Uma implementação de destroyItem (ViewGroup, int, Object) correspondente removeria o View do ViewGroup pai e isViewFromObject (View, Object) poderia ser implementado como return view == object ;.

Portanto, minha conclusão é que ViewPagerdepende de seu adaptador subjacente para adicionar / remover explicitamente seus filhos em instantiateItem()/ destroyItem(). Ou seja, se seu adaptador é uma subclasse de PagerAdapter, sua subclasse deve implementar essa lógica.

Nota lateral: esteja ciente disso se usar listas internas ViewPager.

futtetennista
fonte
2
Estou tendo o mesmo problema, mas com FragmentPagerAdapter, que lida com onDestroy para mim. Nenhuma das outras soluções aqui funciona também.
Greg Ennis
14
O que também pode ser o problema é que alguém está usando getFragmentManger em vez de GetChildFragmentManager
Rapaz
@Boy, o que é GetChildFragmentManager?
incêndio no buraco de
1
@Boy Wooooow .... Fiquei puxando meu cabelo por dois dias inteiros tentando descobrir por que não conseguia atualizar nada. OBRIGADO! (eles deveriam realmente colocar um aviso na documentação do Google para usar o childfragmentmanager, que não quer dizer que você precisa de um gerente diferente).
user0721090601
@futtetennista Estou fazendo o mesmo .. enfrentando este problema .. você pode verificar .. stackoverflow.com/questions/61727835/…
AskQ
55

Se o ViewPagerfor definido dentro de um Fragment com um FragmentPagerAdapter, use em getChildFragmentManager()vez de getSupportFragmentManager()como o parâmetro para inicializar seu FragmentPagerAdapter.

mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

Ao invés de

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
escravo de olhos
fonte
2
Boa captura, parece resolver meu problema ao usar um FragmentPagerAdapter
Tobliug
1
Obrigado - funcionou. Eu estava tentando forçar getItem()um FragmentPagerAdapterque estava aninhado em um fragmento recriado.
kosiara - Bartosz Kosarzycki
wow este trabalho como charme. Acho que o problema se deve ao aumento do visor em um fragmento
Thecarisma
Em momentos como este, gostaria que tivéssemos aplausos como o Médio, para poder dar-lhe mais de 1000 aplausos por isso. Bom trabalho, esta deve ser a resposta aceita
Codelicious
21

Eu tive exatamente o mesmo problema, mas na verdade destruí a visualização em destroyItem (pensei). O problema, entretanto, é que eu o destruí usando em viewPager.removeViewAt(index);vez deviewPager.removeView((View) object);

Errado:

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

Direito:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeView((View) object);
}
Ringen
fonte
Obrigado pela dica. Já faz algum tempo que estou analisando o problema.
Moritz
2
Isso funcionou para mim, mas você sabe por que o primeiro é um problema?
HannahMitt 01 de
@HannahMitt removeViewAt removerá qualquer visão que esteja naquela posição particular dentro da lista atual de filhos do ViewPager, e as páginas podem ser adicionadas ao ViewPager em qualquer ordem (então a página 0 pode realmente estar no ViewPager no índice 1 ou qualquer outro). removeView irá iterar através da lista de filhos e remover exatamente o objeto que está especificado.
2
Não está funcionando para mim: não é possível lançar visão para fragmentar. objeto aqui é um fragmento.
FRK de
viewpager deve ser estático?
Kanagalingam
10

ViewPager tenta fazer coisas inteligentes em torno da reutilização de itens, mas requer que você retorne novas posições de item quando as coisas mudaram. Tente adicionar ao seu PagerAdapter:

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

Basicamente, diz ao ViewPager que tudo mudou (e força a instanciar tudo novamente). Essa é a única coisa que consigo pensar de cabeça.

Chris Banes
fonte
1
Sim, eu li sobre essa opção neste tópico: stackoverflow.com/questions/7263291/… - no entanto, esta parece ser a abordagem de marreta. Certamente deve haver uma maneira mais elegante? Eu me pergunto se isso está relacionado ao uso de listas como páginas de pager?
Matthias
É uma abordagem meio marreta, mas você pode contornar isso. ou seja, retornar um valor correto do método.
Chris Banes
@ChrisBanes Como você disse ligando return POSITION_NONE;, forces it to re-instantiate everythingmas no meu caso não quero instanciar tudo novamente , então seria possível remover viewsem limpar o material existente? Informe-me
Ritesh Adulkar
você é o sabor da vida
máscara 8
2

Tentei muitas soluções, mas viewPager.post()funcionou inesperadamente

 mAdapter = new NewsVPAdapter(getContext(), articles);
    viewPager.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdapter(mAdapter);
        }
    });
Zeero0
fonte
1
meu Deus. por que ninguém aprovou isso? Tive um comportamento estranho quando reinseri um fragmento e o visualizador interno não renderizou a si mesmo, e atrasá-lo um pouco assim realmente resolveu o problema!
Fugogugo
0

A Android Support Library tem uma atividade de demonstração que inclui um ViewPager com um ListView em cada página. Você provavelmente deveria dar uma olhada e ver o que ele faz.

No Eclipse (com Android Dev Tools r20):

  1. Selecione New > Android Sample Project
  2. Selecione seu nível de API de destino (sugiro o mais recente disponível)
  3. Selecione Support4Demos
  4. Clique com o botão direito no projeto e selecione Android Tools > Add Support Library
  5. Execute o aplicativo e selecione Fragmente depoisPager

O código para isso está em src/com.example.android.supportv4.app/FragmentPagerSupport.java. Boa sorte!

Sparky
fonte
obrigado - vou dar uma olhada nisso! Talvez eu identifique algo que estamos errando.
Matthias
0

Corri para isso e tive problemas muito semelhantes. Eu até perguntei sobre estouro de pilha.

Para mim, no pai do pai da minha visão, alguém LinearLayoutcriou uma subclasse e substituiu requestLayout()sem chamar super.requestLayout(). Isso evitou onMeasuree onLayoutfoi chamado no meu ViewPager (embora o hierarchyviewer os chame manualmente). Sem serem medidos, eles aparecerão em branco no ViewPager.

Portanto, verifique suas visualizações. Certifique-se de que eles tenham subclasses de View e não sobrescrevam requestLayout ou algo semelhante.

Tim O'Brien
fonte
0

Tive o mesmo problema, que é algo a ver com ListView(porque minha visualização vazia aparece bem se a lista estiver vazia). Acabei de requestLayout()ligar para a problemática ListView. Agora está tudo bem!

Oleg Vaskevich
fonte
0

Eu tive esse mesmo problema ao usar um ViewPager e FragmentStatePagerAdapter. Tentei usar um manipulador com um atraso de 3 segundos para chamar invalidate () e requestLayout (), mas não funcionou. O que funcionou foi redefinir a cor de fundo do viewPager da seguinte maneira:

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

Tive um problema com os mesmos sintomas, mas uma causa diferente que acabou sendo um erro bobo da minha parte. Pensei em adicioná-lo aqui para o caso de ajudar alguém.

Eu tinha um ViewPager usando FragmentStatePagerAdapter que costumava ter dois fragmentos, mas depois adicionei um terceiro. No entanto, esqueci que o limite padrão de página fora da tela é 1 - então, quando eu mudasse para o novo terceiro fragmento, o primeiro seria destruído e recriado após voltar. O problema era que minha atividade era responsável por notificar esses fragmentos para inicializar seu estado de IU. Isso funcionava quando os ciclos de vida de atividade e fragmento eram os mesmos, mas para consertar eu tive que mudar os fragmentos para inicializar sua própria IU durante seu ciclo de vida de inicialização. No final, também acabei alterando setOffscreenPageLimit para 2, de modo que todos os três fragmentos fossem mantidos vivos o tempo todo (seguro neste caso, já que eles não exigiam muita memória).

dfinn
fonte
-1

Eu tive um problema semelhante. Coloco visualizações em cache porque preciso de apenas 3 visualizações em ViewPager. Quando eu deslizo para frente está tudo bem, mas quando eu começo a deslizar para trás ocorre um erro, ele diz que "minha visão já tem um pai". A solução é excluir itens desnecessários 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
fonte
-1

Para mim, o problema era voltar à atividade depois que o processo do aplicativo foi encerrado. Estou usando um adaptador de pager de visualização customizado modificado das fontes do Android. O pager de visualização está incorporado diretamente na atividade.

Chamando viewPager.setCurrentItem(position, true);

(com animação) depois de definir os dados e notificarDataSetChanged () parece funcionar, mas se o parâmetro for definido como falso, ele não funciona e o fragmento está em branco. Este é um caso extremo que pode ser útil para alguém.

Homem malvado
fonte