NotifyDataSetChanged не работает в FragmentStateAdapter с Viewpager2

Я делаю страницу выбора файла с помощью viewpager2 с FragmentStateAdapter. На одной из страниц я показываю все подключенные устройства хранения, на которые я хотел бы нажать, чтобы просмотреть содержимое, но я хочу, чтобы содержимое было на другом фрагменте.

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

Мой пейджер

public static class PagerAdapter extends FragmentStateAdapter {


        private Fragment mFragmentAtPos0;
        private final FragmentManager mFragmentManager;
        private final FirstPageListener listener = new FirstPageListener();
        public final class FirstPageListener implements
                FirstPageFragmentListener {
            public void onSwitchToNextFragment() {
                mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
                        .commit();
                if (mFragmentAtPos0 instanceof FilesandFolder_Others_MainPage){
                    mFragmentAtPos0 = new FileExplorer(listener);
                }else{ // Instance of NextFragment
                    mFragmentAtPos0 = new FilesandFolder_Others_MainPage(listener);
                }
                notifyDataSetChanged();
            }
            public void onSwitchToNextFragment(Bundle bundle) {
//                mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
//                        .commit();
                if (mFragmentAtPos0 instanceof FilesandFolder_Others_MainPage){
                    mFragmentAtPos0 = new FileExplorer(listener);
                    mFragmentAtPos0.setArguments(bundle);
                }else{ // Instance of NextFragment
                    mFragmentAtPos0 = new FilesandFolder_Others_MainPage(listener);
                    mFragmentAtPos0.setArguments(bundle);
                }
                notifyDataSetChanged();
//                notifyItemChanged(getItemPosition(mFragmentAtPos0));
            }
        }


//        @Override
//        public void onBindViewHolder(@NonNull FragmentViewHolder holder, int position, @NonNull List<Object> payloads) {
//            super.onBindViewHolder(holder, position, payloads);
//            if (position == getItemPosition(mFragmentAtPos0)) {
//
//                Fragment f = mFragmentManager.findFragmentByTag("f" + holder.getItemId());
//                if (f != null) {
//                    mFragmentManager.beginTransaction().replace(holder.getItemId(), )
//                }
//            }
//        }

        private int getItemPosition(Fragment mFragmentAtPos0) {
            for (int i = 0; i < getItemCount(); i++){

                if (createFragment(i).equals(mFragmentAtPos0)){
                    return i;
                }
            }
            return -1;
        }


        public PagerAdapter(FragmentActivity fm) {
            super(fm);
            mFragmentManager = fm.getSupportFragmentManager();
            mFragmentAtPos0 = new FilesandFolder_Others_MainPage(listener);
        }

        @NonNull
        @Override
        public Fragment createFragment(int position) {
            switch (position){
                case 0:
                    return new AppSelectionFragment();
                case 1:
                    return new Photos();
                case 2:
                    return new VideoGalleryFragment();
                case 3:
                    return new Gallery();
                default:
                    return mFragmentAtPos0;
            }
        }

        @Override
        public int getItemCount() {
            return 5;
        }
    }

Вот конструктор фрагмента, из которого я хотел бы заменить его другим фрагментом:

    private static FileSelection.PagerAdapter.FirstPageListener pageFragmentListener;

    public FilesandFolder_Others_MainPage() {
    }
    public FilesandFolder_Others_MainPage(FileSelection.PagerAdapter.FirstPageListener firstPageFragmentListener) {
        pageFragmentListener = firstPageFragmentListener;
    }

И при нажатии на представление должен вызываться следующий код: -

Bundle bundle = new Bundle();
bundle.putString("PATH", volumes.get(position).path);
pageFragmentListener.onSwitchToNextFragment(bundle);

Я просмотрел весь stackoverflow и никогда не видел правильного ответа с помощью viewpager 2 и FragmentStateAdapter, поскольку все, похоже, используют Viewpager

Я попытался реализовать это: - FragmentStateAdapter для ViewPager2 notifyItemChanged не работает должным образом < / а>

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


person Saksham Gupta    schedule 13.07.2020    source источник
comment
Я пытаюсь сделать то же самое. Вы решили проблему?   -  person i_o    schedule 10.08.2020


Ответы (2)


Была такая же проблема, и вот что я нашел:

FragmentStateAdapter имеет метод onBindViewHolder(holder, position, payloads), который вы можете переопределить (в отличие от onBindViewHolder(holder, position), который является окончательным). Этот метод вызывается, когда вы вызываете notify методы, а для методов notifyItemChanged(), notifyItemRangeChanged() вы можете получить доступ к фрагменту и обновить его вручную.

Вот код для доступа к фрагменту:

public void onBindViewHolder(@NonNull FragmentViewHolder holder, int position, @NonNull List<Object> payloads) {

    String tag = "f" + holder.getItemId();

    Fragment fragment = fragmentManager.findFragmentByTag(tag);

    if (fragment != null) {
        //manual update fragment
    } else {
        // fragment might be null, if it`s call of notifyDatasetChanged() 
        // which is updates whole list, not specific fragment
        super.onBindViewHolder(holder, position, payloads);
    }
}

FragmentStateAdapter имеет теги "f" + holder.getItemId(), поэтому это работает.

Также вы можете использовать DiffUtil, и это лучший способ. Вот хороший пример решения этой проблемы и несколько ссылок в конце для лучшего понимания ViewPager2.

person Vitalii Husak    schedule 26.08.2020
comment
Приятно знать, что этот onBindViewHolder(...) метод существует и его можно переопределить, чтобы обновить фрагменты в ViewPager2. Любой, кто использует этот подход, должен учитывать, что он полагается на детали реализации FragmentStateAdapter, то есть тот факт, что фрагменты добавляются в FragmentManager с тегом "f" + holder.getItemId(). Вы должны покрыть свое приложение некоторыми тестами пользовательского интерфейса, чтобы во время разработки выявлять, будут ли когда-либо изменены эти детали реализации в будущих версиях класса FragmentStateAdapter. - person Adil Hussain; 17.10.2020
comment
Да, это небезопасно. Может быть, если сравнивать теги фрагментов вручную, а не использовать fragmentManager.findFragmentByTag("f" + holder.getItemId()), будет безопасно. - person Vitalii Husak; 27.10.2020
comment
В моем случае вместо этого у меня есть FragmentActivity, знаете ли вы, как я все еще могу заставить эту работу? - person Luke Galea; 20.05.2021

Сегодня я столкнулся с той же проблемой и подумал, что поделюсь своим решением здесь. Вместо перехвата onBindViewHolder() мое решение просто заменяет старый фрагмент:

OldFragment.instance.containerId?.let {
    replace(it, NewFragment.instance)
}

Чтобы быть более конкретным:

В моем приложении есть ViewPager2 с двумя фрагментами. Назовем их MainFragment и DetailFragment. При нажатии кнопки в MainFragment я хотел заменить его на SecondMainFragment. И наоборот: нажатие кнопки в SecondMainFragment заменяет себя на MainFragment.

class CustomFragmentStateAdapter(
    private val fragmentActivity: FragmentActivity
) : FragmentStateAdapter(fragmentActivity) {

    var displaySecondMainFragment: Boolean = false
        set(value) {
            if (field != value) {
                switchFragments(replaceWithSecondMainFragment = value)
            }
            field = value
        }

    private fun switchFragments(replaceWithSecondMainFragment: Boolean) {
        fragmentActivity.supportFragmentManager.commit {
            if (replaceWithSecondMainFragment) {
                MainFragment.instance.containerId?.let {
                    replace(it, SecondMainFragment.instance)
                }
            } else {
                SecondMainFragment.instance.containerId?.let {
                    replace(it, MainFragment.instance)
                }
            }
        }
    }
        
    // Standard adapter overrides
    override fun getItemCount(): Int = 2

    override fun createFragment(position: Int): Fragment = when (position) {
        0 -> if (displaySecondMainFragment) SecondMainFragment.instance else MainFragment.instance
        else -> DetailFragment.instance
    } 
}

С помощью этого свойства расширения, чтобы получить идентификатор контейнера, в котором находится фрагмент:

inline val Fragment.containerId: Int?
    get() = (view?.parent as? ViewGroup?)?.id

В конце концов, мне нужно было сделать что-то вроде этого:

button.setOnClickListener {
    customFragmentStateAdapter.displaySecondMainFragment = true // Or false for the other way
}
person sebschaef    schedule 30.01.2021