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) {
}
}
}
fonte
Respostas:
Existem várias maneiras de conseguir isso.
A primeira opção é mais fácil, mas um pouco mais ineficiente.
Substitua
getItemPosition
daPagerAdapter
seguinte maneira: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 o
setTag()
métodoinstantiateItem()
ao instanciar uma nova exibição. Em vez de usarnotifyDataSetChanged()
, você pode usarfindViewWithTag()
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.
fonte
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
PagerAdapter
eViewPager
, e eu encontrei o seguinte:O
notifyDataSetChanged()
método noPagerAdapter
notificará apenasViewPager
que as páginas subjacentes foram alteradas. Por exemplo, se você criou / excluiu páginas dinamicamente (adicionando ou removendo itens da sua lista), vocêViewPager
deve cuidar disso. Nesse caso, acho que oViewPager
determina se uma nova visão deve ser excluída ou instanciada usando os métodosgetItemPosition()
egetCount()
.eu penso isso
ViewPager
, após umanotifyDataSetChanged()
chamada, obtém as visualizações de crianças e verifica sua posição com ogetItemPosition()
. Se, para uma visão filho, esse método retornarPOSITION_NONE
, eleViewPager
entenderá que a visão foi excluída, chamando o métododestroyItem()
e removendo essa visão.Dessa forma, substituindo
getItemPosition()
para sempre retornarPOSITION_NONE
estará 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ê ligarnotifyDatasetChanged()
. Pode não parecer tão errado apenas por algunsTextView
segundos, 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 noinstantiateItem()
método. Portanto, quando você deseja alterar os dados ou invalidar a visualização necessária, pode chamar ofindViewWithTag()
métodoViewPager
para 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
TextView
se deseja atualizar apenas um valor periodicamente. Com as abordagens explicadas anteriormente, isso significa que você está removendo e instanciando 100TextView
s em cada atualização. Não faz sentido...fonte
setTag
no métodoonInstantiateItem
. Gostaria de atualizar esta resposta com alguma codificação? Obrigado.Mude
FragmentPagerAdapter
paraFragmentStatePagerAdapter
.Substituir
getItemPosition()
método e retornoPOSITION_NONE
.Eventualmente, ele ouvirá o
notifyDataSetChanged()
pager no modo de exibição.fonte
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:
Em seguida, substituindo o
notifyDataSetChanged
método, você pode atualizar as visualizações ...Você pode realmente usar código semelhante
instantiateItem
enotifyDataSetChanged
atualizar sua exibição. No meu código eu uso exatamente o mesmo método.fonte
Teve o mesmo problema. Para mim, trabalhou para estender o FragmentStatePagerAdapter e substituir os métodos abaixo:
fonte
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
ViewPager
com que destruísse o antigoFragment
e preenchesse opager
com o novoFragment
s. Resolvi o problema da seguinte maneira:1) Faça a
ViewPager
aula estender daFragmentPagerAdapter
seguinte maneira:2) Crie um item para
ViewPager
esse armazenamentotitle
e ofragment
seguinte:3) Faça o construtor do
ViewPager
take minhaFragmentManager
instância para armazená-lo no meu daclass
seguinte maneira:4) Crie um método para re-definir os
adapter
dados com os novos dados, eliminando todos os anterioresfragment
dafragmentManager
própria diretamente para fazer aadapter
para definir o novofragment
a partir da nova lista novamente da seguinte forma:5) No contêiner
Activity
ouFragment
não reinicialize o adaptador com os novos dados. Defina os novos dados através do métodosetPagerItems
com os novos dados da seguinte maneira:Espero que ajude.
fonte
Eu tive o mesmo problema e minha solução está usando
FragmentPagerAdapter
com a substituição deFragmentPagerAdapter#getItemId(int position)
:Por padrão, esse método retorna a posição do item. Suponho que
ViewPager
verifique seitemId
foi alterado e recria a página apenas se for. Porém, a versão não substituída retorna a mesma posição,itemId
mesmo 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.
fonte
PagerAdapter
. Que classe é essa?FragmentPagerAdapter
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
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:
um em seu instanciarItem:
portanto, dessa maneira, você pode criar uma função que atualizará sua visualização:
Espero que isto ajude!
fonte
instantiateItem
, por isso minhas visualizações não estavam sendo atualizadas. Pela sua resposta, eu percebi isso. +1Dois 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):
Na minha atividade, onde faço o carregamento das páginas:
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!
fonte
Todas essas soluções não me ajudaram. Assim, eu encontrei uma solução de trabalho: você pode
setAdapter
sempre, mas não é suficiente. você deve fazer isso antes de alterar o adaptador:e depois disso:
fonte
ViewPager
fragmento interno, então substituislideShowPagerAdapter.getFragmentManager()
porgetChildFragmentManager()
. TalvezgetFragmentManager()
ajude no seu caso. Eu useiFragmentPagerAdapter
, nãoFragmentStatePagerAdapter
. Consulte também stackoverflow.com/a/25994654/2914140 para obter uma maneira hacky.Uma maneira muito mais fácil: use
FragmentPagerAdapter
ae agrupe suas visualizações paginadas em fragmentos. Eles são atualizadosfonte
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:
E quando os dados mudam:
fonte
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:
fonte
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.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 ligargetItem(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.
fonte
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.
fonte
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.
Obrigado por todos os pesquisadores anteriores!
fonte
O código abaixo funcionou para mim.
Crie uma classe que estenda a classe FragmentPagerAdapter como abaixo.
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,
e uma instância do Adpater,
então faça isso,
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:
Esta solução foi baseada na técnica sugerida por Alvaro Luis Bustamante.
fonte
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
fonte
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
getItemPosition
dessa maneirapode não ser a maneira mais correta de fazer isso, mas funcionou (o
return POSITION_NONE
truque causou um acidente para mim, então não era uma opção)fonte
Você pode atualizar dinamicamente todos os fragmentos, é possível ver em três etapas.
No seu adaptador:
Agora em sua atividade:
Finalmente no seu fragmento, algo assim:
Você pode ver o código completo aqui .
Obrigado Alvaro Luis Bustamante.
fonte
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_NONE
emgetItemPosiition()
apenas quando necessário.Você pode alterar itens dinamicamente, como segue, usando esta biblioteca.
A biblioteca Thils também suporta páginas criadas por Fragments.
fonte
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 ():
fonte
Você pode adicionar transformação de pager no Viewpager assim
No código abaixo, mudei minha cor de exibição no tempo de execução quando o pager rolar
fonte
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
fonte
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:
Em seguida, você pode substituir o seu ViewPager no seu arquivo xml por:
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
No caso de você estar usando um TabLayout, você pode usar um TabLayoutMediator:
Em seguida, você poderá atualizar suas visualizações modificando os dados do adaptador e chamando o método notifyDataSetChanged
fonte
Em vez de retornar
POSITION_NONE
e criar todos os fragmentos novamente, você pode fazer o que sugeri aqui: Atualizar o ViewPager dinamicamente?fonte
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:
para "updateView", preencha a visualização com todos os dados que você deseja preencher (setText, setBitmapImage, ...).
verifique se destroyView funciona assim:
Agora, suponha que você precise alterar os dados, faça-o e chame a próxima função no PagerAdapter:
Por exemplo, se você deseja notificar todas as visualizações que estão sendo mostradas pelo viewPager que algo mudou, você pode chamar:
É isso aí.
fonte
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ê sejasetOffscreenPageLimit
suficientemente alto. Eu sou capaz de obter o comportamento desejado fazendoviewPager.setOffscreenPageLimit(2)
.fonte
Eu realmente usar
notifyDataSetChanged()
emViewPager
eCirclePageIndicator
e depois que eu chamodestroyDrawingCache()
noViewPager
e ele funciona .. Nenhuma das outras soluções trabalhou para mim.fonte