Estou tentando adicionar e remover dinamicamente fragmentos de um ViewPager, adicionando obras sem problemas, mas a remoção não funciona conforme o esperado.
Sempre que eu quero remover o item atual, o último é removido.
Também tentei usar um FragmentStatePagerAdapter ou retornar POSITION_NONE no método getItemPosition do adaptador.
O que estou fazendo de errado?
Aqui está um exemplo básico:
MainActivity.java
public class MainActivity extends FragmentActivity implements TextProvider {
private Button mAdd;
private Button mRemove;
private ViewPager mPager;
private MyPagerAdapter mAdapter;
private ArrayList<String> mEntries = new ArrayList<String>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mEntries.add("pos 1");
mEntries.add("pos 2");
mEntries.add("pos 3");
mEntries.add("pos 4");
mEntries.add("pos 5");
mAdd = (Button) findViewById(R.id.add);
mRemove = (Button) findViewById(R.id.remove);
mPager = (ViewPager) findViewById(R.id.pager);
mAdd.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
addNewItem();
}
});
mRemove.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
removeCurrentItem();
}
});
mAdapter = new MyPagerAdapter(this.getSupportFragmentManager(), this);
mPager.setAdapter(mAdapter);
}
private void addNewItem() {
mEntries.add("new item");
mAdapter.notifyDataSetChanged();
}
private void removeCurrentItem() {
int position = mPager.getCurrentItem();
mEntries.remove(position);
mAdapter.notifyDataSetChanged();
}
@Override
public String getTextForPosition(int position) {
return mEntries.get(position);
}
@Override
public int getCount() {
return mEntries.size();
}
private class MyPagerAdapter extends FragmentPagerAdapter {
private TextProvider mProvider;
public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
super(fm);
this.mProvider = provider;
}
@Override
public Fragment getItem(int position) {
return MyFragment.newInstance(mProvider.getTextForPosition(position));
}
@Override
public int getCount() {
return mProvider.getCount();
}
}
}
TextProvider.java
public interface TextProvider {
public String getTextForPosition(int position);
public int getCount();
}
MyFragment.java
public class MyFragment extends Fragment {
private String mText;
public static MyFragment newInstance(String text) {
MyFragment f = new MyFragment(text);
return f;
}
public MyFragment() {
}
public MyFragment(String text) {
this.mText = text;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment, container, false);
((TextView) root.findViewById(R.id.position)).setText(mText);
return root;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="add new item" />
<Button
android:id="@+id/remove"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="remove current item" />
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1" />
</LinearLayout>
fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/position"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="35sp" />
</LinearLayout>
MyFragment.java
??Respostas:
A solução de Louth não foi suficiente para fazer as coisas funcionarem para mim, pois os fragmentos existentes não estavam sendo destruídos. Motivado por esta resposta , descobri que a solução é substituir o
getItemId(int position)
método deFragmentPagerAdapter
fornecer um novo ID exclusivo sempre que houver uma alteração na posição esperada de um fragmento.Código fonte:
Agora, por exemplo, se você excluir uma única guia ou alterar o pedido, ligue
notifyChangeInPosition(1)
antes de ligarnotifyDataSetChanged()
, o que garantirá que todos os Fragmentos serão recriados.Por que esta solução funciona
Substituindo getItemPosition ():
Quando
notifyDataSetChanged()
é chamado, o adaptador chama onotifyChanged()
método aoViewPager
qual está conectado. EmViewPager
seguida, verifica o valor retornado pelos adaptadoresgetItemPosition()
para cada item, removendo os itens que retornamPOSITION_NONE
(consulte o código-fonte ) e repovoando.Substituindo getItemId ():
Isso é necessário para impedir que o adaptador recarregue o fragmento antigo quando ele
ViewPager
estiver sendo preenchido novamente. Você pode entender facilmente por que isso funciona olhando o código-fonte para instantiateItem () emFragmentPagerAdapter
.Como você pode ver, o
getItem()
método é chamado apenas se o gerenciador de fragmentos não encontrar fragmentos existentes com o mesmo ID. Para mim, parece um bug que os fragmentos antigos ainda estão anexados mesmo depois denotifyDataSetChanged()
serem chamados, mas a documentação paraViewPager
afirma claramente que:Portanto, esperamos que a solução alternativa fornecida aqui não seja necessária em uma versão futura da biblioteca de suporte.
fonte
notifyDataSetChanged()
funcione.O ViewPager não remove seus fragmentos com o código acima, porque carrega várias visualizações (ou fragmentos no seu caso) na memória. Além da vista visível, também carrega a vista em ambos os lados da vista visível. Isso fornece a rolagem suave de uma visualização para outra, o que torna o ViewPager tão legal.
Para alcançar o efeito desejado, você precisa fazer algumas coisas.
Altere o FragmentPagerAdapter para um FragmentStatePagerAdapter. A razão para isso é que o FragmentPagerAdapter manterá todas as visualizações carregadas na memória para sempre. Onde o FragmentStatePagerAdapter descarta as visualizações que ficam fora das visualizações atuais e passíveis de travessia.
Substitua o método do adaptador getItemPosition (mostrado abaixo). Quando ligamos,
mAdapter.notifyDataSetChanged();
o ViewPager interroga o adaptador para determinar o que mudou em termos de posicionamento. Usamos esse método para dizer que tudo mudou e reprocessar todo o seu posicionamento de exibição.E aqui está o código ...
fonte
minha solução de trabalho para remover a página de fragmento do modo de exibição pager
E quando eu preciso remover alguma página por índice, faço isso
Aqui está a inicialização do meu adaptador
fonte
O fragmento já deve ser removido, mas o problema foi o estado de salvamento do viewpager
Experimentar
Nada funcionou, mas isso resolveu o problema!
Felicidades !
fonte
Eu tive a ideia de simplesmente copiar o código-fonte
android.support.v4.app.FragmentPagerAdpater
para uma classe personalizada chamadaCustumFragmentPagerAdapter
. Isso me deu a chance de modificar oinstantiateItem(...)
modo que, toda vez que é chamado, ele remove / destrói o fragmento atualmente anexado antes de adicionar o novo fragmento recebido dogetItem()
método.Simplesmente modifique
instantiateItem(...)
da seguinte maneira:fonte
Você pode combinar os dois para melhor:
fonte
Para futuros leitores!
Agora você pode usar o ViewPager2 para adicionar dinamicamente, remover fragmentos do viewpager.
Referência da API do formulário de cotação
Veja
MutableCollectionFragmentActivity.kt
em googlesample / android-viewpager2 um exemplo de adição e remoção dinâmica de fragmentos do viewpager.Para a sua informação:
Artigos:
Referência da API
Notas de versão
Repo de amostras: https://github.com/googlesamples/android-viewpager2
fonte
A resposta de Louth funciona bem. Mas acho que nem sempre retornar POSITION_NONE é uma boa ideia. Porque POSITION_NONE significa que o fragmento deve ser destruído e um novo fragmento será criado. Você pode verificar isso na função dataSetChanged no código fonte do ViewPager.
Então, acho melhor você usar uma lista de array de fracaReference para salvar todos os fragmentos que você criou. E quando você adiciona ou remove alguma página, pode obter a posição correta em sua própria lista de matrizes.
De acordo com os comentários, getItemPosition é chamado quando a visualização do host está tentando determinar se a posição de um item foi alterada. E o valor de retorno significa sua nova posição.
Mas isso não é suficiente. Ainda temos um passo importante a dar. No código-fonte do FragmentStatePagerAdapter, há uma matriz chamada "mFragments" que armazena em cache os fragmentos que não são destruídos. E na função instantiateItem.
Ele retornou o fragmento em cache diretamente quando descobriu que o fragmento em cache não é nulo. Portanto, há um problema. Por exemplo, vamos excluir uma página na posição 2. Primeiramente, removemos esse fragmento de nossa própria lista de matriz de referência. portanto, em getItemPosition, ele retornará POSITION_NONE para esse fragmento e, em seguida, esse fragmento será destruído e removido de "mFragments".
Agora o fragmento na posição 3 estará na posição 2. E o item instantiatedItem com a posição 3 do param será chamado. No momento, o terceiro item em "mFramgents" não é nulo; portanto, ele retornará diretamente. Mas, na verdade, o que retornou é o fragmento na posição 2. Então, quando entrarmos na página 3, encontraremos uma página vazia lá.
Para contornar esse problema. Meu conselho é que você pode copiar o código-fonte do FragmentStatePagerAdapter em seu próprio projeto e, ao adicionar ou remover operações, adicione e remova elementos na lista de matrizes "mFragments".
As coisas serão mais simples se você apenas usar o PagerAdapter em vez do FragmentStatePagerAdapter. Boa sorte.
fonte
fonte
Eu tive alguns problemas com
FragmentStatePagerAdapter
. Depois de remover um item:Depois de muitas experiências, eu vim com a seguinte solução.
Esta solução usa apenas um hack no caso de remover um item. Caso contrário (por exemplo, ao adicionar um item), ele mantém a limpeza e o desempenho de um código original.
Obviamente, do lado de fora do adaptador, você chama apenas addItem / removeItem, não é necessário chamar notifyDataSetChanged ().
fonte
Experimente esta solução. Eu usei a ligação de dados para a visualização de ligação. Você pode usar a função "findViewById ()" comum.
fonte
Eu adicionei uma função "clearFragments" e usei essa função para limpar o adaptador antes de definir os novos fragmentos. Isso chama as ações apropriadas de remoção de fragmentos. Minha classe pagerAdapter:
fonte
eu resolvi esse problema com essas etapas
1- use FragmentPagerAdapter
2- em cada fragmento, crie um ID aleatório
3- substituir getItemPosition no adaptador
GetItemId de 4 substituições no adaptador
5- agora o código de exclusão é
isso funcionou para mim, espero ajudar
fonte
Meu código de versão final, corrigiu TODOS os erros. Levei 3 dias
Atualizado em 2020/07/18: mudei muito o código-fonte e consertei muitos bugs, mas não prometo que ainda funcione hoje.
https://github.com/lin1987www/FragmentBuilder/blob/master/commonLibrary/src/main/java/android/support/v4/app/FragmentStatePagerAdapterFix.java
fonte
Você pode simplesmente substituir o
destroyItem
métodofonte
Espero que isso possa ajudar o que você deseja.
fonte