PagerAdapter.notifyDataSetChanged не обновляет фрагменты

Мой Activity содержит ViewPager и его собственный Adapter, который расширяет FragmentStatePagerAdapter. ViewPager содержит 3 фрагмента

Код для удаления фрагментов из ViewPager

MainActivity

public void deleteElement(){
    final int currentItem = mViewPager.getCurrentItem();
    mPagerAdapter.removeItem(currentItem);
    mPagerAdapter.notifyDataSetChanged();
}

CustomPagerAdapter

private ArrayList<Item> data;
public void removeItem(int index){
        data.remove(index);
}

при удалении среднего Fragment (индекс 1):

  • из data удаляю правильный item.
  • Проблема в том, что я (я полагаю) Fragment 3 удаляется после notifyDataSetChanged, а текущий Fragment по-прежнему является фрагментом, который видел пользователь, а данные, которые загружаются, взяты из пакета SavedInstance

Таким образом, конечный результат состоит в том, что пользователь по-прежнему видит то же Fragment, которое он пытался удалить, что для него отстой.

Идеи?

***** ИЗМЕНИТЬ ******

похоже на ViewPager2 Функции могут решить эту проблему, а также многие другие проблемы.


person royB    schedule 23.01.2015    source источник
comment
Я бы посоветовал перейти на мой ArrayPagerAdapter, который этим занимается. Я не очень удивлен, что у FragmentStatePagerAdapter есть проблемы в этой области, поэтому я накатил кое-что отдельно.   -  person CommonsWare    schedule 24.01.2015
comment
Попробуйте это   -  person Hai nguyen thanh    schedule 08.11.2016
comment
Хорошее простое решение: stackoverflow.com/a/35450575/2162226   -  person Gene Bo    schedule 03.05.2018
comment
@gnB это не решение этой проблемы   -  person Emanuel Graf    schedule 05.05.2018


Ответы (7)


Я знаю, что этот вопрос немного устарел, но может быть интересно узнать, что Google недавно решил эту проблему с помощью ViewPager2. См. Примеры здесь

Прежде всего, следующая зависимость в вашем файле build.gradle:

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

Теперь вы можете заменить ViewPager в XML-файле на:

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

Затем вам нужно будет заменить ViewPager на ViewPager2 в своей деятельности.

ViewPager2 требуется либо RecycleView.Adapter, либо FragmentStateAdapter:

    public class TabAdapter extends FragmentStateAdapter {
        private final List<Tab> tabs;

        public TabAdapter(@NonNull FragmentManager fm, List<Tab> tabs, Lifecycle lifecycle) {
            super(fm, lifecycle);

            this.tabs = tabs;
        }

        @NonNull
        @Override
        public Fragment createFragment(int position) {
            //create your fragment here
        }

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

        @Override
        public long getItemId(int position) {
            // use a distinct id for each item to allow the adapter to see the changes
        }
    }

В случае, если вы использовали TabLayout, вы можете использовать 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();

Чтобы удалить данные, после удаления элемента из вашей коллекции вы можете вызвать метод notifyDataSetChanged() или, более конкретно, notifyItemRemoved() с позицией удаленного элемента.

person Yves Delerm    schedule 07.08.2019
comment
Добавьте, пожалуйста, пример удаления данных, и я одобряю ваш ответ. Спасибо! - person royB; 07.08.2019
comment
Я только что отредактировал свой ответ, чтобы показать, как удалить элемент. Я также добавляю в адаптер метод getItemId, он может понадобиться адаптеру для отслеживания изменений. - person Yves Delerm; 07.08.2019
comment
Согласно JavaDoc getItemId(int) вы также должны переопределить containsItem(long) When overriding, also override {@link #containsItem(long)}. - person Laurent Russier; 05.12.2019

Попробуйте добавить это:

private class MyPagerAdapter extends FragmentStatePagerAdapter {

//... your existing code

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

}
person kelvincer    schedule 23.01.2015
comment
но тогда это заставит Android перезапустить все мои фрагменты, что бессмысленно, нет? - person royB; 23.01.2015
comment
Да, здесь другие параметры - person kelvincer; 23.01.2015
comment
Я знаю об этих двух вариантах, и я видел эти сообщения, прежде чем задать вопрос. проблема в том, что я не обновляю представление, а удаляю его. Я не могу быть уверен, какое представление будет удалено, поэтому вызов фрагмента по тегу небезопасен (возможно, он уже уничтожен? Следует ли мне обновить все остальные фрагменты?) - person royB; 23.01.2015
comment
Спасибо за работу: расширяет FragmentStatePagerAdapter - person krishan mohan verma; 01.07.2019

У вас нет другого выбора, кроме как реализовать PagerAdapter#getItemPosition. Глядя на источник, именно так ViewPager определяет, как обновлять свою позицию, добавлять новые или офигительные фрагменты после notifyDataSetChanged. Это не должно быть слишком сложно, вам просто нужно получить доступ к состоянию переданного фрагмента, и вам также необходимо получить доступ к главному состоянию в вашей деятельности, которое содержит ваш заказ.

Также необходимо реализовать getItemId, чтобы возвращать согласованный идентификатор для каждого фрагмента, потому что их можно переупорядочить.

Вот как я обработал это в ViewPager фотографий.

    private inner class PhotoPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
    override fun getItem(position: Int): PhotoViewFragment {
        // getItem is called to instantiate the fragment for the given page.
        return PhotoViewFragment.newInstance(position, [email protected][position])
    }

    override fun getItemId(position: Int): Long {
        return [email protected][position].ID!!.hashCode().toLong()
    }

    override fun getItemPosition(`object`: Any): Int {
        val frag = `object` as PhotoViewFragment
        val idx = [email protected] { ph -> ph.ID == frag.dataBinding.photo.ID }
        return if (idx >= 0) idx else PagerAdapter.POSITION_NONE
    }

    override fun getCount(): Int {
        return [email protected]
    }
}
person androidguy    schedule 07.09.2017

Вы можете попробовать использовать FragmentPagerAdapter вместо FragmentStatePagerAdapter. Дело в том, что я тоже столкнулся с этой проблемой, и это мне помогло.

person Chhatrasal Singh    schedule 24.02.2018
comment
однострочное решение должно быть размещено в комментарии. - person Rumit Patel; 24.02.2018

Вы должны сначала установить для адаптера значение null, а затем вернуть его:

int currentPosition = mViewPager.getCurrentItem();
mPagerAdapter.notifyDataSetChanged();
mViewPager.setAdapter(null);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setCurrentItem(currentPosition);
person Matjaz Kristl    schedule 14.09.2018
comment
int getItemPosition (объект объекта) {return POSITION_NONE; } - person krishan mohan verma; 01.07.2019

Различите фрагмент, тогда система Android позаботится о переработке фрагментов.

Например, переопределите getItemId, указав уникальный идентификатор.

FragmentStateAdapter (ViewPager2) - версия Kotlin.

class HomeViewPagerAdapter(
fa: FragmentActivity
) : FragmentStateAdapter(fa) {

lateinit var fragments: ArrayList<Fragments>

override fun getItemCount(): Int {
    return fragments.size
}

override fun getItemId(position: Int): Long {
    return fragments[position].id.ordinal.toLong()
}
override fun createFragment(position: Int): Fragment {
    return fragments[position].fragment
}
}
data class Fragments (val id: FragmentId, val fragment: Fragment)
enum class FragmentId {
    PageOne, 
    PageTwo,
    PageThree
}

Обновить адаптер

    private fun setupAdapter(fragments: ArrayList<Fragments>) {
    if (adapter != null) {
        adapter?.fragments = fragments
        adapter?.notifyDataSetChanged()
    } else {
        adapter = HomeViewPagerAdapter(requireActivity())
        adapter?.fragments = fragments
        binding.homeViewPager.adapter = adapter
        binding.locationDisabledLl.setOnClickListener {
            findNavController().navigate(HomeFragmentDirections.actionHomeToPermission())
        }
    }
}
person ManiKandan Selvanathan    schedule 30.06.2021

Вы можете попробовать использовать FragmentStatePagerAdapter вместо FragmentPagerAdapterpublic

Это работа для меня ..!

person krishan mohan verma    schedule 01.07.2019