FragmentStateAdapter与Viewpager2中的NotifyDataSetChanged无效。

8

我正在使用FragmentStateAdapter制作带有viewpager2的文件选择页面。在其中一页中,我显示了所有已安装的存储设备,我想要点击查看内容,但我希望内容在另一个片段中显示。

我想在viewpager的最后一页替换片段。但是使用以下代码时,页面会刷新,新的片段不会被创建,因为createFragment没有被调用。

我的PagerAdapter

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;
        }
    }

这是 Fragment 的构造函数,我想把它替换为另一个 Fragment:

    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上搜索了很久,但并没有找到一个适用于ViewPager2和FragmentStateAdapter的合适答案,因为似乎大家都在使用ViewPager。

我尝试实现以下内容:- FragmentStateAdapter for ViewPager2 notifyItemChanged not working as expected

但我还是不明白该如何修改我的代码。请帮忙,谢谢。


我也在尝试做同样的事情。你解决了这个问题吗? - i_o
3个回答

7

我遇到了同样的问题,这是我找到的解决方法:

FragmentStateAdapter 有一个方法 onBindViewHolder(holder, position, payloads),你可以重写它(与 onBindViewHolder(holder, position) 不同,后者是 final 的)。当你调用 notify 方法时,就会调用这个方法。对于 notifyItemChanged()notifyItemRangeChanged() 方法,你可以访问 fragment 并手动更新它。

这里是访问 fragment 的代码:

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


1
知道 onBindViewHolder(...) 方法存在并可以像这样覆盖它来更新 ViewPager2 中的 Fragments 是很好的。任何使用此方法的人都必须考虑到,它依赖于 FragmentStateAdapter 的实现细节,即 Fragments 被添加到 FragmentManager 中时使用 "f" + holder.getItemId() 标签的事实。您应该使用一些 UI 测试来覆盖您的应用程序,以便在 FragmentStateAdapter 类的未来版本中此实现细节发生变化时,在开发时进行检测。 - Adil Hussain
是的,那样不安全。也许手动比较片段标签而不是使用 fragmentManager.findFragmentByTag("f" + holder.getItemId()) 会更安全。 - Vitalii Husak
在我的情况下,我有一个FragmentActivity,你知道我怎么能让它仍然工作吗? - Luke Galea

4

您可以覆盖这两个方法。

    override fun getItemId(position: Int): Long {
        // generate new id
        return getItem(position).hashCode().toLong()
    }

    override fun containsItem(itemId: Long): Boolean {
        // false if item is changed
        return dataList.find { it.hashCode().toLong() == itemId } != null
    }

当屏幕重新创建时,会导致 java.lang.IllegalStateException: Design assumption violated. - Dawid Hyży

1
今天我遇到了同样的问题,想在这里分享我的解决方案。与其拦截onBindViewHolder(),我的解决方案只是替换旧的片段:
OldFragment.instance.containerId?.let {
    replace(it, NewFragment.instance)
}

更具体地说:

我的应用程序有一个带有两个片段的ViewPager2。我们称它们为MainFragmentDetailFragment。在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
    } 
}

使用此扩展属性可以获取片段所在容器的ID:
inline val Fragment.containerId: Int?
    get() = (view?.parent as? ViewGroup?)?.id

最终,我只需要像这样做:
button.setOnClickListener {
    customFragmentStateAdapter.displaySecondMainFragment = true // Or false for the other way
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接