O ViewPager PagerAdapter não atualiza o View

597

Estou usando o ViewPager da biblioteca de compatibilidade. Consegui exibi-lo com sucesso, com várias visualizações nas quais posso paginar.

No entanto, estou tendo dificuldade para descobrir como atualizar o ViewPager com um novo conjunto de modos de exibição.

Eu tentei todo tipo de coisa como ligar mAdapter.notifyDataSetChanged(), mViewPager.invalidate()até mesmo criar um adaptador totalmente novo sempre que eu quiser usar uma nova lista de dados.

Nada ajudou, as visualizações de texto permanecem inalteradas em relação aos dados originais.

Atualização: fiz um pequeno projeto de teste e quase consegui atualizar as visualizações. Vou colar a turma abaixo.

O que parece não atualizar, no entanto, é a segunda visualização, o 'B' permanece; ele deve exibir 'Y' depois de pressionar o botão de atualização.

public class ViewPagerBugActivity extends Activity {

    private ViewPager myViewPager;
    private List<String> data;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        data = new ArrayList<String>();
        data.add("A");
        data.add("B");
        data.add("C");

        myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
        myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

        Button updateButton = (Button) findViewById(R.id.update_button);
        updateButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                updateViewPager();
            }
        });
    }

    private void updateViewPager() {
        data.clear();
        data.add("X");
        data.add("Y");
        data.add("Z");
        myViewPager.getAdapter().notifyDataSetChanged();
    }

    private class MyViewPagerAdapter extends PagerAdapter {

        private List<String> data;
        private Context ctx;

        public MyViewPagerAdapter(Context ctx, List<String> data) {
            this.ctx = ctx;
            this.data = data;
        }

        @Override
        public int getCount() {
            return data.size();
        }

        @Override
        public Object instantiateItem(View collection, int position) {
            TextView view = new TextView(ctx);
            view.setText(data.get(position));
            ((ViewPager)collection).addView(view);
            return view;
        }

        @Override
        public void destroyItem(View collection, int position, Object view) {
             ((ViewPager) collection).removeView((View) view);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Parcelable saveState() {
            return null;
        }

        @Override
        public void restoreState(Parcelable arg0, ClassLoader arg1) {
        }

        @Override
        public void startUpdate(View arg0) {
        }

        @Override
        public void finishUpdate(View arg0) {
        }
    }
}
C0deAttack
fonte
1
Consulte stackoverflow.com/questions/12510404/… para obter um bug em potencial com o FragmentStatePagerAdapter.
samis
2
Desculpe desenterrar isso. Sua pergunta realmente me ajudou, obrigado! A única coisa que eu não entendo é como a referência a dados na PagerAdapter é alterado quando você atualizá-lo na Atividade
Ewanw
Faça seu adaptador estender o BaseAdapter Consulte: stackoverflow.com/questions/13664155/…
1
Aparentemente, se você configurar o adaptador novamente para o seu pager de exibição, ele será redefinido.
EpicPandaForce
Eu tenho problema no pager stackoverflow.com/questions/41217237/...
chris

Respostas:

856

Existem várias maneiras de conseguir isso.

A primeira opção é mais fácil, mas um pouco mais ineficiente.

Substitua getItemPositionda PagerAdapterseguinte maneira:

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

Dessa forma, quando você ligar notifyDataSetChanged(), o pager da visualização removerá todas as visualizações e recarregará todas elas. Assim, o efeito de recarga é obtido.

A segunda opção, sugerida por Alvaro Luis Bustamante (anteriormente alvarolb) , é usar osetTag() método instantiateItem()ao instanciar uma nova exibição. Em vez de usar notifyDataSetChanged(), você pode usar findViewWithTag()para encontrar a exibição que deseja atualizar.

A segunda abordagem é muito flexível e de alto desempenho. Parabéns ao alvarolb pela pesquisa original.

rui.araujo
fonte
4
Melhor do que o meu trabalho, parece não ter nenhum efeito colateral, obrigado.
precisa saber é o seguinte
3
Eu tenho um problema similar. Meu viewPager mostrará os novos resultados da pesquisa, mas não retornará à primeira entrada no cursor. Alguém sabe como redefinir a posição ao atualizar o conjunto de dados?
mAndroid 14/09/11
1
@ mAndroid, você deve enviar uma pergunta separada com mais detalhes.
Rui.araujo 15/09
1
o retorno de POSITION_NONE aparentemente adiciona muita ineficiência ao aplicativo geral, pois isso faz com que o Android exclua a visualização associada à posição consultada. A exibição precisa ser recriada novamente antes da exibição. Resolve o problema de as exibições não serem atualizadas, mas provavelmente não é a melhor solução para layouts complexos.
Wufoo
11
Melhor resposta (sem remover todos os itens) -> stackoverflow.com/a/10852046/898056
yhpark
484

Eu não acho que exista algum tipo de bug no PagerAdapter . O problema é que entender como funciona é um pouco complexo. Observando as soluções explicadas aqui, há um mal-entendido e, portanto, um uso inadequado de visualizações instanciadas do meu ponto de vista.

Os últimos dias tenho vindo a trabalhar com PagerAdaptere ViewPager, e eu encontrei o seguinte:

O notifyDataSetChanged()método noPagerAdapter notificará apenas ViewPagerque as páginas subjacentes foram alteradas. Por exemplo, se você criou / excluiu páginas dinamicamente (adicionando ou removendo itens da sua lista), você ViewPagerdeve cuidar disso. Nesse caso, acho que o ViewPagerdetermina se uma nova visão deve ser excluída ou instanciada usando os métodos getItemPosition()e getCount().

eu penso isso ViewPager , após uma notifyDataSetChanged()chamada, obtém as visualizações de crianças e verifica sua posição com o getItemPosition(). Se, para uma visão filho, esse método retornar POSITION_NONE, ele ViewPagerentenderá que a visão foi excluída, chamando o métododestroyItem() e removendo essa visão.

Dessa forma, substituindo getItemPosition() para sempre retornar POSITION_NONEestará completamente errada se você quiser atualizar apenas o conteúdo das páginas, porque as visualizações criadas anteriormente serão destruídas e novas serão criadas sempre que você ligar notifyDatasetChanged(). Pode não parecer tão errado apenas por alguns TextViewsegundos, mas quando você tem visualizações complexas, como ListViews preenchidas em um banco de dados, isso pode ser um problema real e um desperdício de recursos.

Portanto, existem várias abordagens para alterar com eficiência o conteúdo de uma visualização sem precisar remover e instanciar a visualização novamente. Depende do problema que você deseja resolver. Minha abordagem é usar o setTag()método para qualquer exibição instanciada no instantiateItem()método. Portanto, quando você deseja alterar os dados ou invalidar a visualização necessária, pode chamar o findViewWithTag()método ViewPagerpara recuperar a visualização instanciada anteriormente e modificá-la / usá-la como desejar, sem precisar excluir / criar uma nova visualização sempre que desejar para atualizar algum valor.

Imagine, por exemplo, que você tenha 100 páginas com 100 TextViewse deseja atualizar apenas um valor periodicamente. Com as abordagens explicadas anteriormente, isso significa que você está removendo e instanciando 100 TextViews em cada atualização. Não faz sentido...

Alvaro Luis Bustamante
fonte
11
+1 - Sua solução não funciona apenas como um encanto. Eu corri exatamente no problema que você descreveu aqui quando tive que atualizar páginas que continham guias com visualizações de lista, visualizações de texto e imagens. (OutOfErrorExceptions e tal ...) Obrigado!
Schlingel
3
@alvarolb: Olá, eu realmente não sei como você quer dizer setTagno método onInstantiateItem. Gostaria de atualizar esta resposta com alguma codificação? Obrigado.
Huy Tower
35
Seria perfeito se você escrevesse um exemplo.
Sami Eltamawy
13
Como alguém diz: um exemplo será apreciado!
precisa saber é o seguinte
15
Há 6 anos, ninguém pode dar um exemplo.
Oussaki
80

Mude FragmentPagerAdapterpara FragmentStatePagerAdapter.

Substituir getItemPosition()método e retorno POSITION_NONE.

Eventualmente, ele ouvirá o notifyDataSetChanged()pager no modo de exibição.

Kit
fonte
Eu acho que você deve implementar toda a getItemPosition corretamente E retornar POSITION_NONE se o fragmento não for encontrado.
Tkhduracell 17/09/19
46

A resposta dada pelo alvarolb é definitivamente a melhor maneira de fazê-lo. Com base em sua resposta, uma maneira fácil de implementar isso é simplesmente armazenar as visualizações ativas por posição:

SparseArray<View> views = new SparseArray<View>();

@Override
public Object instantiateItem(View container, int position) {
    View root = <build your view here>;
    ((ViewPager) container).addView(root);
    views.put(position, root);
    return root;
}

@Override
public void destroyItem(View collection, int position, Object o) {
    View view = (View)o;
    ((ViewPager) collection).removeView(view);
    views.remove(position);
    view = null;
}

Em seguida, substituindo o notifyDataSetChangedmétodo, você pode atualizar as visualizações ...

@Override
public void notifyDataSetChanged() {
    int key = 0;
    for(int i = 0; i < views.size(); i++) {
       key = views.keyAt(i);
       View view = views.get(key);
       <refresh view with new data>
    }
    super.notifyDataSetChanged();
}

Você pode realmente usar código semelhante instantiateIteme notifyDataSetChangedatualizar sua exibição. No meu código eu uso exatamente o mesmo método.

Grimmace
fonte
8
você pode fornecer um exemplo de adaptador completo? isso está sendo feito usando fragmentos ou não?
9
<atualizar visualização com novos dados> ?? qual é o significado disso, ou temos que acrescentar que ele vê aqui ou nada?
Akarsh H
Este método atualiza todas as visualizações. Se você deseja atualizar apenas uma visualização, pode escrever seu próprio método notifyDataItemChanged assim: public void notifyDataItemChanged (int pos) {TextView tv = (TextView) views.get (pos) .findViewById (R.id.tv); tv.setText (list.get (pos)); super.notifyDataSetChanged (); }
Kai Wang
Este código falha se o tamanho do novo pager for menor que o tamanho antigo
Anton Duzenko 24/03
@AntonDuzenko correto, excluir visualizações é bastante difícil.
MLProgrammer-CiM
28

Teve o mesmo problema. Para mim, trabalhou para estender o FragmentStatePagerAdapter e substituir os métodos abaixo:

@Override
public Parcelable saveState() {
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {

}
Mario Mosca
fonte
Esta é a única solução que funcionou para mim também. Meu caso não está atualizando o fragmento, mas removendo ou adicionando fragmentos dinamicamente. Sem estender o FragmentStatePagerAdapter, o ViewPager retorna fragmentos armazenados em cache, que é uma posição incorreta. Obrigado!
precisa
Funciona bem na biblioteca de suporte 28.0.0
Oleksii K.
Substituir para fazer o que? Como criar um objeto de estado associado a um adaptador?
Phani Rithvij
Obrigado parceiro. Esta solução só funciona
Parthi 31/03
20

Depois de horas de frustração enquanto tentava todas as soluções acima para superar esse problema e também tentava muitas soluções em outras questões semelhantes como esta , esta e esta que todas falharam comigo para resolver esse problema e fazer ViewPagercom que destruísse o antigo Fragmente preenchesse opager com o novo Fragments. Resolvi o problema da seguinte maneira:

1) Faça a ViewPageraula estender da FragmentPagerAdapterseguinte maneira:

 public class myPagerAdapter extends FragmentPagerAdapter {

2) Crie um item para ViewPageresse armazenamento titlee o fragmentseguinte:

public class PagerItem {
private String mTitle;
private Fragment mFragment;


public PagerItem(String mTitle, Fragment mFragment) {
    this.mTitle = mTitle;
    this.mFragment = mFragment;
}
public String getTitle() {
    return mTitle;
}
public Fragment getFragment() {
    return mFragment;
}
public void setTitle(String mTitle) {
    this.mTitle = mTitle;
}

public void setFragment(Fragment mFragment) {
    this.mFragment = mFragment;
}

}

3) Faça o construtor do ViewPagertake minha FragmentManagerinstância para armazená-lo no meu da classseguinte maneira:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mPagerItems = pagerItems;
}

4) Crie um método para re-definir os adapterdados com os novos dados, eliminando todos os anteriores fragmentda fragmentManagerprópria diretamente para fazer a adapterpara definir o novo fragmenta partir da nova lista novamente da seguinte forma:

public void setPagerItems(ArrayList<PagerItem> pagerItems) {
    if (mPagerItems != null)
        for (int i = 0; i < mPagerItems.size(); i++) {
            mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
        }
    mPagerItems = pagerItems;
}

5) No contêiner Activityou Fragmentnão reinicialize o adaptador com os novos dados. Defina os novos dados através do método setPagerItemscom os novos dados da seguinte maneira:

ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));

mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();

Espero que ajude.

Sami Eltamawy
fonte
@ Qual foi o problema que você enfrentou?
Sami Eltamawy
9

Eu tive o mesmo problema e minha solução está usando FragmentPagerAdaptercom a substituição de FragmentPagerAdapter#getItemId(int position):

@Override
public long getItemId(int position) {
    return mPages.get(position).getId();
}

Por padrão, esse método retorna a posição do item. Suponho que ViewPagerverifique se itemIdfoi alterado e recria a página apenas se for. Porém, a versão não substituída retorna a mesma posição, itemIdmesmo que a página seja realmente diferente, e o ViewPager não define que a página seja substituída por uma e precise ser recriada.

Para usar isso, long idé necessário para cada página. Normalmente, espera-se que ele seja único, mas sugiro, nesse caso, que ele seja diferente do valor anterior da mesma página. Portanto, é possível usar o contador contínuo no adaptador ou em números aleatórios (com ampla distribuição) aqui.

Eu acho que é uma maneira mais consistente do que usar as Tags de visão mencionadas como uma solução neste tópico. Mas provavelmente não para todos os casos.

atlascoder
fonte
1
Isto é. Eu verifiquei: apenas POSITION_NONE não é suficiente. Obrigado pela sua maravilhosa resposta! :)
Slava
Esta função nem está presente no PagerAdapter. Que classe é essa?
Saket
@Saket, você está certo, a classe éFragmentPagerAdapter
atlascoder
6

Achei uma decisão muito interessante sobre esse problema. Em vez de usar o FragmentPagerAdapter , que mantém na memória todos os fragmentos, podemos usar o FragmentStatePagerAdapter ( android.support.v4.app.FragmentStatePagerAdapter ), que recarrega o fragmento a cada vez que o selecionamos.

As realizações de ambos os adaptadores são idênticas. Portanto, precisamos apenas alterar " estender FragmentPagerAdapter " em " estender FragmentStatePagerAdapter "

Рома Богдан
fonte
bom ponto, ele vai ajudar outros a resolver problemas de memória de viewPager relacionadas com fragmento
Ashu Kumar
Ótima resposta, isso me ajudou muito.
Sergio Marani
5

Depois de muita pesquisa para esse problema, encontrei uma solução realmente boa que acho que é o caminho certo a seguir. Essencialmente, instantiateItem só é chamado quando a exibição é instanciada e nunca mais, a menos que a exibição seja destruída (é o que acontece quando você substitui a função getItemPosition para retornar POSITION_NONE). Em vez disso, o que você quer fazer é salvar as visualizações criadas e atualizá-las no adaptador, gerar uma função get para que outra pessoa possa atualizá-la ou uma função set que atualize o adaptador (o meu favorito).

Portanto, no seu MyViewPagerAdapter, adicione uma variável como:

private View updatableView;

um em seu instanciarItem:

 public Object instantiateItem(View collection, int position) {
        updatableView = new TextView(ctx); //My change is here
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);
        return view;
    }

portanto, dessa maneira, você pode criar uma função que atualizará sua visualização:

public updateText(String txt)
{
    ((TextView)updatableView).setText(txt);
}

Espero que isto ajude!

alemão
fonte
Percebi que não estava configurando os dados instantiateItem, por isso minhas visualizações não estavam sendo atualizadas. Pela sua resposta, eu percebi isso. +1
Phani Rithvij
5

Dois anos e meio depois que o OP fez sua pergunta, essa questão ainda é, bem, ainda é uma questão. É óbvio que a prioridade do Google nisso não é particularmente alta, então, em vez de encontrar uma solução, encontrei uma solução alternativa. O grande avanço para mim foi descobrir qual era a verdadeira causa do problema (veja a resposta aceita neste post ). Depois que ficou claro que o problema era que as páginas ativas não eram atualizadas corretamente, minha solução alternativa foi óbvia:

No meu fragmento (as páginas):

  • Peguei todo o código que preenche o formulário do onCreateView e o coloquei em uma função chamada PopulateForm, que pode ser chamada de qualquer lugar, e não pela estrutura. Essa função tenta obter a Visualização atual usando getView e, se isso for nulo, ela retornará. É importante que o PopulateForm contenha apenas o código exibido - todos os outros códigos que criam os ouvintes do FocusChange e similares ainda estão no OnCreate
  • Crie um booleano que possa ser usado como um sinalizador indicando que o formulário deve ser recarregado. O meu é mbReloadForm
  • Substitua OnResume () para chamar PopulateForm () se mbReloadForm estiver definido.

Na minha atividade, onde faço o carregamento das páginas:

  • Vá para a página 0 antes de alterar qualquer coisa. Estou usando o FragmentStatePagerAdapter, então sei que duas ou três páginas são afetadas no máximo. Mudar para a página 0 garante que eu tenha apenas o problema nas páginas 0, 1 e 2.
  • Antes de limpar a lista antiga, escolha o tamanho (). Dessa forma, você sabe quantas páginas são afetadas pelo bug. Se> 3, reduza para 3 - se você estiver usando um PagerAdapter diferente, precisará ver quantas páginas precisa lidar (talvez todas?)
  • Recarregue os dados e chame pageAdapter.notifyDataSetChanged ()
  • Agora, para cada uma das páginas afetadas, verifique se a página está ativa usando pager.getChildAt (i) - isso informa se você possui uma visualização. Nesse caso, chame pager.PopulateView (). Caso contrário, defina o sinalizador ReloadForm.

Depois disso, quando você recarregar um segundo conjunto de páginas, o bug ainda fará com que alguns exibam os dados antigos. No entanto, agora eles serão atualizados e você verá os novos dados - seus usuários nunca saberão que a página estava incorreta porque essa atualização ocorrerá antes de ver a página.

Espero que isso ajude alguém!

RichardP
fonte
5

Todas essas soluções não me ajudaram. Assim, eu encontrei uma solução de trabalho: você pode setAdaptersempre, mas não é suficiente. você deve fazer isso antes de alterar o adaptador:

FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
    transaction.remove(f);
}
transaction.commit();

e depois disso:

viewPager.setAdapter(adapter);
Mahdi Perfect
fonte
Obrigado! No meu caso, usei ViewPagerfragmento interno, então substitui slideShowPagerAdapter.getFragmentManager()por getChildFragmentManager(). Talvez getFragmentManager()ajude no seu caso. Eu usei FragmentPagerAdapter, não FragmentStatePagerAdapter. Consulte também stackoverflow.com/a/25994654/2914140 para obter uma maneira hacky.
CoolMind
5

Uma maneira muito mais fácil: use FragmentPagerAdapterae agrupe suas visualizações paginadas em fragmentos. Eles são atualizados

alfongj
fonte
5

Obrigado rui.araujo e Alvaro Luis Bustamante. No começo, tento usar o caminho do rui.araujo, porque é fácil. Funciona, mas quando os dados mudam, a página será redesenhada obviamente. É ruim, então eu tento usar o caminho de Alvaro Luis Bustamante. É perfeito. Aqui está o código:

@Override
protected void onStart() {
    super.onStart();
}

private class TabPagerAdapter extends PagerAdapter {
    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public boolean isViewFromObject(final View view, final Object object) {
        return view.equals(object);
    }

    @Override
    public void destroyItem(final View container, final int position, final Object object) {
        ((ViewPager) container).removeView((View) object);
    }

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View view = LayoutInflater.from(
                getBaseContext()).inflate(R.layout.activity_approval, null, false);
        container.addView(view);
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        view.setTag(position);
        new ShowContentListTask(listView, position).execute();
        return view;
    }
}

E quando os dados mudam:

for (int i = 0; i < 4; i++) {
    View view = contentViewPager.findViewWithTag(i);
    if (view != null) {
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        new ShowContentListTask(listView, i).execute();
    }
}
evan.z
fonte
4

Caso alguém esteja usando o adaptador baseado no FragmentStatePagerAdapter (que permitirá ao ViewPager criar páginas mínimas necessárias para fins de exibição, no máximo 2 para o meu caso), a resposta de @ rui.araujo sobre a substituição de getItemPosition no seu adaptador não causará desperdício significativo, mas ainda assim pode ser melhorado.

No pseudo código:

public int getItemPosition(Object object) {
    YourFragment f = (YourFragment) object;
    YourData d = f.data;
    logger.info("validate item position on page index: " + d.pageNo);

    int dataObjIdx = this.dataPages.indexOf(d);

    if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
        logger.info("data changed, discard this fragment.");
        return POSITION_NONE;
    }

    return POSITION_UNCHANGED;
}
Jerry Tian
fonte
dentro do ViewPager, era muito fácil saber a posição anterior do item, a implementação mais perfeita é apenas retornar a nova posição getItemPosition()quando o conjunto de dados foi alterado, e o ViewPager saberá que eles foram alterados ou não. infelizmente, o ViewPager não fez isso.
VinceStyling
3

Eu tive um problema semelhante no qual tinha quatro páginas e uma das páginas atualizava as visualizações nas outras três. Consegui atualizar os widgets (SeekBars, TextViews etc.) na página adjacente à página atual. As duas últimas páginas teriam widgets não inicializados ao chamar mTabsAdapter.getItem(position).

Para resolver meu problema, usei setSelectedPage(index)antes de ligar getItem(position). Isso instanciaria a página, permitindo-me alterar valores e widgets em cada página.

Depois de toda a atualização, eu usaria setSelectedPage(position)seguido pornotifyDataSetChanged() .

Você pode ver uma ligeira oscilação no ListView na página principal de atualização, mas nada perceptível. Não testei completamente, mas resolve meu problema imediato.

enKoder
fonte
Olá, estou interessado no seu código, estou enfrentando um problema semelhante, consigo atualizar fragmentos adjacentes, mas o fragmento atual visível não é atualizado. Estou muito confuso com todas as respostas aqui. Como posso atualizar vista em fragmento de corrente que é mostrado pelo adaptador VIewPager
Pankaj Nimgade
3

Estou apenas postando esta resposta caso alguém ache útil. Para fazer exatamente a mesma coisa, simplesmente peguei o código-fonte do ViewPager e do PagerAdapter da biblioteca de compatibilidade e o compilei no meu código (você precisa resolver todos os erros e importar você mesmo, mas isso definitivamente pode ser feito).

Em seguida, no CustomViewPager, crie um método chamado updateViewAt (int position). A visualização em si pode ser obtida nos mItems ArrayList definidos na classe ViewPager (você precisa definir um ID para as visualizações no item instanciar e comparar esse ID com a posição no método updateViewAt ()). Em seguida, você pode atualizar a exibição conforme necessário.

Naveen LP
fonte
2

Eu acho que tenho a lógica do ViewPager.

Se eu precisar atualizar um conjunto de páginas e exibi-las com base no novo conjunto de dados, chamo notifyDataSetChanged () . Em seguida, o ViewPager faz várias chamadas para getItemPosition () , passando para lá o Fragment como um Objeto. Esse fragmento pode ser de um conjunto de dados antigo (que eu quero descartar) ou de um novo (que eu quero exibir). Então, eu substituo getItemPosition () e preciso determinar de alguma forma se meu Fragmento é do conjunto de dados antigo ou do novo.

No meu caso, tenho um layout de dois painéis com uma lista dos itens principais no painel esquerdo e uma exibição de furto (ViewPager) à direita. Portanto, armazeno um link para o item principal atual dentro do meu PagerAdapter e também dentro de cada Fragmento de página instanciada. Quando o item principal selecionado na lista é alterado, eu armazeno o novo item superior no PagerAdapter e chamo notifyDataSetChanged () . E no getItemPosition () substituído, comparo o item superior do meu adaptador com o item superior do meu fragmento. E somente se eles não forem iguais, retornarei POSITION_NONE. Em seguida, o PagerAdapter restabelece todos os fragmentos que retornaram POSITION_NONE.

NOTA. Armazenar o ID do item principal em vez de uma referência pode ser uma ideia melhor.

O trecho de código abaixo é um pouco esquemático, mas eu o adaptei a partir do código que realmente funciona.

public class SomeFragment extends Fragment {
  private TopItem topItem;
}

public class SomePagerAdapter extends FragmentStatePagerAdapter {
  private TopItem topItem;

  public void changeTopItem(TopItem newTopItem) {
    topItem = newTopItem;
    notifyDataSetChanged();
  }

  @Override
  public int getItemPosition(Object object) {
    if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
      return POSITION_NONE;
    }
    return super.getItemPosition(object);
  }
}

Obrigado por todos os pesquisadores anteriores!

Stanislav Karakhanov
fonte
2

O código abaixo funcionou para mim.

Crie uma classe que estenda a classe FragmentPagerAdapter como abaixo.

public class Adapter extends FragmentPagerAdapter {

private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;

public Adapter(FragmentManager fm) {
    super(fm);
}

public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
    super(fm);
    mActivity = mA;
    mFragmentManager = fm;
    object = new ArrayList<>();
    mFragmentTags = new HashMap<Integer, String>();
    this.tabCount = numberOfTabs;
}

@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return Fragment0.newInstance(mActivity);
        case 1:
            return Fragment1.newInstance(mActivity);
        case 2:
            return Fragment2.newInstance(mActivity);
        default:
            return null;
    }}


@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Log.e("Already defined","Yes");
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        Log.e("Fragment Tag","" + position + ", " + tag);
        mFragmentTags.put(position, tag);
    }else{
        Log.e("Already defined","No");
    }
    container_id = container.getId();
    this.container = container;
    if(position == 0){
        this.object.add(0,object);
    }else if(position == 1){
        this.object.add(1,object);
    }else if(position == 2){
        this.object.add(2,object);
    }
    return object;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    if (object instanceof Fragment) {
        Log.e("Removed" , String.valueOf(position));
    }
}

@Override
public int getItemPosition (Object object)
{   int index = 0;
    if(this.object.get(0) == object){
        index = 0;
    }else if(this.object.get(1) == object){
        index = 1;
    }else if(this.object.get(2) == object){
        index = 2;
    }else{
        index = -1;
    }
    Log.e("Index" , "..................." + String.valueOf(index));
    if (index == -1)
        return POSITION_NONE;
    else
        return index;
}

public String getFragmentTag(int pos){
    return "android:switcher:"+R.id.pager+":"+pos;
}

public void NotifyDataChange(){
    this.notifyDataSetChanged();
}

public int getcontainerId(){
    return container_id;
}

public ViewGroup getContainer(){
    return this.container;
}

public List<Object> getObject(){
    return this.object;
}

@Override
public int getCount() {
    return tabCount;
}}

Em seguida, dentro de cada Fragmento que você criou, crie um método updateFragment. Neste método, você altera as coisas que precisa alterar no fragmento. Por exemplo, no meu caso, o Fragment0 continha um GLSurfaceView que exibe um objeto 3d com base no caminho para um arquivo .ply, portanto, dentro do meu método updateFragment, altero o caminho para esse arquivo de dobras.

crie uma instância do ViewPager,

viewPager = (ViewPager) findViewById(R.id.pager);

e uma instância do Adpater,

adapter = new Adapter(getSupportFragmentManager(), 3, this);

então faça isso,

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);

Então, dentro da classe, você inicializou a classe Adapter acima e criou um viewPager, sempre que quiser atualizar um de seus fragmentos (no nosso caso, Fragment0), use o seguinte:

adapter.NotifyDataChange();

adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.

fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.

fragment0.updateFragment() method which include the updates on this fragment

adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.

Esta solução foi baseada na técnica sugerida por Alvaro Luis Bustamante.

Brainnovo
fonte
1

1.Primeiro você precisa definir o método getItemposition na sua classe Pageradapter 2.Você precisa ler a posição exata do seu View Pager 3.envie essa posição como local de dados do seu novo 4.Write update button onclick listener dentro do setonPageChange ouvinte

esse código de programa é um pouco modificado para definir apenas o elemento de posição específico

public class MyActivity extends Activity {

private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    data = new ArrayList<String>();
    data.add("A");
    data.add("B");
    data.add("C");
    data.add("D");
    data.add("E");
    data.add("F");

    myViewPager = (ViewPager) findViewById(R.id.pager);
    myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

      updateButton = (Button) findViewById(R.id.update);

    myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int i, float v, int i2) {
             //Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPageSelected( int i) {
          // here you will get the position of selected page
            final int k = i;
             updateViewPager(k);

        }

        @Override
        public void onPageScrollStateChanged(int i) {

        }
    });
}

private void updateViewPager(final int i) {  
    updateButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
            data.set(i, "Replaced "+i);         
            myViewPager.getAdapter().notifyDataSetChanged();
        }
    });

}

private class MyViewPagerAdapter extends PagerAdapter {

    private List<String> data;
    private Context ctx;

    public MyViewPagerAdapter(Context ctx, List<String> data) {
        this.ctx = ctx;
        this.data = data;
    }

    @Override
    public int getCount() {
        return data.size();
    }

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

    @Override
    public Object instantiateItem(View collection, int position) {          

        TextView view = new TextView(ctx);
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);            
        return view;
    }

    @Override
    public void destroyItem(View collection, int position, Object view) {
         ((ViewPager) collection).removeView((View) view);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable arg0, ClassLoader arg1) {
    }

    @Override
    public void startUpdate(View arg0) {
    }

    @Override
    public void finishUpdate(View arg0) {
    }
}
}
Naveen Kumar
fonte
1

o que funcionou para mim estava indo viewPager.getAdapter().notifyDataSetChanged();

e no adaptador colocando seu código para atualizar a visualização do lado de dentro getItemPositiondessa maneira

@Override
public int getItemPosition(Object object) {

    if (object instanceof YourViewInViewPagerClass) { 
        YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
        view.setData(data);
    }

    return super.getItemPosition(object);
}

pode não ser a maneira mais correta de fazer isso, mas funcionou (o return POSITION_NONEtruque causou um acidente para mim, então não era uma opção)

Fonix
fonte
1

Você pode atualizar dinamicamente todos os fragmentos, é possível ver em três etapas.

No seu adaptador:

public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;

public MyPagerAdapter(FragmentManager fragmentManager) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mFragmentTags = new HashMap<Integer, String>();
}

// Returns total number of pages
@Override
public int getCount() {
    return NUM_ITEMS;
}

// Returns the fragment to display for that page
@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return FirstFragment.newInstance();
        case 1:
            return SecondFragment.newInstance();
        case 2:
            return ThirdFragment.newInstance();
        default:
            return null;
    }
}

// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
    return "Page " + position;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        mFragmentTags.put(position, tag);
    }
    return object;
}

public Fragment getFragment(int position) {
    Fragment fragment = null;
    String tag = mFragmentTags.get(position);
    if (tag != null) {
        fragment = mFragmentManager.findFragmentByTag(tag);
    }
    return fragment;
}}

Agora em sua atividade:

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{

MyPagerAdapter mAdapterViewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
    mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
    viewPager.setAdapter(mAdapterViewPager);
    viewPager.addOnPageChangeListener(this);
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {

    Fragment fragment = mAdapterViewPager.getFragment(position);
    if (fragment != null) {
        fragment.onResume();
    }
}

@Override
public void onPageScrollStateChanged(int state) {

}}

Finalmente no seu fragmento, algo assim:

public class YourFragment extends Fragment {

// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {

    return new YourFragment();
}

// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment, container, false);
}


@Override
public void onResume() {
    super.onResume();

    //to refresh your view
    refresh();

}}

Você pode ver o código completo aqui .

Obrigado Alvaro Luis Bustamante.

Cabezas
fonte
1

Sempre retornar POSITION_NONEé simples, mas um pouco ineficiente, porque evoca instanciação de todas as páginas que já foram instanciadas.

Eu criei uma biblioteca ArrayPagerAdapter para alterar itens no PagerAdapters dinamicamente.

Internamente, adaptadores desta biblioteca retornar POSITION_NONEem getItemPosiition()apenas quando necessário.

Você pode alterar itens dinamicamente, como segue, usando esta biblioteca.

@Override
protected void onCreate(Bundle savedInstanceState) {
        /** ... **/
    adapter = new MyStatePagerAdapter(getSupportFragmentManager()
                            , new String[]{"1", "2", "3"});
    ((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
     adapter.add("4");
     adapter.remove(0);
}

class MyPagerAdapter extends ArrayViewPagerAdapter<String> {

    public MyPagerAdapter(String[] data) {
        super(data);
    }

    @Override
    public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
        View v = inflater.inflate(R.layout.item_page, container, false);
        ((TextView) v.findViewById(R.id.item_txt)).setText(item);
        return v;
    }
}

A biblioteca Thils também suporta páginas criadas por Fragments.

T.Nakama
fonte
1

Isso é para todos os que gostam de mim, que precisam atualizar o Viewpager a partir de um serviço (ou outro encadeamento em segundo plano) e nenhuma das propostas funcionou: Após um pouco de verificação de log, percebi que o método notifyDataSetChanged () nunca retorna. getItemPosition (objeto Objeto) é chamado de tudo termina aí sem processamento adicional. Então eu encontrei nos documentos da classe PagerAdapter pai (não está nos documentos das subclasses), "As alterações do conjunto de dados devem ocorrer no thread principal e devem terminar com uma chamada para notifyDataSetChanged ()". Portanto, a solução de trabalho nesse caso foi (usando FragmentStatePagerAdapter e getItemPosition (objeto Objeto) definido para retornar POSITION_NONE):

e depois a chamada para notifyDataSetChanged ():

runOnUiThread(new Runnable() {
         @Override
         public void run() {
             pager.getAdapter().notifyDataSetChanged();
         }
     });
Helmwag
fonte
1

Você pode adicionar transformação de pager no Viewpager assim

myPager.setPageTransformer(true, new MapPagerTransform());

No código abaixo, mudei minha cor de exibição no tempo de execução quando o pager rolar

public class MapPagerTransform implements ViewPager.PageTransformer {


    public void transformPage(View view, float position) {
     LinearLayout  showSelectionLL=(LinearLayout)view.findViewById(R.id.showSelectionLL);

     if (position < 0) {

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else if (position >0){

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else {
                showSelectionLL.setBackgroundColor(Color.RED);
     }
   }
}
Akshay dashore
fonte
1

Eu sei que estou atrasado, mas ainda assim pode ajudar alguém. Estou apenas estendendo a resposta accetping e também adicionei o comentário.

bem,

a resposta em si diz que é ineficiente

Então, para torná-lo atualizado somente quando necessário, você pode fazer isso

private boolean refresh;

public void refreshAdapter(){
    refresh = true;
    notifyDataSetChanged();
}

@Override
public int getItemPosition(@NonNull Object object) {
    if(refresh){
        refresh = false;
        return POSITION_NONE;
    }else{
        return super.getItemPosition(object);
    }
}
exemplo
fonte
1

O ViewPager não foi projetado para suportar alterações dinâmicas na exibição.

Confirmei isso enquanto procurava outro bug relacionado a este https://issuetracker.google.com/issues/36956111 e, em particular, https://issuetracker.google.com/issues/36956111#comment56

Esta pergunta é um pouco antiga, mas o Google recentemente resolveu esse problema com o ViewPager2 . Permitirá substituir soluções artesanais (não mantidas e potencialmente com erros) por uma solução padrão. Também impede a recriação de visualizações desnecessariamente, como algumas respostas.

Para exemplos do ViewPager2, você pode verificar https://github.com/googlesamples/android-viewpager2

Se você deseja usar o ViewPager2, precisará adicionar a seguinte dependência no arquivo build.gradle:

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }

Em seguida, você pode substituir o seu ViewPager no seu arquivo xml por:

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

Depois disso, você precisará substituir o ViewPager pelo ViewPager2 em sua atividade

O ViewPager2 precisa de um RecyclerView.Adapter ou um FragmentStateAdapter, no seu caso, pode ser um RecyclerView.Adapter

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private Context context;
    private ArrayList<String> arrayList = new ArrayList<>();

    public MyAdapter(Context context, ArrayList<String> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tvName.setText(arrayList.get(position));
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tvName);
        }
    }
} 

No caso de você estar usando um TabLayout, você pode usar um TabLayoutMediator:

        TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
            @Override
            public void onConfigureTab(@NotNull TabLayout.Tab tab, int position) {
                // configure your tab here
                tab.setText(tabs.get(position).getTitle());
            }
        });

        tabLayoutMediator.attach();

Em seguida, você poderá atualizar suas visualizações modificando os dados do adaptador e chamando o método notifyDataSetChanged

Yves Delerm
fonte
0

Acho que fiz uma maneira simples de notificar as alterações no conjunto de dados:

Primeiro, mude um pouco a maneira como a função instantiateItem funciona:

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View rootView = mInflater.inflate(...,container, false);
        rootView.setTag(position);
        updateView(rootView, position);
        container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mViewPager.setObjectForPosition(rootView, position);
        return rootView;
    }

para "updateView", preencha a visualização com todos os dados que você deseja preencher (setText, setBitmapImage, ...).

verifique se destroyView funciona assim:

    @Override
    public void destroyItem(final ViewGroup container, final int position, final Object obj) {
        final View viewToRemove = (View) obj;
        mViewPager.removeView(viewToRemove);
    }

Agora, suponha que você precise alterar os dados, faça-o e chame a próxima função no PagerAdapter:

    public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
            final NotifyLocation toPos) {
        final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
        final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        if (fromPosInt <= toPosInt) {
            notifyDataSetChanged();
            for (int i = fromPosInt; i <= toPosInt; ++i) {
                final View pageView = viewPager.findViewWithTag(i);
                mPagerAdapter.updateView(pageView, i);
            }
        }
    }

public enum NotifyLocation {
    MOST_LEFT, CENTER, MOST_RIGHT
}

Por exemplo, se você deseja notificar todas as visualizações que estão sendo mostradas pelo viewPager que algo mudou, você pode chamar:

notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);

É isso aí.

desenvolvedor android
fonte
0

Pelo que vale a pena, no KitKat + parece que adapter.notifyDataSetChanged()é suficiente para fazer com que as novas visualizações sejam exibidas, desde que você seja setOffscreenPageLimitsuficientemente alto. Eu sou capaz de obter o comportamento desejado fazendo viewPager.setOffscreenPageLimit(2).

mszaro
fonte
0

Eu realmente usar notifyDataSetChanged()em ViewPagere CirclePageIndicatore depois que eu chamo destroyDrawingCache()no ViewPagere ele funciona .. Nenhuma das outras soluções trabalhou para mim.

czaku
fonte
A pergunta original tem quase 2 anos, tenho certeza de que existem muitas diferenças na API do Android até agora. As respostas aqui podem não estar corretas para todas as versões da API do Android.
C0deAttack